From 27e4c2ce34156bd0dd967cafb79c91aa1e85f498 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 3 Apr 2024 15:20:57 -0700 Subject: [PATCH 1/7] Update .pylintrc --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index 5ea4385ea0..114dadef75 100644 --- a/.pylintrc +++ b/.pylintrc @@ -81,6 +81,7 @@ disable=missing-docstring, missing-module-docstring, # temp-pylint-upgrade import-error, # needed as a workaround as reported here: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/290 cyclic-import, + not-context-manager # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From 44a0664d449b61d90ede42c28847591242d5311a Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 5 Jun 2024 16:09:45 -0700 Subject: [PATCH 2/7] asgi --- CHANGELOG.md | 5 +++++ .../instrumentation/asgi/__init__.py | 17 ++++++++++++----- .../instrumentation/fastapi/__init__.py | 19 +++++++++++++++++-- .../instrumentation/starlette/__init__.py | 18 ++++++++++++++++-- .../instrumentation/wsgi/__init__.py | 2 ++ 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b92f0c62c7..3324607ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Breaking changes + +- `opentelemetry-instrumentation-asgi`, `opentelemetry-instrumentation-fastapi`, `opentelemetry-instrumentation-starlette` Use `tracer` and `meter` of originating components instead of one from `asgi` middleware + ([#2538](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2538)) + ### Fixed - `opentelemetry-instrumentation-httpx` Ensure httpx.get or httpx.request like methods are instrumented diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py index 8edb3420b1..e416e8dec2 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -461,6 +461,8 @@ class OpenTelemetryMiddleware: scope and event which are sent as dictionaries for when the method send is called. tracer_provider: The optional tracer provider to use. If omitted the current globally configured one is used. + meter_provider: The optional meter provider to use. If omitted + the current globally configured one is used. """ # pylint: disable=too-many-branches @@ -474,17 +476,22 @@ def __init__( client_response_hook: ClientResponseHook = None, tracer_provider=None, meter_provider=None, + tracer=None, meter=None, http_capture_headers_server_request: list[str] | None = None, http_capture_headers_server_response: list[str] | None = None, http_capture_headers_sanitize_fields: list[str] | None = None, ): self.app = guarantee_single_callable(app) - self.tracer = trace.get_tracer( - __name__, - __version__, - tracer_provider, - schema_url="https://opentelemetry.io/schemas/1.11.0", + self.tracer = ( + trace.get_tracer( + __name__, + __version__, + tracer_provider, + schema_url="https://opentelemetry.io/schemas/1.11.0", + ) + if tracer is None + else tracer ) self.meter = ( get_meter( diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 6f52c6ef3b..263cc0fb78 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -188,6 +188,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.metrics import get_meter from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import get_tracer from opentelemetry.util.http import get_excluded_urls, parse_excluded_urls _excluded_urls_from_env = get_excluded_urls("FASTAPI") @@ -221,6 +222,12 @@ def instrument_app( excluded_urls = _excluded_urls_from_env else: excluded_urls = parse_excluded_urls(excluded_urls) + tracer = get_tracer( + __name__, + __version__, + tracer_provider, + schema_url="https://opentelemetry.io/schemas/1.11.0", + ) meter = get_meter( __name__, __version__, @@ -235,7 +242,8 @@ def instrument_app( server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook, - tracer_provider=tracer_provider, + # Pass in tracer/meter to get __name__and __version__ of fastapi instrumentation + tracer=tracer, meter=meter, ) app._is_instrumented_by_opentelemetry = True @@ -298,6 +306,12 @@ class _InstrumentedFastAPI(fastapi.FastAPI): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + tracer = get_tracer( + __name__, + __version__, + _InstrumentedFastAPI._tracer_provider, + schema_url="https://opentelemetry.io/schemas/1.11.0", + ) meter = get_meter( __name__, __version__, @@ -311,7 +325,8 @@ def __init__(self, *args, **kwargs): server_request_hook=_InstrumentedFastAPI._server_request_hook, client_request_hook=_InstrumentedFastAPI._client_request_hook, client_response_hook=_InstrumentedFastAPI._client_response_hook, - tracer_provider=_InstrumentedFastAPI._tracer_provider, + # Pass in tracer/meter to get __name__and __version__ of fastapi instrumentation + tracer=tracer, meter=meter, ) self._is_instrumented_by_opentelemetry = True diff --git a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py index 83f5b5c52b..a99a0756e2 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py @@ -185,6 +185,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from opentelemetry.instrumentation.starlette.version import __version__ from opentelemetry.metrics import get_meter from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import get_tracer from opentelemetry.util.http import get_excluded_urls _excluded_urls = get_excluded_urls("STARLETTE") @@ -208,6 +209,12 @@ def instrument_app( tracer_provider=None, ): """Instrument an uninstrumented Starlette application.""" + tracer = get_tracer( + __name__, + __version__, + tracer_provider, + schema_url="https://opentelemetry.io/schemas/1.11.0", + ) meter = get_meter( __name__, __version__, @@ -222,7 +229,7 @@ def instrument_app( server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook, - tracer_provider=tracer_provider, + # Pass in tracer/meter to get __name__and __version__ of starlette instrumentation meter=meter, ) app.is_instrumented_by_opentelemetry = True @@ -278,6 +285,12 @@ class _InstrumentedStarlette(applications.Starlette): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + tracer = get_tracer( + __name__, + __version__, + _InstrumentedStarlette._tracer_provider, + schema_url="https://opentelemetry.io/schemas/1.11.0", + ) meter = get_meter( __name__, __version__, @@ -291,7 +304,8 @@ def __init__(self, *args, **kwargs): server_request_hook=_InstrumentedStarlette._server_request_hook, client_request_hook=_InstrumentedStarlette._client_request_hook, client_response_hook=_InstrumentedStarlette._client_response_hook, - tracer_provider=_InstrumentedStarlette._tracer_provider, + # Pass in tracer/meter to get __name__and __version__ of starlette instrumentation + tracer=tracer, meter=meter, ) self._is_instrumented_by_opentelemetry = True diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py index 0a873d0fc3..810a07e315 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py @@ -533,6 +533,8 @@ class OpenTelemetryMiddleware: incoming request. tracer_provider: Optional tracer provider to use. If omitted the current globally configured one is used. + meter_provider: Optional meter provider to use. If omitted the current + globally configured one is used. """ def __init__( From 939d1a211477cc45cd6c89f2cf9e78278dba4887 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 5 Jun 2024 16:21:30 -0700 Subject: [PATCH 3/7] changlog --- CHANGELOG.md | 2 +- .../src/opentelemetry/instrumentation/starlette/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3324607ca1..7100623987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking changes - `opentelemetry-instrumentation-asgi`, `opentelemetry-instrumentation-fastapi`, `opentelemetry-instrumentation-starlette` Use `tracer` and `meter` of originating components instead of one from `asgi` middleware - ([#2538](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2538)) + ([#2580](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2580)) ### Fixed diff --git a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py index a99a0756e2..4bb3608935 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py @@ -230,6 +230,7 @@ def instrument_app( client_request_hook=client_request_hook, client_response_hook=client_response_hook, # Pass in tracer/meter to get __name__and __version__ of starlette instrumentation + tracer=tracer, meter=meter, ) app.is_instrumented_by_opentelemetry = True From d18610abad2ce8278c0158e7535048fa181721f3 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 6 Jun 2024 08:19:20 -0700 Subject: [PATCH 4/7] tests --- .../tests/test_asgi_middleware.py | 6 ++++++ .../tests/test_fastapi_instrumentation.py | 8 ++++++++ .../tests/test_starlette_instrumentation.py | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py index 902cd4ec7e..d2cb52d5a0 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -269,6 +269,7 @@ def validate_outputs(self, outputs, error=None, modifiers=None): "name": "GET / http receive", "kind": trace_api.SpanKind.INTERNAL, "attributes": {"asgi.event.type": "http.request"}, + "instrumentation_scope.name": "opentelemetry.instrumentation.asgi", }, { "name": "GET / http send", @@ -309,6 +310,7 @@ def validate_outputs(self, outputs, error=None, modifiers=None): self.assertEqual(span.name, expected["name"]) self.assertEqual(span.kind, expected["kind"]) self.assertDictEqual(dict(span.attributes), expected["attributes"]) + self.assertEqual(span.instrumentation_scope.name, expected["instrumentation_scope.name"]) def test_basic_asgi_call(self): """Test that spans are emitted as expected.""" @@ -728,6 +730,10 @@ def test_asgi_metrics(self): self.assertTrue(len(resource_metric.scope_metrics) != 0) for scope_metric in resource_metric.scope_metrics: self.assertTrue(len(scope_metric.metrics) != 0) + self.assertEqual( + scope_metric.scope.name, + "opentelemetry.instrumentation.asgi", + ) for metric in scope_metric.metrics: self.assertIn(metric.name, _expected_metric_names) data_points = list(metric.data.data_points) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 5cf9a0d590..6499e6ed15 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -117,6 +117,10 @@ def test_instrument_app_with_instrument(self): self.assertEqual(len(spans), 3) for span in spans: self.assertIn("GET /foobar", span.name) + self.assertEqual( + span.instrumentation_scope.name, + "opentelemetry.instrumentation.fastapi" + ) def test_uninstrument_app(self): self._client.get("/foobar") @@ -197,6 +201,10 @@ def test_fastapi_metrics(self): for resource_metric in metrics_list.resource_metrics: self.assertTrue(len(resource_metric.scope_metrics) == 1) for scope_metric in resource_metric.scope_metrics: + self.assertEqual( + scope_metric.scope.name, + "opentelemetry.instrumentation.fastapi", + ) self.assertTrue(len(scope_metric.metrics) == 3) for metric in scope_metric.metrics: self.assertIn(metric.name, _expected_metric_names) diff --git a/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py b/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py index 0accda18fd..04ea7f219e 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py @@ -98,6 +98,10 @@ def test_basic_starlette_call(self): self.assertEqual(len(spans), 3) for span in spans: self.assertIn("GET /foobar", span.name) + self.assertEqual( + span.instrumentation_scope.name, + "opentelemetry.instrumentation.starlette" + ) def test_starlette_route_attribute_added(self): """Ensure that starlette routes are used as the span name.""" @@ -132,6 +136,10 @@ def test_starlette_metrics(self): for resource_metric in metrics_list.resource_metrics: self.assertTrue(len(resource_metric.scope_metrics) == 1) for scope_metric in resource_metric.scope_metrics: + self.assertEqual( + scope_metric.scope.name, + "opentelemetry.instrumentation.starlette", + ) self.assertTrue(len(scope_metric.metrics) == 3) for metric in scope_metric.metrics: self.assertIn(metric.name, _expected_metric_names) From 6c98982d60774d6e9db5a26442f0ada6ca489514 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 6 Jun 2024 08:26:03 -0700 Subject: [PATCH 5/7] Update test_asgi_middleware.py --- .../tests/test_asgi_middleware.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py index d2cb52d5a0..ba0d007bda 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -278,11 +278,13 @@ def validate_outputs(self, outputs, error=None, modifiers=None): SpanAttributes.HTTP_STATUS_CODE: 200, "asgi.event.type": "http.response.start", }, + "instrumentation_scope.name": "opentelemetry.instrumentation.asgi", }, { "name": "GET / http send", "kind": trace_api.SpanKind.INTERNAL, "attributes": {"asgi.event.type": "http.response.body"}, + "instrumentation_scope.name": "opentelemetry.instrumentation.asgi", }, { "name": "GET /", @@ -299,6 +301,7 @@ def validate_outputs(self, outputs, error=None, modifiers=None): SpanAttributes.NET_PEER_PORT: 32767, SpanAttributes.HTTP_STATUS_CODE: 200, }, + "instrumentation_scope.name": "opentelemetry.instrumentation.asgi", }, ] # Run our expected modifiers @@ -310,7 +313,10 @@ def validate_outputs(self, outputs, error=None, modifiers=None): self.assertEqual(span.name, expected["name"]) self.assertEqual(span.kind, expected["kind"]) self.assertDictEqual(dict(span.attributes), expected["attributes"]) - self.assertEqual(span.instrumentation_scope.name, expected["instrumentation_scope.name"]) + self.assertEqual( + span.instrumentation_scope.name, + expected["instrumentation_scope.name"], + ) def test_basic_asgi_call(self): """Test that spans are emitted as expected.""" From b9c66c04ea5247564a4f2e0cae72e0163ca325b9 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 6 Jun 2024 08:36:39 -0700 Subject: [PATCH 6/7] Update test_asgi_middleware.py --- .../tests/test_asgi_middleware.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py index ba0d007bda..d2fe6bc52b 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -269,7 +269,6 @@ def validate_outputs(self, outputs, error=None, modifiers=None): "name": "GET / http receive", "kind": trace_api.SpanKind.INTERNAL, "attributes": {"asgi.event.type": "http.request"}, - "instrumentation_scope.name": "opentelemetry.instrumentation.asgi", }, { "name": "GET / http send", @@ -278,13 +277,11 @@ def validate_outputs(self, outputs, error=None, modifiers=None): SpanAttributes.HTTP_STATUS_CODE: 200, "asgi.event.type": "http.response.start", }, - "instrumentation_scope.name": "opentelemetry.instrumentation.asgi", }, { "name": "GET / http send", "kind": trace_api.SpanKind.INTERNAL, "attributes": {"asgi.event.type": "http.response.body"}, - "instrumentation_scope.name": "opentelemetry.instrumentation.asgi", }, { "name": "GET /", @@ -301,7 +298,6 @@ def validate_outputs(self, outputs, error=None, modifiers=None): SpanAttributes.NET_PEER_PORT: 32767, SpanAttributes.HTTP_STATUS_CODE: 200, }, - "instrumentation_scope.name": "opentelemetry.instrumentation.asgi", }, ] # Run our expected modifiers @@ -315,7 +311,7 @@ def validate_outputs(self, outputs, error=None, modifiers=None): self.assertDictEqual(dict(span.attributes), expected["attributes"]) self.assertEqual( span.instrumentation_scope.name, - expected["instrumentation_scope.name"], + "opentelemetry.instrumentation.asgi", ) def test_basic_asgi_call(self): From 06bfc116d7b307c056e9980fb07fcc1fd4d997b9 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 6 Jun 2024 08:40:48 -0700 Subject: [PATCH 7/7] lint --- .../tests/test_fastapi_instrumentation.py | 2 +- .../tests/test_starlette_instrumentation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 6499e6ed15..948bd343db 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -119,7 +119,7 @@ def test_instrument_app_with_instrument(self): self.assertIn("GET /foobar", span.name) self.assertEqual( span.instrumentation_scope.name, - "opentelemetry.instrumentation.fastapi" + "opentelemetry.instrumentation.fastapi", ) def test_uninstrument_app(self): diff --git a/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py b/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py index 04ea7f219e..1e768982b5 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py @@ -100,7 +100,7 @@ def test_basic_starlette_call(self): self.assertIn("GET /foobar", span.name) self.assertEqual( span.instrumentation_scope.name, - "opentelemetry.instrumentation.starlette" + "opentelemetry.instrumentation.starlette", ) def test_starlette_route_attribute_added(self):