Skip to content

Commit fece662

Browse files
rads-1996xrmx
andauthored
flask: fixed http_server_request_duration metrics being recorded for excluded urls (open-telemetry#3794)
* Fixed an issue open-telemetry#2352 where http_server_request_duration metrics was being recorded for excluded urls. * Updated CHANGELOG * Fix indentation * Fix tests * Fix assert condition * Update CHANGELOG.md Co-authored-by: Riccardo Magliocchetti <[email protected]> * Addressed comments * Modified logic for excluded urls * Fix test name * Align tests with the same assertion * Update instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py --------- Co-authored-by: Riccardo Magliocchetti <[email protected]>
1 parent 21a35b6 commit fece662

File tree

3 files changed

+112
-25
lines changed

3 files changed

+112
-25
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Fixed
1515

16+
- `opentelemetry-instrumentation-flask`: Do not record `http.server.duration` metrics for excluded URLs.
17+
([#3794](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3794))
1618
- `opentelemetry-instrumentation-botocore`: migrate off the deprecated events API to use the logs API
1719
([#3624](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3624))
1820
- `opentelemetry-instrumentation-dbapi`: fix crash retrieving libpq version when enabling commenter with psycopg

instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,12 @@ def _wrapped_app(wrapped_app_environ, start_response):
350350
active_requests_counter.add(1, active_requests_count_attrs)
351351
request_route = None
352352

353+
should_trace = True
354+
353355
def _start_response(status, response_headers, *args, **kwargs):
354-
if flask.request and (
355-
excluded_urls is None
356-
or not excluded_urls.url_disabled(flask.request.url)
357-
):
356+
nonlocal should_trace
357+
should_trace = _should_trace(excluded_urls)
358+
if should_trace:
358359
nonlocal request_route
359360
request_route = flask.request.url_rule
360361

@@ -395,33 +396,43 @@ def _start_response(status, response_headers, *args, **kwargs):
395396
return start_response(status, response_headers, *args, **kwargs)
396397

397398
result = wsgi_app(wrapped_app_environ, _start_response)
398-
duration_s = default_timer() - start
399-
if duration_histogram_old:
400-
duration_attrs_old = otel_wsgi._parse_duration_attrs(
401-
attributes, _StabilityMode.DEFAULT
402-
)
399+
if should_trace:
400+
duration_s = default_timer() - start
401+
if duration_histogram_old:
402+
duration_attrs_old = otel_wsgi._parse_duration_attrs(
403+
attributes, _StabilityMode.DEFAULT
404+
)
403405

404-
if request_route:
405-
# http.target to be included in old semantic conventions
406-
duration_attrs_old[HTTP_TARGET] = str(request_route)
406+
if request_route:
407+
# http.target to be included in old semantic conventions
408+
duration_attrs_old[HTTP_TARGET] = str(request_route)
407409

408-
duration_histogram_old.record(
409-
max(round(duration_s * 1000), 0), duration_attrs_old
410-
)
411-
if duration_histogram_new:
412-
duration_attrs_new = otel_wsgi._parse_duration_attrs(
413-
attributes, _StabilityMode.HTTP
414-
)
410+
duration_histogram_old.record(
411+
max(round(duration_s * 1000), 0), duration_attrs_old
412+
)
413+
if duration_histogram_new:
414+
duration_attrs_new = otel_wsgi._parse_duration_attrs(
415+
attributes, _StabilityMode.HTTP
416+
)
415417

416-
if request_route:
417-
duration_attrs_new[HTTP_ROUTE] = str(request_route)
418+
if request_route:
419+
duration_attrs_new[HTTP_ROUTE] = str(request_route)
418420

419-
duration_histogram_new.record(
420-
max(duration_s, 0), duration_attrs_new
421-
)
421+
duration_histogram_new.record(
422+
max(duration_s, 0), duration_attrs_new
423+
)
422424
active_requests_counter.add(-1, active_requests_count_attrs)
423425
return result
424426

427+
def _should_trace(excluded_urls) -> bool:
428+
return bool(
429+
flask.request
430+
and (
431+
excluded_urls is None
432+
or not excluded_urls.url_disabled(flask.request.url)
433+
)
434+
)
435+
425436
return _wrapped_app
426437

427438

instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def setUp(self):
164164
self.env_patch = patch.dict(
165165
"os.environ",
166166
{
167-
"OTEL_PYTHON_FLASK_EXCLUDED_URLS": "http://localhost/env_excluded_arg/123,env_excluded_noarg",
167+
"OTEL_PYTHON_FLASK_EXCLUDED_URLS": "http://localhost/env_excluded_arg/123,env_excluded_noarg,env_excluded_arg/789",
168168
OTEL_SEMCONV_STABILITY_OPT_IN: sem_conv_mode,
169169
},
170170
)
@@ -736,6 +736,80 @@ def test_metric_uninstrument(self):
736736
if isinstance(point, HistogramDataPoint):
737737
self.assertEqual(point.count, 1)
738738

739+
def test_flask_metrics_excluded_urls(self):
740+
start = default_timer()
741+
self.client.get("/env_excluded_arg/123")
742+
self.client.get("/env_excluded_noarg")
743+
self.client.get("/env_excluded_arg/789")
744+
duration = max(round((default_timer() - start) * 1000), 0)
745+
metrics_list = self.memory_metrics_reader.get_metrics_data()
746+
number_data_point_seen = False
747+
histogram_data_point_seen = False
748+
self.assertTrue(len(metrics_list.resource_metrics) != 0)
749+
for resource_metric in metrics_list.resource_metrics:
750+
self.assertTrue(len(resource_metric.scope_metrics) != 0)
751+
for scope_metric in resource_metric.scope_metrics:
752+
self.assertTrue(len(scope_metric.metrics) != 0)
753+
for metric in scope_metric.metrics:
754+
self.assertIn(metric.name, _expected_metric_names_old)
755+
data_points = list(metric.data.data_points)
756+
self.assertEqual(len(data_points), 1)
757+
for point in data_points:
758+
if isinstance(point, HistogramDataPoint):
759+
self.assertEqual(point.count, 0)
760+
self.assertAlmostEqual(
761+
duration, point.sum, delta=10
762+
)
763+
histogram_data_point_seen = True
764+
if isinstance(point, NumberDataPoint):
765+
number_data_point_seen = True
766+
for attr in point.attributes:
767+
self.assertIn(
768+
attr,
769+
_recommended_metrics_attrs_old[metric.name],
770+
)
771+
self.assertTrue(number_data_point_seen)
772+
self.assertFalse(histogram_data_point_seen)
773+
774+
def test_flask_metrics_excluded_urls_new_semconv(self):
775+
start = default_timer()
776+
self.client.get("/env_excluded_arg/123")
777+
self.client.get("/env_excluded_noarg")
778+
self.client.get("/env_excluded_arg/789")
779+
duration_s = max(default_timer() - start, 0)
780+
metrics_list = self.memory_metrics_reader.get_metrics_data()
781+
number_data_point_seen = False
782+
histogram_data_point_seen = False
783+
self.assertTrue(len(metrics_list.resource_metrics) != 0)
784+
for resource_metric in metrics_list.resource_metrics:
785+
self.assertTrue(len(resource_metric.scope_metrics) != 0)
786+
for scope_metric in resource_metric.scope_metrics:
787+
self.assertTrue(len(scope_metric.metrics) != 0)
788+
for metric in scope_metric.metrics:
789+
self.assertIn(metric.name, _expected_metric_names_new)
790+
data_points = list(metric.data.data_points)
791+
self.assertEqual(len(data_points), 1)
792+
for point in data_points:
793+
if isinstance(point, HistogramDataPoint):
794+
self.assertEqual(point.count, 0)
795+
self.assertAlmostEqual(
796+
duration_s, point.sum, places=1
797+
)
798+
self.assertEqual(
799+
point.explicit_bounds,
800+
HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
801+
)
802+
histogram_data_point_seen = True
803+
if isinstance(point, NumberDataPoint):
804+
number_data_point_seen = True
805+
for attr in point.attributes:
806+
self.assertIn(
807+
attr,
808+
_recommended_metrics_attrs_new[metric.name],
809+
)
810+
self.assertTrue(number_data_point_seen)
811+
self.assertFalse(histogram_data_point_seen)
812+
739813

740814
class TestProgrammaticHooks(InstrumentationTest, WsgiTestBase):
741815
def setUp(self):

0 commit comments

Comments
 (0)