diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f252e8290..f78adbe54e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- `opentelemetry-instrumentation-tornado` Fix server (request) duration metric calculation + ([#3679](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3679)) +- `opentelemetry-instrumentation-tornado` Fix to properly skip all server telemetry when URL excluded. + ([#3680](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3680)) + ## Version 1.36.0/0.57b0 (2025-07-29) ### Fixed diff --git a/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py b/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py index e396558076..bdccb33edb 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py @@ -211,6 +211,7 @@ def client_response_hook(span, future): _logger = getLogger(__name__) _TraceContext = namedtuple("TraceContext", ["activation", "span", "token"]) +_HANDLER_STATE_KEY = "_otel_state_key" _HANDLER_CONTEXT_KEY = "_otel_trace_context_key" _OTEL_PATCHED_KEY = "_otel_patched_key" @@ -402,10 +403,14 @@ def _wrap(cls, method_name, wrapper): def _prepare( tracer, server_histograms, request_hook, func, handler, args, kwargs ): - server_histograms[_START_TIME] = default_timer() - request = handler.request - if _excluded_urls.url_disabled(request.uri): + otel_handler_state = { + _START_TIME: default_timer(), + "exclude_request": _excluded_urls.url_disabled(request.uri), + } + setattr(handler, _HANDLER_STATE_KEY, otel_handler_state) + + if otel_handler_state["exclude_request"]: return func(*args, **kwargs) _record_prepare_metrics(server_histograms, handler) @@ -622,9 +627,11 @@ def _record_prepare_metrics(server_histograms, handler): def _record_on_finish_metrics(server_histograms, handler, error=None): - elapsed_time = round( - (default_timer() - server_histograms[_START_TIME]) * 1000 - ) + otel_handler_state = getattr(handler, _HANDLER_STATE_KEY, None) or {} + if otel_handler_state.get("exclude_request"): + return + start_time = otel_handler_state.get(_START_TIME, None) or default_timer() + elapsed_time = round((default_timer() - start_time) * 1000) response_size = int(handler._headers.get("Content-Length", 0)) metric_attributes = _create_metric_attributes(handler) diff --git a/instrumentation/opentelemetry-instrumentation-tornado/tests/test_metrics_instrumentation.py b/instrumentation/opentelemetry-instrumentation-tornado/tests/test_metrics_instrumentation.py index ea601ce8e9..b84b991c92 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/tests/test_metrics_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-tornado/tests/test_metrics_instrumentation.py @@ -177,3 +177,16 @@ def test_metric_uninstrument(self): for point in list(metric.data.data_points): if isinstance(point, HistogramDataPoint): self.assertEqual(point.count, 1) + + def test_exclude_lists(self): + def test_excluded(path): + self.fetch(path) + + # Verify no server metrics written (only client ones should exist) + metrics = self.get_sorted_metrics() + for metric in metrics: + self.assertTrue("http.server" not in metric.name, metric) + self.assertEqual(len(metrics), 3, metrics) + + test_excluded("/healthz") + test_excluded("/ping")