From 85648eeb71a76bc8320cdd2077363acf132a1b2f Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Fri, 16 Aug 2024 10:21:44 +0300 Subject: [PATCH 01/20] feat: add option to disable internal send and receive spans Fixes #831 --- .../instrumentation/asgi/__init__.py | 112 +++++++++++------- .../asgi/environment_variables.py | 24 ++++ 2 files changed, 90 insertions(+), 46 deletions(-) create mode 100644 instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/environment_variables.py 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 8e3199cef8..73af611f45 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -191,9 +191,11 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from __future__ import annotations +import os import typing import urllib from collections import defaultdict +from distutils.util import strtobool from functools import wraps from timeit import default_timer from typing import Any, Awaitable, Callable, DefaultDict, Tuple @@ -226,6 +228,10 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A _set_http_user_agent, _set_status, ) +from opentelemetry.instrumentation.asgi.environment_variables import ( + OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN, + OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN, +) from opentelemetry.instrumentation.asgi.types import ( ClientRequestHook, ClientResponseHook, @@ -527,6 +533,8 @@ class OpenTelemetryMiddleware: the current globally configured one is used. meter_provider: The optional meter provider to use. If omitted the current globally configured one is used. + exclude_receive_span: Optional flag to exclude the http receive span from the trace. + exclude_send_span: Optional flag to exclude the http send span from the trace. """ # pylint: disable=too-many-branches @@ -545,6 +553,8 @@ def __init__( 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, + exclude_receive_span: bool = False, + exclude_send_span: bool = False, ): # initialize semantic conventions opt-in if needed _OpenTelemetrySemanticConventionStability._initialize() @@ -651,6 +661,12 @@ def __init__( ) or [] ) + self.exclude_receive_span = exclude_receive_span or strtobool( + os.getenv(OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN, "false") + ) + self.exclude_send_span = exclude_send_span or strtobool( + os.getenv(OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN, "false") + ) # pylint: disable=too-many-statements async def __call__( @@ -796,6 +812,8 @@ async def __call__( # pylint: enable=too-many-branches def _get_otel_receive(self, server_span_name, scope, receive): + if self.exclude_receive_span: + return receive @wraps(receive) async def otel_receive(): with self.tracer.start_as_current_span( @@ -832,41 +850,48 @@ def _get_otel_send( @wraps(send) async def otel_send(message: dict[str, Any]): nonlocal expecting_trailers - with self.tracer.start_as_current_span( - " ".join((server_span_name, scope["type"], "send")) - ) as send_span: - if callable(self.client_response_hook): - self.client_response_hook(send_span, scope, message) - - status_code = None - if message["type"] == "http.response.start": - status_code = message["status"] - elif message["type"] == "websocket.send": - status_code = 200 - - if send_span.is_recording(): - if message["type"] == "http.response.start": - expecting_trailers = message.get("trailers", False) - send_span.set_attribute("asgi.event.type", message["type"]) - if ( - server_span.is_recording() - and server_span.kind == trace.SpanKind.SERVER - and "headers" in message - ): - custom_response_attributes = ( - collect_custom_headers_attributes( - message, - self.http_capture_headers_sanitize_fields, - self.http_capture_headers_server_response, - normalise_response_header_name, + + status_code = None + if message["type"] == "http.response.start": + status_code = message["status"] + expecting_trailers = message.get("trailers", False) + elif message["type"] == "websocket.send": + status_code = 200 + + # Conditional send_span creation + if not self.exclude_send_span: + with self.tracer.start_as_current_span( + " ".join((server_span_name, scope["type"], "send")) + ) as send_span: + if callable(self.client_response_hook): + self.client_response_hook(send_span, scope, message) + + if send_span.is_recording(): + send_span.set_attribute("asgi.event.type", message["type"]) + if status_code: + set_status_code( + send_span, + status_code, + None, + self._sem_conv_opt_in_mode, ) - if self.http_capture_headers_server_response - else {} + + # Server span logic always applied + if server_span.is_recording() and "headers" in message: + if server_span.kind == trace.SpanKind.SERVER: + custom_response_attributes = ( + collect_custom_headers_attributes( + message, + self.http_capture_headers_sanitize_fields, + self.http_capture_headers_server_response, + normalise_response_header_name, ) - if len(custom_response_attributes) > 0: - server_span.set_attributes( - custom_response_attributes - ) + if self.http_capture_headers_server_response + else {} + ) + if len(custom_response_attributes) > 0: + server_span.set_attributes(custom_response_attributes) + if status_code: # We record metrics only once set_status_code( @@ -875,12 +900,6 @@ async def otel_send(message: dict[str, Any]): duration_attrs, self._sem_conv_opt_in_mode, ) - set_status_code( - send_span, - status_code, - None, - self._sem_conv_opt_in_mode, - ) propagator = get_global_response_propagator() if propagator: @@ -892,14 +911,15 @@ async def otel_send(message: dict[str, Any]): setter=asgi_setter, ) - content_length = asgi_getter.get(message, "content-length") - if content_length: - try: - self.content_length_header = int(content_length[0]) - except ValueError: - pass + content_length = asgi_getter.get(message, "content-length") + if content_length: + try: + self.content_length_header = int(content_length[0]) + except ValueError: + pass + + await send(message) - await send(message) # pylint: disable=too-many-boolean-expressions if ( not expecting_trailers diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/environment_variables.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/environment_variables.py new file mode 100644 index 0000000000..3d9b435ce9 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/environment_variables.py @@ -0,0 +1,24 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Exclude the http send span from the tracing feature in Python. +""" + +OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN = "OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN" + +""" +Exclude the http receive span from the tracing feature in Python. +""" +OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN = "OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN" From 5b7759a76e17d6a1e66a246995ed11f4a6d36b5d Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Fri, 16 Aug 2024 12:51:00 +0300 Subject: [PATCH 02/20] fix: broken custom headers --- .../instrumentation/asgi/__init__.py | 79 ++++++++++--------- 1 file changed, 43 insertions(+), 36 deletions(-) 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 73af611f45..88c85e2c5c 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -487,7 +487,7 @@ def get_default_span_details(scope: dict) -> Tuple[str, dict]: def _collect_target_attribute( - scope: typing.Dict[str, typing.Any] + scope: typing.Dict[str, typing.Any], ) -> typing.Optional[str]: """ Returns the target path as defined by the Semantic Conventions. @@ -814,6 +814,7 @@ async def __call__( def _get_otel_receive(self, server_span_name, scope, receive): if self.exclude_receive_span: return receive + @wraps(receive) async def otel_receive(): with self.tracer.start_as_current_span( @@ -858,7 +859,6 @@ async def otel_send(message: dict[str, Any]): elif message["type"] == "websocket.send": status_code = 200 - # Conditional send_span creation if not self.exclude_send_span: with self.tracer.start_as_current_span( " ".join((server_span_name, scope["type"], "send")) @@ -867,7 +867,11 @@ async def otel_send(message: dict[str, Any]): self.client_response_hook(send_span, scope, message) if send_span.is_recording(): - send_span.set_attribute("asgi.event.type", message["type"]) + if message["type"] == "http.response.start": + expecting_trailers = message.get("trailers", False) + send_span.set_attribute( + "asgi.event.type", message["type"] + ) if status_code: set_status_code( send_span, @@ -876,40 +880,43 @@ async def otel_send(message: dict[str, Any]): self._sem_conv_opt_in_mode, ) - # Server span logic always applied - if server_span.is_recording() and "headers" in message: - if server_span.kind == trace.SpanKind.SERVER: - custom_response_attributes = ( - collect_custom_headers_attributes( - message, - self.http_capture_headers_sanitize_fields, - self.http_capture_headers_server_response, - normalise_response_header_name, - ) - if self.http_capture_headers_server_response - else {} - ) - if len(custom_response_attributes) > 0: - server_span.set_attributes(custom_response_attributes) - - if status_code: - # We record metrics only once - set_status_code( - server_span, - status_code, - duration_attrs, - self._sem_conv_opt_in_mode, - ) + if ( + server_span.is_recording() + and server_span.kind == trace.SpanKind.SERVER + and "headers" in message + ): + custom_response_attributes = ( + collect_custom_headers_attributes( + message, + self.http_capture_headers_sanitize_fields, + self.http_capture_headers_server_response, + normalise_response_header_name, + ) + if self.http_capture_headers_server_response + else {} + ) + if len(custom_response_attributes) > 0: + server_span.set_attributes( + custom_response_attributes + ) + + if status_code: + set_status_code( + server_span, + status_code, + duration_attrs, + self._sem_conv_opt_in_mode, + ) - propagator = get_global_response_propagator() - if propagator: - propagator.inject( - message, - context=set_span_in_context( - server_span, trace.context_api.Context() - ), - setter=asgi_setter, - ) + propagator = get_global_response_propagator() + if propagator: + propagator.inject( + message, + context=set_span_in_context( + server_span, trace.context_api.Context() + ), + setter=asgi_setter, + ) content_length = asgi_getter.get(message, "content-length") if content_length: From b6faf30dc9d9e04d5a65bce8dce3397dab6bbdcc Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Fri, 16 Aug 2024 15:22:52 +0300 Subject: [PATCH 03/20] test(TestAsgiApplication): add test for internal span exclusion Tested-by: Tobias Backer Dirks --- .../instrumentation/asgi/__init__.py | 5 +++++ .../tests/test_asgi_middleware.py | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) 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 88c85e2c5c..2644effee3 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -185,6 +185,11 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A Note: The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. +Internal HTTP spans +***************************** +Internal HTTP send and receive spans are added by default. These can optionally be excluded by setting the boolean environment variables +``OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN`` and ``OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN`` to ``true``. + API --- """ diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py index af51faa808..e6a2aa830f 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -572,6 +572,27 @@ def test_background_execution(self): _SIMULATED_BACKGROUND_TASK_EXECUTION_TIME_S * 10**9, ) + def test_exclude_internal_spans(self): + """Test that internal spans are excluded from the emitted spans when + the `exclude_receive_span` or `exclude_send_span` attributes are set. + """ + app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) + self.seed_app(app) + cases = [ + (True, True, ["GET / http receive", "GET / http send"]), + (False, True, ["GET / http send"]), + (True, False, ["GET / http receive"]), + (False, False, []), + ] + for exclude_receive_span, exclude_send_span, excluded_spans in cases: + app.exclude_receive_span = exclude_receive_span + app.exclude_send_span = exclude_send_span + self.send_default_request() + span_list = self.memory_exporter.get_finished_spans() + for span in span_list: + for excluded_span in excluded_spans: + self.assertNotEqual(span.name, excluded_span) + def test_trailers(self): """Test that trailers are emitted as expected and that the server span is ended BEFORE the background task is finished.""" From 15b4d60123a45d4b2a346ee037934b421408fae9 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Fri, 16 Aug 2024 16:00:06 +0300 Subject: [PATCH 04/20] chore: update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39236a33f3..1bd16f0777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-kafka-python` Instrument temporary fork, kafka-python-ng inside kafka-python's instrumentation ([#2537](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2537)) +- `opentelemetry-instrumentation-asgi` Add ability to disable internal HTTP send and receive spans + ([#2802](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2802)) ## Breaking changes From 2073d2a506825ac496bb04e78555cb8a0b60ffe8 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Mon, 19 Aug 2024 18:50:13 +0300 Subject: [PATCH 05/20] refactor: remove environment variables for internal span toggle --- .../instrumentation/asgi/__init__.py | 18 ++------------ .../asgi/environment_variables.py | 24 ------------------- 2 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/environment_variables.py 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 2644effee3..2040874e36 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -185,11 +185,6 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A Note: The environment variable names used to capture HTTP headers are still experimental, and thus are subject to change. -Internal HTTP spans -***************************** -Internal HTTP send and receive spans are added by default. These can optionally be excluded by setting the boolean environment variables -``OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN`` and ``OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN`` to ``true``. - API --- """ @@ -200,7 +195,6 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A import typing import urllib from collections import defaultdict -from distutils.util import strtobool from functools import wraps from timeit import default_timer from typing import Any, Awaitable, Callable, DefaultDict, Tuple @@ -233,10 +227,6 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A _set_http_user_agent, _set_status, ) -from opentelemetry.instrumentation.asgi.environment_variables import ( - OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN, - OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN, -) from opentelemetry.instrumentation.asgi.types import ( ClientRequestHook, ClientResponseHook, @@ -666,12 +656,8 @@ def __init__( ) or [] ) - self.exclude_receive_span = exclude_receive_span or strtobool( - os.getenv(OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN, "false") - ) - self.exclude_send_span = exclude_send_span or strtobool( - os.getenv(OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN, "false") - ) + self.exclude_receive_span = exclude_receive_span + self.exclude_send_span = exclude_send_span # pylint: disable=too-many-statements async def __call__( diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/environment_variables.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/environment_variables.py deleted file mode 100644 index 3d9b435ce9..0000000000 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/environment_variables.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright The OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Exclude the http send span from the tracing feature in Python. -""" - -OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN = "OTEL_PYTHON_ASGI_EXCLUDE_SEND_SPAN" - -""" -Exclude the http receive span from the tracing feature in Python. -""" -OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN = "OTEL_PYTHON_ASGI_EXCLUDE_RECEIVE_SPAN" From 906721b66ee15d1de64181059e0afaa19f3f77aa Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Mon, 19 Aug 2024 18:51:55 +0300 Subject: [PATCH 06/20] chore: remove unused import --- .../src/opentelemetry/instrumentation/asgi/__init__.py | 1 - 1 file changed, 1 deletion(-) 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 2040874e36..4d0ae8029e 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -191,7 +191,6 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from __future__ import annotations -import os import typing import urllib from collections import defaultdict From 0741bc61ef71b7d11366a629c7687110cf071016 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Mon, 19 Aug 2024 19:04:06 +0300 Subject: [PATCH 07/20] refactor(FastAPIInstrumentor): add ability to exclude internal send and receive spans --- .../instrumentation/fastapi/__init__.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) 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 37a293764e..1f82894f9f 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -232,8 +232,30 @@ def instrument_app( 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, + exclude_receive_span: bool = False, + exclude_send_span: bool = False, ): - """Instrument an uninstrumented FastAPI application.""" + """Instrument an uninstrumented FastAPI application. + + Args: + app: The ASGI application callable to forward requests to. + server_request_hook: Optional callback which is called with the server span and ASGI + scope object for every incoming request. + client_request_hook: Optional callback which is called with the internal span, and ASGI + scope and event which are sent as dictionaries for when the method receive is called. + client_response_hook: Optional callback which is called with the internal span, and ASGI + 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. + excluded_urls: Optional comma delimited string of regexes to match URLs that should not be traced. + http_capture_headers_server_request: Optional list of HTTP headers to capture from the request. + http_capture_headers_server_response: Optional list of HTTP headers to capture from the response. + http_capture_headers_sanitize_fields: Optional list of HTTP headers to sanitize. + exclude_receive_span: Optional flag to exclude the http receive span from the trace. + exclude_send_span: Optional flag to exclude the http send span from the trace. + """ if not hasattr(app, "_is_instrumented_by_opentelemetry"): app._is_instrumented_by_opentelemetry = False @@ -273,6 +295,8 @@ def instrument_app( http_capture_headers_server_request=http_capture_headers_server_request, http_capture_headers_server_response=http_capture_headers_server_response, http_capture_headers_sanitize_fields=http_capture_headers_sanitize_fields, + exclude_receive_span=exclude_receive_span, + exclude_send_span=exclude_send_span, ) app._is_instrumented_by_opentelemetry = True if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: @@ -373,6 +397,8 @@ def __init__(self, *args, **kwargs): http_capture_headers_server_request=_InstrumentedFastAPI._http_capture_headers_server_request, http_capture_headers_server_response=_InstrumentedFastAPI._http_capture_headers_server_response, http_capture_headers_sanitize_fields=_InstrumentedFastAPI._http_capture_headers_sanitize_fields, + exclude_receive_span=False, + exclude_send_span=False, ) self._is_instrumented_by_opentelemetry = True _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) From 839dbf9d6279d05e6fce3d0d890ec5e1f5235e11 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Mon, 19 Aug 2024 20:33:15 +0300 Subject: [PATCH 08/20] refactor: remove spurious addition --- .../src/opentelemetry/instrumentation/asgi/__init__.py | 1 - 1 file changed, 1 deletion(-) 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 4d0ae8029e..dbe907d416 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -845,7 +845,6 @@ async def otel_send(message: dict[str, Any]): status_code = None if message["type"] == "http.response.start": status_code = message["status"] - expecting_trailers = message.get("trailers", False) elif message["type"] == "websocket.send": status_code = 200 From 52bd6600a1abfce1bfba3b2a41682ef3ee5aaa37 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Mon, 19 Aug 2024 20:45:13 +0300 Subject: [PATCH 09/20] docs: update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd16f0777..faab6fda07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-kafka-python` Instrument temporary fork, kafka-python-ng inside kafka-python's instrumentation ([#2537](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2537)) -- `opentelemetry-instrumentation-asgi` Add ability to disable internal HTTP send and receive spans +- `opentelemetry-instrumentation-asgi`, `opentelemetry-instrumentation-fastapi` Add ability to disable internal HTTP send and receive spans ([#2802](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2802)) ## Breaking changes From b1e445e862897a1a0d050c03f0046d1292b17737 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Mon, 19 Aug 2024 20:51:06 +0300 Subject: [PATCH 10/20] fix: add missing instrumentation --- .../opentelemetry/instrumentation/fastapi/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 1f82894f9f..a2cf0046c7 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -347,6 +347,12 @@ def _instrument(self, **kwargs): else parse_excluded_urls(_excluded_urls) ) _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") + _InstrumentedFastAPI._exclude_receive_span = kwargs.get( + "exclude_receive_span" + ) + _InstrumentedFastAPI._exclude_send_span = kwargs.get( + "exclude_send_span" + ) fastapi.FastAPI = _InstrumentedFastAPI def _uninstrument(self, **kwargs): @@ -397,8 +403,8 @@ def __init__(self, *args, **kwargs): http_capture_headers_server_request=_InstrumentedFastAPI._http_capture_headers_server_request, http_capture_headers_server_response=_InstrumentedFastAPI._http_capture_headers_server_response, http_capture_headers_sanitize_fields=_InstrumentedFastAPI._http_capture_headers_sanitize_fields, - exclude_receive_span=False, - exclude_send_span=False, + exclude_receive_span=_InstrumentedFastAPI._exclude_receive_span, + exclude_send_span=_InstrumentedFastAPI._exclude_send_span, ) self._is_instrumented_by_opentelemetry = True _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) From a5bfd43869ef4d7f332896fb88681e11f79fcf9d Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Tue, 20 Aug 2024 07:15:50 +0300 Subject: [PATCH 11/20] chore: fix too-many-branches ignore --- .../src/opentelemetry/instrumentation/asgi/__init__.py | 1 - 1 file changed, 1 deletion(-) 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 dbe907d416..2684a2fb9a 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -800,7 +800,6 @@ async def __call__( span.end() # pylint: enable=too-many-branches - def _get_otel_receive(self, server_span_name, scope, receive): if self.exclude_receive_span: return receive From 9daafa75576a2a33dc486f5f74b52795556da2bc Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Tue, 20 Aug 2024 10:31:36 +0300 Subject: [PATCH 12/20] docs: appease sphinx autodoc --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a2cf0046c7..a4668a9f73 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -222,7 +222,7 @@ class FastAPIInstrumentor(BaseInstrumentor): @staticmethod def instrument_app( - app: fastapi.FastAPI, + app, server_request_hook: ServerRequestHook = None, client_request_hook: ClientRequestHook = None, client_response_hook: ClientResponseHook = None, @@ -238,7 +238,7 @@ def instrument_app( """Instrument an uninstrumented FastAPI application. Args: - app: The ASGI application callable to forward requests to. + app: The fastapi ASGI application callable to forward requests to. server_request_hook: Optional callback which is called with the server span and ASGI scope object for every incoming request. client_request_hook: Optional callback which is called with the internal span, and ASGI From 9c32d80e1d9e9c339d96ab05ee271b5c1aade0c4 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Wed, 21 Aug 2024 08:49:24 +0300 Subject: [PATCH 13/20] fix: server span logic should always be executed if recording --- .../instrumentation/asgi/__init__.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) 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 2684a2fb9a..bbf90499b7 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -868,25 +868,25 @@ async def otel_send(message: dict[str, Any]): self._sem_conv_opt_in_mode, ) - if ( - server_span.is_recording() - and server_span.kind == trace.SpanKind.SERVER - and "headers" in message - ): - custom_response_attributes = ( - collect_custom_headers_attributes( - message, - self.http_capture_headers_sanitize_fields, - self.http_capture_headers_server_response, - normalise_response_header_name, - ) - if self.http_capture_headers_server_response - else {} - ) - if len(custom_response_attributes) > 0: - server_span.set_attributes( - custom_response_attributes - ) + if ( + server_span.is_recording() + and server_span.kind == trace.SpanKind.SERVER + and "headers" in message + ): + custom_response_attributes = ( + collect_custom_headers_attributes( + message, + self.http_capture_headers_sanitize_fields, + self.http_capture_headers_server_response, + normalise_response_header_name, + ) + if self.http_capture_headers_server_response + else {} + ) + if len(custom_response_attributes) > 0: + server_span.set_attributes( + custom_response_attributes + ) if status_code: set_status_code( From f312d4aa5d281d8e6d6bb8b6a82dcd8692636011 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Wed, 21 Aug 2024 09:08:42 +0300 Subject: [PATCH 14/20] refactor: combine exclusions into one optional list --- .../instrumentation/asgi/__init__.py | 12 +++++------ .../instrumentation/fastapi/__init__.py | 21 +++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) 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 bbf90499b7..a47b739a6c 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -527,8 +527,7 @@ class OpenTelemetryMiddleware: the current globally configured one is used. meter_provider: The optional meter provider to use. If omitted the current globally configured one is used. - exclude_receive_span: Optional flag to exclude the http receive span from the trace. - exclude_send_span: Optional flag to exclude the http send span from the trace. + exclude_spans: Optionally exclude http `send` and/or `receive` span from the trace. """ # pylint: disable=too-many-branches @@ -547,8 +546,7 @@ def __init__( 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, - exclude_receive_span: bool = False, - exclude_send_span: bool = False, + exclude_spans: list[typing.Literal["receive", "send"]] | None = None, ): # initialize semantic conventions opt-in if needed _OpenTelemetrySemanticConventionStability._initialize() @@ -655,8 +653,10 @@ def __init__( ) or [] ) - self.exclude_receive_span = exclude_receive_span - self.exclude_send_span = exclude_send_span + self.exclude_receive_span = ( + "receive" in exclude_spans if exclude_spans else False + ) + self.exclude_send_span = "send" in exclude_spans if exclude_spans else False # pylint: disable=too-many-statements async def __call__( 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 a4668a9f73..1319615fbd 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -179,7 +179,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from __future__ import annotations import logging -from typing import Collection +from typing import Collection, Literal import fastapi from starlette.routing import Match @@ -232,8 +232,7 @@ def instrument_app( 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, - exclude_receive_span: bool = False, - exclude_send_span: bool = False, + exclude_spans: list[Literal["receive", "send"]] | None = None, ): """Instrument an uninstrumented FastAPI application. @@ -253,8 +252,7 @@ def instrument_app( http_capture_headers_server_request: Optional list of HTTP headers to capture from the request. http_capture_headers_server_response: Optional list of HTTP headers to capture from the response. http_capture_headers_sanitize_fields: Optional list of HTTP headers to sanitize. - exclude_receive_span: Optional flag to exclude the http receive span from the trace. - exclude_send_span: Optional flag to exclude the http send span from the trace. + exclude_spans: Optionally exclude http `send` and/or `receive` span from the trace. """ if not hasattr(app, "_is_instrumented_by_opentelemetry"): app._is_instrumented_by_opentelemetry = False @@ -295,8 +293,7 @@ def instrument_app( http_capture_headers_server_request=http_capture_headers_server_request, http_capture_headers_server_response=http_capture_headers_server_response, http_capture_headers_sanitize_fields=http_capture_headers_sanitize_fields, - exclude_receive_span=exclude_receive_span, - exclude_send_span=exclude_send_span, + exclude_spans=exclude_spans, ) app._is_instrumented_by_opentelemetry = True if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: @@ -347,11 +344,8 @@ def _instrument(self, **kwargs): else parse_excluded_urls(_excluded_urls) ) _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") - _InstrumentedFastAPI._exclude_receive_span = kwargs.get( - "exclude_receive_span" - ) - _InstrumentedFastAPI._exclude_send_span = kwargs.get( - "exclude_send_span" + _InstrumentedFastAPI._exclude_spans = kwargs.get( + "exclude_spans" ) fastapi.FastAPI = _InstrumentedFastAPI @@ -403,8 +397,7 @@ def __init__(self, *args, **kwargs): http_capture_headers_server_request=_InstrumentedFastAPI._http_capture_headers_server_request, http_capture_headers_server_response=_InstrumentedFastAPI._http_capture_headers_server_response, http_capture_headers_sanitize_fields=_InstrumentedFastAPI._http_capture_headers_sanitize_fields, - exclude_receive_span=_InstrumentedFastAPI._exclude_receive_span, - exclude_send_span=_InstrumentedFastAPI._exclude_send_span, + exclude_spans=_InstrumentedFastAPI._exclude_spans, ) self._is_instrumented_by_opentelemetry = True _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) From c074c625df5027786859a648353d1afc2cec50a8 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Wed, 21 Aug 2024 09:20:30 +0300 Subject: [PATCH 15/20] docs: update wording --- .../src/opentelemetry/instrumentation/asgi/__init__.py | 2 +- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 a47b739a6c..a3fd3e1ba3 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -527,7 +527,7 @@ class OpenTelemetryMiddleware: the current globally configured one is used. meter_provider: The optional meter provider to use. If omitted the current globally configured one is used. - exclude_spans: Optionally exclude http `send` and/or `receive` span from the trace. + exclude_spans: Optionally exclude HTTP `send` and/or `receive` spans from the trace. """ # pylint: disable=too-many-branches 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 1319615fbd..311f2b827b 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -252,7 +252,7 @@ def instrument_app( http_capture_headers_server_request: Optional list of HTTP headers to capture from the request. http_capture_headers_server_response: Optional list of HTTP headers to capture from the response. http_capture_headers_sanitize_fields: Optional list of HTTP headers to sanitize. - exclude_spans: Optionally exclude http `send` and/or `receive` span from the trace. + exclude_spans: Optionally exclude HTTP `send` and/or `receive` spans from the trace. """ if not hasattr(app, "_is_instrumented_by_opentelemetry"): app._is_instrumented_by_opentelemetry = False From ddfb5e6cb54298ad029e716c034f4bfadb2d8f3f Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Wed, 21 Aug 2024 09:29:21 +0300 Subject: [PATCH 16/20] style: format code --- .../src/opentelemetry/instrumentation/asgi/__init__.py | 8 ++++---- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) 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 a3fd3e1ba3..41c97d8355 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -656,7 +656,9 @@ def __init__( self.exclude_receive_span = ( "receive" in exclude_spans if exclude_spans else False ) - self.exclude_send_span = "send" in exclude_spans if exclude_spans else False + self.exclude_send_span = ( + "send" in exclude_spans if exclude_spans else False + ) # pylint: disable=too-many-statements async def __call__( @@ -884,9 +886,7 @@ async def otel_send(message: dict[str, Any]): else {} ) if len(custom_response_attributes) > 0: - server_span.set_attributes( - custom_response_attributes - ) + server_span.set_attributes(custom_response_attributes) if status_code: set_status_code( 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 311f2b827b..7e4d0aac07 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -344,9 +344,7 @@ def _instrument(self, **kwargs): else parse_excluded_urls(_excluded_urls) ) _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") - _InstrumentedFastAPI._exclude_spans = kwargs.get( - "exclude_spans" - ) + _InstrumentedFastAPI._exclude_spans = kwargs.get("exclude_spans") fastapi.FastAPI = _InstrumentedFastAPI def _uninstrument(self, **kwargs): From 3fc263e4250423919b53665d390507d51e092413 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Wed, 21 Aug 2024 21:36:47 +0300 Subject: [PATCH 17/20] refactor: extract set_send_span and set_server_span logic to helpers --- .../instrumentation/asgi/__init__.py | 113 +++++++++++------- 1 file changed, 70 insertions(+), 43 deletions(-) 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 41c97d8355..5137c2695d 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -829,6 +829,66 @@ async def otel_receive(): return otel_receive + def _set_send_span( + self, + server_span_name, + scope, + send, + message, + status_code, + expecting_trailers, + ): + """Set send span attributes and status code.""" + with self.tracer.start_as_current_span( + " ".join((server_span_name, scope["type"], "send")) + ) as send_span: + if callable(self.client_response_hook): + self.client_response_hook(send_span, scope, message) + + if send_span.is_recording(): + if message["type"] == "http.response.start": + expecting_trailers = message.get("trailers", False) + send_span.set_attribute("asgi.event.type", message["type"]) + + if status_code: + set_status_code( + send_span, + status_code, + None, + self._sem_conv_opt_in_mode, + ) + return expecting_trailers + + def _set_server_span( + self, server_span, message, status_code, duration_attrs + ): + """Set server span attributes and status code.""" + if ( + server_span.is_recording() + and server_span.kind == trace.SpanKind.SERVER + and "headers" in message + ): + custom_response_attributes = ( + collect_custom_headers_attributes( + message, + self.http_capture_headers_sanitize_fields, + self.http_capture_headers_server_response, + normalise_response_header_name, + ) + if self.http_capture_headers_server_response + else {} + ) + if len(custom_response_attributes) > 0: + server_span.set_attributes(custom_response_attributes) + + if status_code: + set_status_code( + server_span, + status_code, + duration_attrs, + self._sem_conv_opt_in_mode, + ) + def _get_otel_send( self, server_span, @@ -850,52 +910,19 @@ async def otel_send(message: dict[str, Any]): status_code = 200 if not self.exclude_send_span: - with self.tracer.start_as_current_span( - " ".join((server_span_name, scope["type"], "send")) - ) as send_span: - if callable(self.client_response_hook): - self.client_response_hook(send_span, scope, message) - - if send_span.is_recording(): - if message["type"] == "http.response.start": - expecting_trailers = message.get("trailers", False) - send_span.set_attribute( - "asgi.event.type", message["type"] - ) - if status_code: - set_status_code( - send_span, - status_code, - None, - self._sem_conv_opt_in_mode, - ) - - if ( - server_span.is_recording() - and server_span.kind == trace.SpanKind.SERVER - and "headers" in message - ): - custom_response_attributes = ( - collect_custom_headers_attributes( - message, - self.http_capture_headers_sanitize_fields, - self.http_capture_headers_server_response, - normalise_response_header_name, - ) - if self.http_capture_headers_server_response - else {} - ) - if len(custom_response_attributes) > 0: - server_span.set_attributes(custom_response_attributes) - - if status_code: - set_status_code( - server_span, + expecting_trailers = self._set_send_span( + server_span_name, + scope, + send, + message, status_code, - duration_attrs, - self._sem_conv_opt_in_mode, + expecting_trailers, ) + self._set_server_span( + server_span, message, status_code, duration_attrs + ) + propagator = get_global_response_propagator() if propagator: propagator.inject( From 09a2ef487441a5c1980ceb752d22700f65038589 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Fri, 23 Aug 2024 11:06:15 +0300 Subject: [PATCH 18/20] test: add assertion to ensure list always has at least the server spans --- .../tests/test_asgi_middleware.py | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py index e6a2aa830f..b7054f6448 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -589,6 +589,7 @@ def test_exclude_internal_spans(self): app.exclude_send_span = exclude_send_span self.send_default_request() span_list = self.memory_exporter.get_finished_spans() + assert span_list for span in span_list: for excluded_span in excluded_spans: self.assertNotEqual(span.name, excluded_span) From be1ebbf70935f1d5b4f866014898e4c6eddf7ef7 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Fri, 23 Aug 2024 12:31:55 +0300 Subject: [PATCH 19/20] refactor: apply suggestions from code review Co-authored-by: Riccardo Magliocchetti --- .../tests/test_asgi_middleware.py | 2 +- 1 file changed, 1 insertion(+), 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 b7054f6448..ee8b7ab697 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -589,7 +589,7 @@ def test_exclude_internal_spans(self): app.exclude_send_span = exclude_send_span self.send_default_request() span_list = self.memory_exporter.get_finished_spans() - assert span_list + self.assertTrue(span_list) for span in span_list: for excluded_span in excluded_spans: self.assertNotEqual(span.name, excluded_span) From 690975650892addc74415231c77c5e6eb16badd0 Mon Sep 17 00:00:00 2001 From: Tobias Backer Dirks Date: Mon, 9 Sep 2024 13:19:30 +0300 Subject: [PATCH 20/20] test: ensure memory exporter cleared between cases --- .../tests/test_asgi_middleware.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py index baa75bb329..a9d7897ea6 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -566,22 +566,24 @@ async def test_background_execution(self): _SIMULATED_BACKGROUND_TASK_EXECUTION_TIME_S * 10**9, ) - def test_exclude_internal_spans(self): + async def test_exclude_internal_spans(self): """Test that internal spans are excluded from the emitted spans when the `exclude_receive_span` or `exclude_send_span` attributes are set. """ - app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) - self.seed_app(app) cases = [ - (True, True, ["GET / http receive", "GET / http send"]), - (False, True, ["GET / http send"]), - (True, False, ["GET / http receive"]), - (False, False, []), + (["receive", "send"], ["GET / http receive", "GET / http send"]), + (["send"], ["GET / http send"]), + (["receive"], ["GET / http receive"]), + ([], []), ] - for exclude_receive_span, exclude_send_span, excluded_spans in cases: - app.exclude_receive_span = exclude_receive_span - app.exclude_send_span = exclude_send_span - self.send_default_request() + for exclude_spans, excluded_spans in cases: + self.memory_exporter.clear() + app = otel_asgi.OpenTelemetryMiddleware( + simple_asgi, exclude_spans=exclude_spans + ) + self.seed_app(app) + await self.send_default_request() + await self.get_all_output() span_list = self.memory_exporter.get_finished_spans() self.assertTrue(span_list) for span in span_list: