From cc62d1f05e8b3acfaa97b86c4d483c29c0044587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Em=C3=ADdio=20Neto?= <9735060+emdneto@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:58:04 -0300 Subject: [PATCH] prepare semconv utilities to support database stability opt-in (#3111) --- CHANGELOG.md | 6 + .../aiohttp_client/__init__.py | 8 +- .../tests/test_aiohttp_client_integration.py | 16 +- .../instrumentation/asgi/__init__.py | 16 +- .../tests/test_asgi_middleware.py | 14 +- .../django/middleware/otel_middleware.py | 12 +- .../instrumentation/fastapi/__init__.py | 4 +- .../instrumentation/flask/__init__.py | 12 +- .../instrumentation/httpx/__init__.py | 6 +- .../instrumentation/requests/__init__.py | 8 +- .../instrumentation/urllib/__init__.py | 14 +- .../instrumentation/urllib3/__init__.py | 16 +- .../tests/test_urllib3_integration.py | 28 +- .../instrumentation/wsgi/__init__.py | 14 +- .../tests/test_wsgi_middleware.py | 10 +- .../opentelemetry/instrumentation/_semconv.py | 113 +++++--- .../tests/test_semconv.py | 258 ++++++++++++++++++ 17 files changed, 422 insertions(+), 133 deletions(-) create mode 100644 opentelemetry-instrumentation/tests/test_semconv.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 106d9408c1..5c4c4452bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Add support to database stability opt-in in `_semconv` utilities and add tests + ([#3111](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3111)) + ### Fixed - `opentelemetry-instrumentation-httpx` Fix `RequestInfo`/`ResponseInfo` type hints ([#3105](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3105)) + ## Version 1.29.0/0.50b0 (2024-12-11) ### Added diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py index de60fa6379..c43522efe1 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py @@ -92,13 +92,13 @@ def response_hook(span: Span, params: typing.Union[ from opentelemetry import trace from opentelemetry.instrumentation._semconv import ( _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, _report_new, _set_http_method, _set_http_url, _set_status, + _StabilityMode, ) from opentelemetry.instrumentation.aiohttp_client.package import _instruments from opentelemetry.instrumentation.aiohttp_client.version import __version__ @@ -142,7 +142,7 @@ def _set_http_status_code_attribute( span, status_code, metric_attributes=None, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ): status_code_str = str(status_code) try: @@ -169,7 +169,7 @@ def create_trace_config( request_hook: _RequestHookT = None, response_hook: _ResponseHookT = None, tracer_provider: TracerProvider = None, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ) -> aiohttp.TraceConfig: """Create an aiohttp-compatible trace configuration. @@ -326,7 +326,7 @@ def _instrument( trace_configs: typing.Optional[ typing.Sequence[aiohttp.TraceConfig] ] = None, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): """Enables tracing of all ClientSessions diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py index 33b08fc0b6..62c837f88d 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py @@ -29,8 +29,8 @@ from opentelemetry.instrumentation import aiohttp_client from opentelemetry.instrumentation._semconv import ( OTEL_SEMCONV_STABILITY_OPT_IN, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, + _StabilityMode, ) from opentelemetry.instrumentation.aiohttp_client import ( AioHttpClientInstrumentor, @@ -150,7 +150,7 @@ def test_status_codes_new_semconv(self): path = "test-path?query=param#foobar" host, port = self._http_request( trace_config=aiohttp_client.create_trace_config( - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP + sem_conv_opt_in_mode=_StabilityMode.HTTP ), url=f"/{path}", status_code=status_code, @@ -173,7 +173,7 @@ def test_status_codes_both_semconv(self): path = "test-path?query=param#foobar" host, port = self._http_request( trace_config=aiohttp_client.create_trace_config( - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP_DUP + sem_conv_opt_in_mode=_StabilityMode.HTTP_DUP ), url=f"/{path}", status_code=status_code, @@ -213,7 +213,7 @@ def test_schema_url_new_semconv(self): with self.subTest(status_code=200): self._http_request( trace_config=aiohttp_client.create_trace_config( - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP + sem_conv_opt_in_mode=_StabilityMode.HTTP ), url="/test-path?query=param#foobar", status_code=200, @@ -230,7 +230,7 @@ def test_schema_url_both_semconv(self): with self.subTest(status_code=200): self._http_request( trace_config=aiohttp_client.create_trace_config( - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP_DUP + sem_conv_opt_in_mode=_StabilityMode.HTTP_DUP ), url="/test-path?query=param#foobar", status_code=200, @@ -398,7 +398,7 @@ async def request_handler(request): host, port = self._http_request( trace_config=aiohttp_client.create_trace_config( - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP + sem_conv_opt_in_mode=_StabilityMode.HTTP ), url="/test", request_handler=request_handler, @@ -426,7 +426,7 @@ async def request_handler(request): host, port = self._http_request( trace_config=aiohttp_client.create_trace_config( - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP_DUP + sem_conv_opt_in_mode=_StabilityMode.HTTP_DUP ), url="/test", request_handler=request_handler, @@ -546,7 +546,7 @@ async def do_request(url): def test_nonstandard_http_method_new_semconv(self): trace_configs = [ aiohttp_client.create_trace_config( - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP + sem_conv_opt_in_mode=_StabilityMode.HTTP ) ] app = HttpServerMock("nonstandard_method") 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 725532bc15..b060095160 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -205,7 +205,6 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A _filter_semconv_active_request_count_attr, _filter_semconv_duration_attrs, _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, _report_new, @@ -225,6 +224,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A _set_http_url, _set_http_user_agent, _set_status, + _StabilityMode, ) from opentelemetry.instrumentation.asgi.types import ( ClientRequestHook, @@ -324,7 +324,7 @@ def set(self, carrier: dict, key: str, value: str) -> None: # pylint: disable=n # pylint: disable=too-many-branches def collect_request_attributes( - scope, sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT + scope, sem_conv_opt_in_mode=_StabilityMode.DEFAULT ): """Collects HTTP request attributes from the ASGI scope and returns a dictionary to be used as span creation attributes.""" @@ -356,7 +356,7 @@ def collect_request_attributes( _set_http_url( result, remove_url_credentials(http_url), - _HTTPStabilityMode.DEFAULT, + _StabilityMode.DEFAULT, ) http_method = scope.get("method", "") if http_method: @@ -439,7 +439,7 @@ def set_status_code( span, status_code, metric_attributes=None, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ): """Adds HTTP response attributes to span using the status_code argument.""" status_code_str = str(status_code) @@ -755,12 +755,12 @@ async def __call__( ) duration_s = default_timer() - start duration_attrs_old = _parse_duration_attrs( - attributes, _HTTPStabilityMode.DEFAULT + attributes, _StabilityMode.DEFAULT ) if target: duration_attrs_old[SpanAttributes.HTTP_TARGET] = target duration_attrs_new = _parse_duration_attrs( - attributes, _HTTPStabilityMode.HTTP + attributes, _StabilityMode.HTTP ) if self.duration_histogram_old: self.duration_histogram_old.record( @@ -960,7 +960,7 @@ async def otel_send(message: dict[str, Any]): def _parse_duration_attrs( - req_attrs, sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT + req_attrs, sem_conv_opt_in_mode=_StabilityMode.DEFAULT ): return _filter_semconv_duration_attrs( req_attrs, @@ -971,7 +971,7 @@ def _parse_duration_attrs( def _parse_active_request_count_attrs( - req_attrs, sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT + req_attrs, sem_conv_opt_in_mode=_StabilityMode.DEFAULT ): return _filter_semconv_active_request_count_attr( req_attrs, diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py index a9d7897ea6..6fcccf84ec 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -24,12 +24,12 @@ from opentelemetry import trace as trace_api from opentelemetry.instrumentation._semconv import ( OTEL_SEMCONV_STABILITY_OPT_IN, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _server_active_requests_count_attrs_new, _server_active_requests_count_attrs_old, _server_duration_attrs_new, _server_duration_attrs_old, + _StabilityMode, ) from opentelemetry.instrumentation.propagators import ( TraceResponsePropagator, @@ -1652,7 +1652,7 @@ def test_request_attributes_new_semconv(self): attrs = otel_asgi.collect_request_attributes( self.scope, - _HTTPStabilityMode.HTTP, + _StabilityMode.HTTP, ) self.assertDictEqual( @@ -1677,7 +1677,7 @@ def test_request_attributes_both_semconv(self): attrs = otel_asgi.collect_request_attributes( self.scope, - _HTTPStabilityMode.HTTP_DUP, + _StabilityMode.HTTP_DUP, ) self.assertDictEqual( @@ -1715,7 +1715,7 @@ def test_query_string_new_semconv(self): self.scope["query_string"] = b"foo=bar" attrs = otel_asgi.collect_request_attributes( self.scope, - _HTTPStabilityMode.HTTP, + _StabilityMode.HTTP, ) self.assertEqual(attrs[URL_SCHEME], "http") self.assertEqual(attrs[CLIENT_ADDRESS], "127.0.0.1") @@ -1726,7 +1726,7 @@ def test_query_string_both_semconv(self): self.scope["query_string"] = b"foo=bar" attrs = otel_asgi.collect_request_attributes( self.scope, - _HTTPStabilityMode.HTTP_DUP, + _StabilityMode.HTTP_DUP, ) self.assertEqual( attrs[SpanAttributes.HTTP_URL], "http://127.0.0.1/?foo=bar" @@ -1762,7 +1762,7 @@ def test_response_attributes_new_semconv(self): self.span, 404, None, - _HTTPStabilityMode.HTTP, + _StabilityMode.HTTP, ) expected = (mock.call(HTTP_RESPONSE_STATUS_CODE, 404),) self.assertEqual(self.span.set_attribute.call_count, 1) @@ -1774,7 +1774,7 @@ def test_response_attributes_both_semconv(self): self.span, 404, None, - _HTTPStabilityMode.HTTP_DUP, + _StabilityMode.HTTP_DUP, ) expected = (mock.call(SpanAttributes.HTTP_STATUS_CODE, 404),) expected2 = (mock.call(HTTP_RESPONSE_STATUS_CODE, 404),) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py index da807cc310..f607046959 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py @@ -25,13 +25,13 @@ from opentelemetry.instrumentation._semconv import ( _filter_semconv_active_request_count_attr, _filter_semconv_duration_attrs, - _HTTPStabilityMode, _report_new, _report_old, _server_active_requests_count_attrs_new, _server_active_requests_count_attrs_old, _server_duration_attrs_new, _server_duration_attrs_old, + _StabilityMode, ) from opentelemetry.instrumentation.propagators import ( get_global_response_propagator, @@ -158,7 +158,7 @@ class _DjangoMiddleware(MiddlewareMixin): _duration_histogram_old = None _duration_histogram_new = None _active_request_counter = None - _sem_conv_opt_in_mode = _HTTPStabilityMode.DEFAULT + _sem_conv_opt_in_mode = _StabilityMode.DEFAULT _otel_request_hook: Callable[[Span, HttpRequest], None] = None _otel_response_hook: Callable[[Span, HttpRequest, HttpResponse], None] = ( @@ -430,7 +430,7 @@ def process_response(self, request, response): duration_s = default_timer() - request_start_time if self._duration_histogram_old: duration_attrs_old = _parse_duration_attrs( - duration_attrs, _HTTPStabilityMode.DEFAULT + duration_attrs, _StabilityMode.DEFAULT ) # http.target to be included in old semantic conventions target = duration_attrs.get(SpanAttributes.HTTP_TARGET) @@ -441,7 +441,7 @@ def process_response(self, request, response): ) if self._duration_histogram_new: duration_attrs_new = _parse_duration_attrs( - duration_attrs, _HTTPStabilityMode.HTTP + duration_attrs, _StabilityMode.HTTP ) self._duration_histogram_new.record( max(duration_s, 0), duration_attrs_new @@ -455,7 +455,7 @@ def process_response(self, request, response): def _parse_duration_attrs( - req_attrs, sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT + req_attrs, sem_conv_opt_in_mode=_StabilityMode.DEFAULT ): return _filter_semconv_duration_attrs( req_attrs, @@ -466,7 +466,7 @@ def _parse_duration_attrs( def _parse_active_request_count_attrs( - req_attrs, sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT + req_attrs, sem_conv_opt_in_mode=_StabilityMode.DEFAULT ): return _filter_semconv_active_request_count_attr( req_attrs, 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 7e4d0aac07..a19480b234 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -186,9 +186,9 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from opentelemetry.instrumentation._semconv import ( _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, + _StabilityMode, ) from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware from opentelemetry.instrumentation.asgi.types import ( @@ -362,7 +362,7 @@ class _InstrumentedFastAPI(fastapi.FastAPI): _client_request_hook: ClientRequestHook = None _client_response_hook: ClientResponseHook = None _instrumented_fastapi_apps = set() - _sem_conv_opt_in_mode = _HTTPStabilityMode.DEFAULT + _sem_conv_opt_in_mode = _StabilityMode.DEFAULT def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) 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 f80c0de808..9691f884ab 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py @@ -252,11 +252,11 @@ def response_hook(span: Span, status: str, response_headers: List): from opentelemetry import context, trace from opentelemetry.instrumentation._semconv import ( _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, _report_new, _report_old, + _StabilityMode, ) from opentelemetry.instrumentation.flask.package import _instruments from opentelemetry.instrumentation.flask.version import __version__ @@ -321,7 +321,7 @@ def _rewrapped_app( duration_histogram_old=None, response_hook=None, excluded_urls=None, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, duration_histogram_new=None, ): def _wrapped_app(wrapped_app_environ, start_response): @@ -392,7 +392,7 @@ def _start_response(status, response_headers, *args, **kwargs): duration_s = default_timer() - start if duration_histogram_old: duration_attrs_old = otel_wsgi._parse_duration_attrs( - attributes, _HTTPStabilityMode.DEFAULT + attributes, _StabilityMode.DEFAULT ) if request_route: @@ -406,7 +406,7 @@ def _start_response(status, response_headers, *args, **kwargs): ) if duration_histogram_new: duration_attrs_new = otel_wsgi._parse_duration_attrs( - attributes, _HTTPStabilityMode.HTTP + attributes, _StabilityMode.HTTP ) if request_route: @@ -427,7 +427,7 @@ def _wrapped_before_request( excluded_urls=None, enable_commenter=True, commenter_options=None, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ): def _before_request(): if excluded_urls and excluded_urls.url_disabled(flask.request.url): @@ -548,7 +548,7 @@ class _InstrumentedFlask(flask.Flask): _enable_commenter = True _commenter_options = None _meter_provider = None - _sem_conv_opt_in_mode = _HTTPStabilityMode.DEFAULT + _sem_conv_opt_in_mode = _StabilityMode.DEFAULT def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py index d4e7571f61..27bb3d639d 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py @@ -205,7 +205,6 @@ async def async_response_hook(span, request, response): from opentelemetry.instrumentation._semconv import ( _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, _report_new, @@ -215,6 +214,7 @@ async def async_response_hook(span, request, response): _set_http_peer_port_client, _set_http_status_code, _set_http_url, + _StabilityMode, ) from opentelemetry.instrumentation.httpx.package import _instruments from opentelemetry.instrumentation.httpx.version import __version__ @@ -334,7 +334,7 @@ def _apply_request_client_attributes_to_span( span_attributes: dict, url: typing.Union[str, URL, httpx.URL], method_original: str, - semconv: _HTTPStabilityMode, + semconv: _StabilityMode, ): url = httpx.URL(url) # http semconv transition: http.method -> http.request.method @@ -363,7 +363,7 @@ def _apply_response_client_attributes_to_span( span: Span, status_code: int, http_version: str, - semconv: _HTTPStabilityMode, + semconv: _StabilityMode, ): # http semconv transition: http.status_code -> http.response.status_code # TODO: use _set_status when it's stable for http clients diff --git a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py index db67d378d9..d1afa834d6 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py @@ -87,7 +87,6 @@ def response_hook(span, request_obj, response) _client_duration_attrs_old, _filter_semconv_duration_attrs, _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, _report_new, @@ -100,6 +99,7 @@ def response_hook(span, request_obj, response) _set_http_scheme, _set_http_status_code, _set_http_url, + _StabilityMode, ) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.requests.package import _instruments @@ -147,7 +147,7 @@ def _instrument( request_hook: _RequestHookT = None, response_hook: _ResponseHookT = None, excluded_urls: ExcludeList = None, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): """Enables tracing of all requests calls that go through :code:`requests.session.Session.request` (this includes @@ -312,7 +312,7 @@ def get_or_create_headers(): metric_labels, _client_duration_attrs_old, _client_duration_attrs_new, - _HTTPStabilityMode.DEFAULT, + _StabilityMode.DEFAULT, ) duration_histogram_old.record( max(round(elapsed_time * 1000), 0), @@ -323,7 +323,7 @@ def get_or_create_headers(): metric_labels, _client_duration_attrs_old, _client_duration_attrs_new, - _HTTPStabilityMode.HTTP, + _StabilityMode.HTTP, ) duration_histogram_new.record( elapsed_time, attributes=duration_attrs_new diff --git a/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py b/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py index 8b72a2f3db..9fe9996ba4 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py @@ -90,7 +90,6 @@ def response_hook(span, request_obj, response) _client_duration_attrs_old, _filter_semconv_duration_attrs, _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, _report_new, @@ -99,6 +98,7 @@ def response_hook(span, request_obj, response) _set_http_network_protocol_version, _set_http_url, _set_status, + _StabilityMode, ) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.urllib.package import _instruments @@ -209,7 +209,7 @@ def _instrument( request_hook: _RequestHookT = None, response_hook: _ResponseHookT = None, excluded_urls: ExcludeList = None, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): """Enables tracing of all requests calls that go through :code:`urllib.Client._make_request`""" @@ -305,13 +305,13 @@ def _instrumented_open_call( labels, _client_duration_attrs_old, _client_duration_attrs_new, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ) duration_attrs_new = _filter_semconv_duration_attrs( labels, _client_duration_attrs_old, _client_duration_attrs_new, - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP, + sem_conv_opt_in_mode=_StabilityMode.HTTP, ) duration_attrs_old[SpanAttributes.HTTP_URL] = url @@ -372,7 +372,7 @@ def _set_status_code_attribute( span: Span, status_code: int, metric_attributes: dict = None, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ) -> None: status_code_str = str(status_code) try: @@ -394,7 +394,7 @@ def _set_status_code_attribute( def _create_client_histograms( - meter, sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT + meter, sem_conv_opt_in_mode=_StabilityMode.DEFAULT ) -> Dict[str, Histogram]: histograms = {} if _report_old(sem_conv_opt_in_mode): @@ -442,7 +442,7 @@ def _record_histograms( request_size: int, response_size: int, duration_s: float, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): if _report_old(sem_conv_opt_in_mode): duration = max(round(duration_s * 1000), 0) diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py index eda66bea37..2d1cf4c1b0 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py @@ -98,7 +98,6 @@ def response_hook( _client_duration_attrs_old, _filter_semconv_duration_attrs, _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, _report_new, @@ -111,6 +110,7 @@ def response_hook( _set_http_scheme, _set_http_url, _set_status, + _StabilityMode, ) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.urllib3.package import _instruments @@ -309,7 +309,7 @@ def _instrument( response_hook: _ResponseHookT = None, url_filter: _UrlFilterT = None, excluded_urls: ExcludeList = None, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): def instrumented_urlopen(wrapped, instance, args, kwargs): if not is_http_instrumentation_enabled(): @@ -461,7 +461,7 @@ def _set_status_code_attribute( span: Span, status_code: int, metric_attributes: dict = None, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ) -> None: status_code_str = str(status_code) try: @@ -487,7 +487,7 @@ def _set_metric_attributes( instance: urllib3.connectionpool.HTTPConnectionPool, response: urllib3.response.HTTPResponse, method: str, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ) -> None: _set_http_host_client( metric_attributes, instance.host, sem_conv_opt_in_mode @@ -516,7 +516,7 @@ def _set_metric_attributes( def _filter_attributes_semconv( metric_attributes, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): duration_attrs_old = None duration_attrs_new = None @@ -525,14 +525,14 @@ def _filter_attributes_semconv( metric_attributes, _client_duration_attrs_old, _client_duration_attrs_new, - _HTTPStabilityMode.DEFAULT, + _StabilityMode.DEFAULT, ) if _report_new(sem_conv_opt_in_mode): duration_attrs_new = _filter_semconv_duration_attrs( metric_attributes, _client_duration_attrs_old, _client_duration_attrs_new, - _HTTPStabilityMode.HTTP, + _StabilityMode.HTTP, ) return (duration_attrs_old, duration_attrs_new) @@ -549,7 +549,7 @@ def _record_metrics( duration_s: float, request_size: typing.Optional[int], response_size: int, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): attrs_old, attrs_new = _filter_attributes_semconv( metric_attributes, sem_conv_opt_in_mode diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py index 69bed0eaee..e5a9f3b7e1 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py @@ -24,8 +24,8 @@ from opentelemetry import trace from opentelemetry.instrumentation._semconv import ( OTEL_SEMCONV_STABILITY_OPT_IN, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, + _StabilityMode, ) from opentelemetry.instrumentation.urllib3 import ( RequestInfo, @@ -106,7 +106,7 @@ def assert_success_span( self, response: urllib3.response.HTTPResponse, url: str, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): self.assertEqual(b"Hello!", response.data) @@ -129,9 +129,9 @@ def assert_success_span( } attributes = { - _HTTPStabilityMode.DEFAULT: expected_attr_old, - _HTTPStabilityMode.HTTP: expected_attr_new, - _HTTPStabilityMode.HTTP_DUP: { + _StabilityMode.DEFAULT: expected_attr_old, + _StabilityMode.HTTP: expected_attr_new, + _StabilityMode.HTTP_DUP: { **expected_attr_new, **expected_attr_old, }, @@ -143,7 +143,7 @@ def assert_success_span( def assert_exception_span( self, url: str, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): span = self.assert_span() @@ -159,9 +159,9 @@ def assert_exception_span( } attributes = { - _HTTPStabilityMode.DEFAULT: expected_attr_old, - _HTTPStabilityMode.HTTP: expected_attr_new, - _HTTPStabilityMode.HTTP_DUP: { + _StabilityMode.DEFAULT: expected_attr_old, + _StabilityMode.HTTP: expected_attr_new, + _StabilityMode.HTTP_DUP: { **expected_attr_new, **expected_attr_old, }, @@ -192,7 +192,7 @@ def test_basic_http_success(self): self.assert_success_span( response, self.HTTP_URL, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ) def test_basic_http_success_new_semconv(self): @@ -200,7 +200,7 @@ def test_basic_http_success_new_semconv(self): self.assert_success_span( response, self.HTTP_URL, - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP, + sem_conv_opt_in_mode=_StabilityMode.HTTP, ) def test_basic_http_success_both_semconv(self): @@ -208,7 +208,7 @@ def test_basic_http_success_both_semconv(self): self.assert_success_span( response, self.HTTP_URL, - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP_DUP, + sem_conv_opt_in_mode=_StabilityMode.HTTP_DUP, ) def test_basic_http_success_using_connection_pool(self): @@ -471,7 +471,7 @@ def test_request_exception_new_semconv(self, _): ) self.assert_exception_span( - self.HTTP_URL, sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP + self.HTTP_URL, sem_conv_opt_in_mode=_StabilityMode.HTTP ) @mock.patch( @@ -485,7 +485,7 @@ def test_request_exception_both_semconv(self, _): ) self.assert_exception_span( - self.HTTP_URL, sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP_DUP + self.HTTP_URL, sem_conv_opt_in_mode=_StabilityMode.HTTP_DUP ) @mock.patch( 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 c0384d594b..eb7cbced9c 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py @@ -217,7 +217,6 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he _filter_semconv_active_request_count_attr, _filter_semconv_duration_attrs, _get_schema_url, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, _report_new, @@ -237,6 +236,7 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he _set_http_target, _set_http_user_agent, _set_status, + _StabilityMode, ) from opentelemetry.instrumentation.utils import _start_internal_or_server_span from opentelemetry.instrumentation.wsgi.version import __version__ @@ -308,7 +308,7 @@ def setifnotnone(dic, key, value): def collect_request_attributes( environ, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ): """Collects HTTP request attributes from the PEP3333-conforming WSGI environ and returns a dictionary to be used as span creation attributes. @@ -449,7 +449,7 @@ def _parse_status_code(resp_status): def _parse_active_request_count_attrs( - req_attrs, sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT + req_attrs, sem_conv_opt_in_mode=_StabilityMode.DEFAULT ): return _filter_semconv_active_request_count_attr( req_attrs, @@ -460,7 +460,7 @@ def _parse_active_request_count_attrs( def _parse_duration_attrs( - req_attrs, sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT + req_attrs, sem_conv_opt_in_mode=_StabilityMode.DEFAULT ): return _filter_semconv_duration_attrs( req_attrs, @@ -475,7 +475,7 @@ def add_response_attributes( start_response_status, response_headers, duration_attrs=None, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ): # pylint: disable=unused-argument """Adds HTTP response attributes to span using the arguments passed to a PEP3333-conforming start_response callable. @@ -685,14 +685,14 @@ def __call__(self, environ, start_response): duration_s = default_timer() - start if self.duration_histogram_old: duration_attrs_old = _parse_duration_attrs( - req_attrs, _HTTPStabilityMode.DEFAULT + req_attrs, _StabilityMode.DEFAULT ) self.duration_histogram_old.record( max(round(duration_s * 1000), 0), duration_attrs_old ) if self.duration_histogram_new: duration_attrs_new = _parse_duration_attrs( - req_attrs, _HTTPStabilityMode.HTTP + req_attrs, _StabilityMode.HTTP ) self.duration_histogram_new.record( max(duration_s, 0), duration_attrs_new diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py index 095e263732..da1a3c2696 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py @@ -24,12 +24,12 @@ from opentelemetry import trace as trace_api from opentelemetry.instrumentation._semconv import ( OTEL_SEMCONV_STABILITY_OPT_IN, - _HTTPStabilityMode, _OpenTelemetrySemanticConventionStability, _server_active_requests_count_attrs_new, _server_active_requests_count_attrs_old, _server_duration_attrs_new, _server_duration_attrs_old, + _StabilityMode, ) from opentelemetry.sdk.metrics.export import ( HistogramDataPoint, @@ -527,7 +527,7 @@ def test_request_attributes_new_semconv(self): attrs = otel_wsgi.collect_request_attributes( self.environ, - _HTTPStabilityMode.HTTP, + _StabilityMode.HTTP, ) self.assertDictEqual( attrs, @@ -742,7 +742,7 @@ def test_request_attributes_with_full_request_uri(self): self.assertGreaterEqual( otel_wsgi.collect_request_attributes( self.environ, - _HTTPStabilityMode.HTTP, + _StabilityMode.HTTP, ).items(), expected_new.items(), ) @@ -758,7 +758,7 @@ def test_http_user_agent_attribute(self): self.assertGreaterEqual( otel_wsgi.collect_request_attributes( self.environ, - _HTTPStabilityMode.HTTP, + _StabilityMode.HTTP, ).items(), expected_new.items(), ) @@ -769,7 +769,7 @@ def test_response_attributes(self): self.span, "404 Not Found", {}, - sem_conv_opt_in_mode=_HTTPStabilityMode.HTTP, + sem_conv_opt_in_mode=_StabilityMode.HTTP, ) expected = (mock.call(SpanAttributes.HTTP_STATUS_CODE, 404),) expected_new = ( diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py index c4e720fd04..091c876535 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py @@ -109,23 +109,23 @@ class _OpenTelemetryStabilitySignalType: HTTP = "http" + DATABASE = "database" -class _HTTPStabilityMode(Enum): - # http - emit the new, stable HTTP and networking conventions ONLY +class _StabilityMode(Enum): + DEFAULT = "default" HTTP = "http" - # http/dup - emit both the old and the stable HTTP and networking conventions HTTP_DUP = "http/dup" - # default - continue emitting old experimental HTTP and networking conventions - DEFAULT = "default" + DATABASE = "database" + DATABASE_DUP = "database/dup" -def _report_new(mode): - return mode.name != _HTTPStabilityMode.DEFAULT.name +def _report_new(mode: _StabilityMode): + return mode != _StabilityMode.DEFAULT -def _report_old(mode): - return mode.name != _HTTPStabilityMode.HTTP.name +def _report_old(mode: _StabilityMode): + return mode not in (_StabilityMode.HTTP, _StabilityMode.DATABASE) class _OpenTelemetrySemanticConventionStability: @@ -135,35 +135,61 @@ class _OpenTelemetrySemanticConventionStability: @classmethod def _initialize(cls): - with _OpenTelemetrySemanticConventionStability._lock: - if not _OpenTelemetrySemanticConventionStability._initialized: - # Users can pass in comma delimited string for opt-in options - # Only values for http stability are supported for now - opt_in = os.environ.get(OTEL_SEMCONV_STABILITY_OPT_IN, "") - opt_in_list = [] - if opt_in: - opt_in_list = [s.strip() for s in opt_in.split(",")] - http_opt_in = _HTTPStabilityMode.DEFAULT - if opt_in_list: - # Process http opt-in - # http/dup takes priority over http - if _HTTPStabilityMode.HTTP_DUP.value in opt_in_list: - http_opt_in = _HTTPStabilityMode.HTTP_DUP - elif _HTTPStabilityMode.HTTP.value in opt_in_list: - http_opt_in = _HTTPStabilityMode.HTTP - _OpenTelemetrySemanticConventionStability._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[ - _OpenTelemetryStabilitySignalType.HTTP - ] = http_opt_in - _OpenTelemetrySemanticConventionStability._initialized = True + with cls._lock: + if cls._initialized: + return + + # Users can pass in comma delimited string for opt-in options + # Only values for http and database stability are supported for now + opt_in = os.environ.get(OTEL_SEMCONV_STABILITY_OPT_IN) + + if not opt_in: + # early return in case of default + cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING = { + _OpenTelemetryStabilitySignalType.HTTP: _StabilityMode.DEFAULT, + _OpenTelemetryStabilitySignalType.DATABASE: _StabilityMode.DEFAULT, + } + cls._initialized = True + return + + opt_in_list = [s.strip() for s in opt_in.split(",")] + + cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[ + _OpenTelemetryStabilitySignalType.HTTP + ] = cls._filter_mode( + opt_in_list, _StabilityMode.HTTP, _StabilityMode.HTTP_DUP + ) + + cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[ + _OpenTelemetryStabilitySignalType.DATABASE + ] = cls._filter_mode( + opt_in_list, + _StabilityMode.DATABASE, + _StabilityMode.DATABASE_DUP, + ) + + cls._initialized = True + + @staticmethod + def _filter_mode(opt_in_list, stable_mode, dup_mode): + # Process semconv stability opt-in + # http/dup,database/dup has higher precedence over http,database + if dup_mode.value in opt_in_list: + return dup_mode + + return ( + stable_mode + if stable_mode.value in opt_in_list + else _StabilityMode.DEFAULT + ) @classmethod - # Get OpenTelemetry opt-in mode based off of signal type (http, messaging, etc.) def _get_opentelemetry_stability_opt_in_mode( - cls, - signal_type: _OpenTelemetryStabilitySignalType, - ) -> _HTTPStabilityMode: - return _OpenTelemetrySemanticConventionStability._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING.get( - signal_type, _HTTPStabilityMode.DEFAULT + cls, signal_type: _OpenTelemetryStabilitySignalType + ) -> _StabilityMode: + # Get OpenTelemetry opt-in mode based off of signal type (http, messaging, etc.) + return cls._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING.get( + signal_type, _StabilityMode.DEFAULT ) @@ -171,14 +197,12 @@ def _filter_semconv_duration_attrs( attrs, old_attrs, new_attrs, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ): filtered_attrs = {} # duration is two different metrics depending on sem_conv_opt_in_mode, so no DUP attributes allowed_attributes = ( - new_attrs - if sem_conv_opt_in_mode == _HTTPStabilityMode.HTTP - else old_attrs + new_attrs if sem_conv_opt_in_mode == _StabilityMode.HTTP else old_attrs ) for key, val in attrs.items(): if key in allowed_attributes: @@ -190,7 +214,7 @@ def _filter_semconv_active_request_count_attr( attrs, old_attrs, new_attrs, - sem_conv_opt_in_mode=_HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, ): filtered_attrs = {} if _report_old(sem_conv_opt_in_mode): @@ -367,10 +391,11 @@ def _set_status( status_code: int, status_code_str: str, server_span: bool = True, - sem_conv_opt_in_mode: _HTTPStabilityMode = _HTTPStabilityMode.DEFAULT, + sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT, ): if status_code < 0: - metrics_attributes[ERROR_TYPE] = status_code_str + if _report_new(sem_conv_opt_in_mode): + metrics_attributes[ERROR_TYPE] = status_code_str if span.is_recording(): if _report_new(sem_conv_opt_in_mode): span.set_attribute(ERROR_TYPE, status_code_str) @@ -404,7 +429,7 @@ def _set_status( # Get schema version based off of opt-in mode -def _get_schema_url(mode: _HTTPStabilityMode) -> str: - if mode is _HTTPStabilityMode.DEFAULT: +def _get_schema_url(mode: _StabilityMode) -> str: + if mode is _StabilityMode.DEFAULT: return "https://opentelemetry.io/schemas/1.11.0" return SpanAttributes.SCHEMA_URL diff --git a/opentelemetry-instrumentation/tests/test_semconv.py b/opentelemetry-instrumentation/tests/test_semconv.py new file mode 100644 index 0000000000..6a56efcc37 --- /dev/null +++ b/opentelemetry-instrumentation/tests/test_semconv.py @@ -0,0 +1,258 @@ +# 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. + +import os +from unittest import TestCase +from unittest.mock import Mock, patch + +from opentelemetry.instrumentation._semconv import ( + OTEL_SEMCONV_STABILITY_OPT_IN, + _OpenTelemetrySemanticConventionStability, + _OpenTelemetryStabilitySignalType, + _set_status, + _StabilityMode, +) +from opentelemetry.trace.status import StatusCode + + +def stability_mode(mode): + def decorator(test_case): + @patch.dict(os.environ, {OTEL_SEMCONV_STABILITY_OPT_IN: mode}) + def wrapper(*args, **kwargs): + _OpenTelemetrySemanticConventionStability._initialized = False + _OpenTelemetrySemanticConventionStability._initialize() + return test_case(*args, **kwargs) + + return wrapper + + return decorator + + +class TestOpenTelemetrySemConvStability(TestCase): + @stability_mode("") + def test_default_mode(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.HTTP + ), + _StabilityMode.DEFAULT, + ) + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE + ), + _StabilityMode.DEFAULT, + ) + + @stability_mode("http") + def test_http_stable_mode(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.HTTP + ), + _StabilityMode.HTTP, + ) + + @stability_mode("http/dup") + def test_http_dup_mode(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.HTTP + ), + _StabilityMode.HTTP_DUP, + ) + + @stability_mode("database") + def test_database_stable_mode(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE + ), + _StabilityMode.DATABASE, + ) + + @stability_mode("database/dup") + def test_database_dup_mode(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE + ), + _StabilityMode.DATABASE_DUP, + ) + + @stability_mode("database,http") + def test_multiple_stability_database_http_modes(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE + ), + _StabilityMode.DATABASE, + ) + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.HTTP + ), + _StabilityMode.HTTP, + ) + + @stability_mode("database,http/dup") + def test_multiple_stability_database_http_dup_modes(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE + ), + _StabilityMode.DATABASE, + ) + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.HTTP + ), + _StabilityMode.HTTP_DUP, + ) + + @stability_mode("database/dup,http") + def test_multiple_stability_database_dup_http_stable_modes(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE + ), + _StabilityMode.DATABASE_DUP, + ) + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.HTTP + ), + _StabilityMode.HTTP, + ) + + @stability_mode("database,database/dup,http,http/dup") + def test_stability_mode_dup_precedence(self): + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE + ), + _StabilityMode.DATABASE_DUP, + ) + self.assertEqual( + _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.HTTP + ), + _StabilityMode.HTTP_DUP, + ) + + +class TestOpenTelemetrySemConvStabilityHTTP(TestCase): + def test_set_status_for_non_http_code_with_recording_span(self): + span = Mock() + span.is_recording.return_value = True + metric_attributes = {} + _set_status( + span, + metric_attributes, + -1, + "Exception", + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, + ) + + self.assertIsNone(metric_attributes.get("error.type")) + span.set_attribute.assert_not_called() + status_call = span.set_status.call_args[0][0] + self.assertEqual(status_call.status_code, StatusCode.ERROR) + self.assertEqual( + status_call.description, "Non-integer HTTP status: " + "Exception" + ) + + def test_status_code_http_default(self): + span = Mock() + metrics_attributes = {} + _set_status( + span=span, + metrics_attributes=metrics_attributes, + status_code=404, + status_code_str="404", + server_span=True, + sem_conv_opt_in_mode=_StabilityMode.DEFAULT, + ) + # Verify only old conventions are emitted + span.set_attribute.assert_called_with("http.status_code", 404) + self.assertIn("http.status_code", metrics_attributes) + self.assertNotIn("http.response_status_code", metrics_attributes) + + def test_status_code_http_stable(self): + span = Mock() + metrics_attributes = {} + _set_status( + span=span, + metrics_attributes=metrics_attributes, + status_code=200, + status_code_str="200", + server_span=True, + sem_conv_opt_in_mode=_StabilityMode.HTTP, + ) + # Verify only new conventions are emitted + span.set_attribute.assert_called_with("http.response.status_code", 200) + self.assertIn("http.response.status_code", metrics_attributes) + self.assertNotIn("http.status_code", metrics_attributes) + + def test_status_code_http_dup(self): + span = Mock() + metrics_attributes = {} + _set_status( + span=span, + metrics_attributes=metrics_attributes, + status_code=500, + status_code_str="500", + server_span=True, + sem_conv_opt_in_mode=_StabilityMode.HTTP_DUP, + ) + # Verify both old and new conventions are emitted + span.set_attribute.assert_any_call("http.status_code", 500) + span.set_attribute.assert_any_call("http.response.status_code", 500) + self.assertIn("http.status_code", metrics_attributes) + self.assertIn("http.response.status_code", metrics_attributes) + + def test_error_status_code_new_mode(self): + span = Mock() + metrics_attributes = {} + _set_status( + span=span, + metrics_attributes=metrics_attributes, + status_code=500, + status_code_str="500", + server_span=True, + sem_conv_opt_in_mode=_StabilityMode.HTTP, + ) + # Verify error type is set for new conventions + span.set_attribute.assert_any_call("error.type", "500") + self.assertIn("error.type", metrics_attributes) + self.assertEqual(metrics_attributes["error.type"], "500") + + def test_non_recording_span(self): + span = Mock() + span.is_recording.return_value = False + metrics_attributes = {} + _set_status( + span=span, + metrics_attributes=metrics_attributes, + status_code=200, + status_code_str="200", + server_span=True, + sem_conv_opt_in_mode=_StabilityMode.HTTP_DUP, + ) + # Verify no span attributes are set if not recording + span.set_attribute.assert_not_called() + span.set_status.assert_not_called() + # Verify status code set for metrics independent of tracing decision + self.assertIn("http.status_code", metrics_attributes) + self.assertIn("http.response.status_code", metrics_attributes)