diff --git a/CHANGELOG.md b/CHANGELOG.md index 98bbd3a0bc..5ed29f8ad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `opentelemetry-instrumentation-flask` Add `http.route` and `http.target` to metric attributes + ([#2621](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2621)) - `opentelemetry-instrumentation-sklearn` Deprecated the sklearn instrumentation ([#2708](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2708)) - `opentelemetry-instrumentation-pyramid` Record exceptions raised when serving a request 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 eaf6c79506..9bc5e85a12 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py @@ -266,6 +266,7 @@ def response_hook(span: Span, status: str, response_headers: List): ) from opentelemetry.instrumentation.utils import _start_internal_or_server_span from opentelemetry.metrics import get_meter +from opentelemetry.semconv.attributes.http_attributes import HTTP_ROUTE from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.semconv.metrics.http_metrics import ( HTTP_SERVER_REQUEST_DURATION, @@ -340,12 +341,16 @@ def _wrapped_app(wrapped_app_environ, start_response): ) active_requests_counter.add(1, active_requests_count_attrs) + request_route = None def _start_response(status, response_headers, *args, **kwargs): if flask.request and ( excluded_urls is None or not excluded_urls.url_disabled(flask.request.url) ): + nonlocal request_route + request_route = flask.request.url_rule + span = flask.request.environ.get(_ENVIRON_SPAN_KEY) propagator = get_global_response_propagator() @@ -388,6 +393,12 @@ def _start_response(status, response_headers, *args, **kwargs): duration_attrs_old = otel_wsgi._parse_duration_attrs( attributes, _HTTPStabilityMode.DEFAULT ) + + if request_route: + duration_attrs_old[SpanAttributes.HTTP_TARGET] = str( + request_route + ) + duration_histogram_old.record( max(round(duration_s * 1000), 0), duration_attrs_old ) @@ -395,6 +406,10 @@ def _start_response(status, response_headers, *args, **kwargs): duration_attrs_new = otel_wsgi._parse_duration_attrs( attributes, _HTTPStabilityMode.HTTP ) + + if request_route: + duration_attrs_new[HTTP_ROUTE] = str(request_route) + 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 94437bbfd2..4458daae21 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py +++ b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py @@ -85,6 +85,12 @@ def expected_attributes_new(override_attributes): return default_attributes +_server_duration_attrs_old_copy = _server_duration_attrs_old.copy() +_server_duration_attrs_old_copy.append("http.target") + +_server_duration_attrs_new_copy = _server_duration_attrs_new.copy() +_server_duration_attrs_new_copy.append("http.route") + _expected_metric_names_old = [ "http.server.active_requests", "http.server.duration", @@ -95,11 +101,11 @@ def expected_attributes_new(override_attributes): ] _recommended_metrics_attrs_old = { "http.server.active_requests": _server_active_requests_count_attrs_old, - "http.server.duration": _server_duration_attrs_old, + "http.server.duration": _server_duration_attrs_old_copy, } _recommended_metrics_attrs_new = { "http.server.active_requests": _server_active_requests_count_attrs_new, - "http.server.request.duration": _server_duration_attrs_new, + "http.server.request.duration": _server_duration_attrs_new_copy, } _server_active_requests_count_attrs_both = ( _server_active_requests_count_attrs_old @@ -109,8 +115,8 @@ def expected_attributes_new(override_attributes): ) _recommended_metrics_attrs_both = { "http.server.active_requests": _server_active_requests_count_attrs_both, - "http.server.duration": _server_duration_attrs_old, - "http.server.request.duration": _server_duration_attrs_new, + "http.server.duration": _server_duration_attrs_old_copy, + "http.server.request.duration": _server_duration_attrs_new_copy, } @@ -570,6 +576,7 @@ def test_basic_metric_success(self): self.client.get("/hello/756") expected_duration_attributes = { "http.method": "GET", + "http.target": "/hello/", "http.host": "localhost", "http.scheme": "http", "http.flavor": "1.1", @@ -595,6 +602,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, }