diff --git a/CHANGELOG.md b/CHANGELOG.md index bbbf49f453..8968e1a05f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `opentelemetry-instrumentation-flask` Add http route to metric attributes + ([#2506](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2506)) +- - `opentelemetry-sdk-extension-aws` Register AWS resource detectors under the `opentelemetry_resource_detector` entry point ([#2382](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2382)) diff --git a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py index 34e9b5ea50..616850959f 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py @@ -284,6 +284,7 @@ def response_hook(span: Span, status: str, response_headers: List): _ENVIRON_ACTIVATION_KEY = "opentelemetry-flask.activation_key" _ENVIRON_REQCTX_REF_KEY = "opentelemetry-flask.reqctx_ref_key" _ENVIRON_TOKEN = "opentelemetry-flask.token" +_ENVIRON_REQUEST_ROUTE_KEY = "request-route_key" _excluded_urls_from_env = get_excluded_urls("FLASK") @@ -346,6 +347,11 @@ def _start_response(status, response_headers, *args, **kwargs): excluded_urls is None or not excluded_urls.url_disabled(flask.request.url) ): + if flask.request.url_rule: + wrapped_app_environ[_ENVIRON_REQUEST_ROUTE_KEY] = str( + flask.request.url_rule + ) + span = flask.request.environ.get(_ENVIRON_SPAN_KEY) propagator = get_global_response_propagator() @@ -388,6 +394,12 @@ def _start_response(status, response_headers, *args, **kwargs): duration_attrs_old = otel_wsgi._parse_duration_attrs( attributes, _HTTPStabilityMode.DEFAULT ) + + if wrapped_app_environ.get(_ENVIRON_REQUEST_ROUTE_KEY, None): + duration_attrs_old[SpanAttributes.HTTP_ROUTE] = ( + wrapped_app_environ.get(_ENVIRON_REQUEST_ROUTE_KEY) + ) + duration_histogram_old.record( max(round(duration_s * 1000), 0), duration_attrs_old ) @@ -395,6 +407,12 @@ def _start_response(status, response_headers, *args, **kwargs): duration_attrs_new = otel_wsgi._parse_duration_attrs( attributes, _HTTPStabilityMode.HTTP ) + + if wrapped_app_environ.get(_ENVIRON_REQUEST_ROUTE_KEY, None): + duration_attrs_new[SpanAttributes.HTTP_ROUTE] = ( + wrapped_app_environ.get(_ENVIRON_REQUEST_ROUTE_KEY) + ) + duration_histogram_new.record( max(duration_s, 0), duration_attrs_new ) diff --git a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py index f50d3245a0..3f16764a76 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py +++ b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py @@ -459,6 +459,7 @@ def test_exclude_lists_from_explicit(self): self.assertEqual(len(span_list), 1) def test_flask_metrics(self): + _server_duration_attrs_old.append("http.route") start = default_timer() self.client.get("/hello/123") self.client.get("/hello/321") @@ -570,6 +571,7 @@ def test_basic_metric_success(self): self.client.get("/hello/756") expected_duration_attributes = { "http.method": "GET", + "http.route": "/hello/", "http.host": "localhost", "http.scheme": "http", "http.flavor": "1.1", @@ -597,6 +599,7 @@ def test_basic_metric_success_new_semconv(self): expected_duration_attributes = { "http.request.method": "GET", "url.scheme": "http", + "http.route": "/hello/", "network.protocol.version": "1.1", "http.response.status_code": 200, }