Skip to content

Commit 4d6e5c3

Browse files
authored
Prevent random values to become metrics labels values (#3534)
* Prevent random values to become metrics labels values * Add method and status to size and duration metrics * Fix assertion
1 parent 6c47ed0 commit 4d6e5c3

File tree

4 files changed

+36
-25
lines changed

4 files changed

+36
-25
lines changed

kinto/core/initialization.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -472,10 +472,18 @@ def on_new_response(event):
472472
auth, user_id = user_id.split(":")
473473
metrics_service.count("users", unique=[("auth", auth), ("userid", user_id)])
474474

475+
status = event.response.status_code
476+
477+
if status >= 400:
478+
# Prevent random values of 404 responses to become label values.
479+
request_matchdict = {}
480+
else:
481+
request_matchdict = dict(request.matchdict or {})
482+
475483
# Add extra labels to metrics, based on fields extracted from the request matchdict.
476484
metrics_matchdict_fields = aslist(settings["metrics_matchdict_fields"])
477485
# Turn the `id` field of object endpoints into `{resource}_id` (eg. `mushroom_id`, `bucket_id`)
478-
enhanced_matchdict = dict(request.matchdict or {})
486+
enhanced_matchdict = request_matchdict
479487
try:
480488
enhanced_matchdict[request.current_resource_name + "_id"] = enhanced_matchdict.get(
481489
"id", ""
@@ -487,8 +495,6 @@ def on_new_response(event):
487495
(field, enhanced_matchdict.get(field, "")) for field in metrics_matchdict_fields
488496
]
489497

490-
status = event.response.status_code
491-
492498
service = request.current_service
493499
if service:
494500
# Use the service name as endpoint if available.
@@ -501,35 +507,30 @@ def on_new_response(event):
501507
"unnamed" if status != 404 else "unknown"
502508
) # Do not multiply cardinality for unknown endpoints.
503509

510+
request_labels = [
511+
("method", request.method.lower()),
512+
("endpoint", endpoint),
513+
("status", str(status)),
514+
] + metrics_matchdict_labels
515+
504516
# Count served requests.
505-
metrics_service.count(
506-
"request_summary",
507-
unique=[
508-
("method", request.method.lower()),
509-
("endpoint", endpoint),
510-
("status", str(status)),
511-
]
512-
+ metrics_matchdict_labels,
513-
)
517+
metrics_service.count("request_summary", unique=request_labels)
514518

515519
try:
516520
current = utils.msec_time()
517521
duration = current - request._received_at
518522
metrics_service.timer(
519523
"request_duration",
520524
value=duration,
521-
labels=[("endpoint", endpoint), ("method", request.method.lower())]
522-
+ metrics_matchdict_labels,
525+
labels=request_labels,
523526
)
524527
except AttributeError: # pragma: no cover
525528
# Logging was not setup in this Kinto app (unlikely but possible)
526529
pass
527530

528531
# Observe response size.
529532
metrics_service.observe(
530-
"request_size",
531-
len(event.response.body or b""),
532-
labels=[("endpoint", endpoint)] + metrics_matchdict_labels,
533+
"request_size", len(event.response.body or b""), labels=request_labels
533534
)
534535

535536
# Count authentication verifications.

tests/core/test_initialization.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ def test_statsd_observe_request_size(self):
433433
self.mocked().observe.assert_any_call(
434434
"request_size",
435435
len("{}"),
436-
labels=[("endpoint", "heartbeat")],
436+
labels=[("method", "get"), ("endpoint", "heartbeat"), ("status", "200")],
437437
)
438438

439439
def test_statsd_observe_request_duration(self):
@@ -443,7 +443,7 @@ def test_statsd_observe_request_duration(self):
443443
self.mocked().timer.assert_any_call(
444444
"request_duration",
445445
value=mock.ANY,
446-
labels=[("endpoint", "heartbeat"), ("method", "get")],
446+
labels=[("method", "get"), ("endpoint", "heartbeat"), ("status", "200")],
447447
)
448448

449449
def test_statsd_counts_unknown_urls(self):

tests/plugins/test_prometheus.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,10 @@ def test_metrics_excluded_labels(self):
196196
self.assertNotIn("group_id=", resp.text)
197197
self.assertNotIn("record_id=", resp.text)
198198
self.assertIn(
199-
'kintoprod_request_size_count{bucket_id="bid",collection_id="",endpoint="group-object"}',
199+
'kintoprod_request_size_count{bucket_id="bid",collection_id="",endpoint="group-object",method="put",status="201"}',
200200
resp.text,
201201
)
202202
self.assertIn(
203-
'kintoprod_request_size_count{bucket_id="bid",collection_id="cid",endpoint="record-object"}',
203+
'kintoprod_request_size_count{bucket_id="bid",collection_id="cid",endpoint="record-object",method="put",status="201"}',
204204
resp.text,
205205
)

tests/test_views_metrics.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,21 @@ def test_metrics_have_matchdict_labels(self):
3131
self.app.get("/buckets/beers/collections/barley/records", headers=self.headers)
3232

3333
resp = self.app.get("/__metrics__")
34-
print(resp.text)
34+
3535
self.assertIn(
36-
'request_size_sum{bucket_id="beers",collection_id="",endpoint="bucket-object",group_id="",record_id=""}',
36+
'request_size_sum{bucket_id="beers",collection_id="",endpoint="bucket-object",group_id="",method="put",record_id="",status="201"}',
3737
resp.text,
3838
)
3939
self.assertIn(
40-
'request_size_sum{bucket_id="beers",collection_id="",endpoint="group-object",group_id="amateurs",record_id=""}',
40+
'request_size_sum{bucket_id="beers",collection_id="",endpoint="group-object",group_id="amateurs",method="put",record_id="",status="201"}',
4141
resp.text,
4242
)
4343
self.assertIn(
4444
'request_summary_total{bucket_id="beers",collection_id="barley",endpoint="collection-object",group_id="",method="put",record_id="",status="201"}',
4545
resp.text,
4646
)
4747
self.assertIn(
48-
'request_duration_sum{bucket_id="beers",collection_id="barley",endpoint="record-object",group_id="",method="put",record_id="abc"}',
48+
'request_duration_sum{bucket_id="beers",collection_id="barley",endpoint="record-object",group_id="",method="put",record_id="abc",status="201"}',
4949
resp.text,
5050
)
5151

@@ -61,3 +61,13 @@ def test_metrics_have_matchdict_labels(self):
6161
'request_summary_total{bucket_id="beers",collection_id="barley",endpoint="record-plural",group_id="",method="get",record_id="",status="200"}',
6262
resp.text,
6363
)
64+
65+
def test_4xx_do_not_have_matchdict_labels_values(self):
66+
self.app.get("/buckets/water", headers=self.headers, status=403)
67+
68+
resp = self.app.get("/__metrics__")
69+
70+
self.assertIn(
71+
'request_summary_total{bucket_id="",collection_id="",endpoint="bucket-object",group_id="",method="get",record_id="",status="403"}',
72+
resp.text,
73+
)

0 commit comments

Comments
 (0)