prepare semconv utilities to support database stability opt-in (#3111)

This commit is contained in:
Emídio Neto
2024-12-17 14:58:04 -03:00
committed by GitHub
parent d155540038
commit cc62d1f05e
17 changed files with 423 additions and 134 deletions

View File

@ -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

View File

@ -92,13 +92,13 @@ from opentelemetry import context as context_api
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

View File

@ -29,8 +29,8 @@ from opentelemetry import trace as trace_api
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 @@ class TestAioHttpIntegration(TestBase):
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 @@ class TestAioHttpIntegration(TestBase):
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 @@ class TestAioHttpIntegration(TestBase):
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 @@ class TestAioHttpIntegration(TestBase):
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 @@ class TestAioHttpIntegration(TestBase):
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 @@ class TestAioHttpIntegration(TestBase):
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 @@ class TestAioHttpIntegration(TestBase):
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")

View File

@ -205,7 +205,6 @@ from opentelemetry.instrumentation._semconv import (
_filter_semconv_active_request_count_attr,
_filter_semconv_duration_attrs,
_get_schema_url,
_HTTPStabilityMode,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_report_new,
@ -225,6 +224,7 @@ from opentelemetry.instrumentation._semconv import (
_set_http_url,
_set_http_user_agent,
_set_status,
_StabilityMode,
)
from opentelemetry.instrumentation.asgi.types import (
ClientRequestHook,
@ -324,7 +324,7 @@ asgi_setter = ASGISetter()
# 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 @@ class OpenTelemetryMiddleware:
)
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 @@ class OpenTelemetryMiddleware:
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,

View File

@ -24,12 +24,12 @@ import opentelemetry.instrumentation.asgi as otel_asgi
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 @@ class TestAsgiAttributes(unittest.TestCase):
attrs = otel_asgi.collect_request_attributes(
self.scope,
_HTTPStabilityMode.HTTP,
_StabilityMode.HTTP,
)
self.assertDictEqual(
@ -1677,7 +1677,7 @@ class TestAsgiAttributes(unittest.TestCase):
attrs = otel_asgi.collect_request_attributes(
self.scope,
_HTTPStabilityMode.HTTP_DUP,
_StabilityMode.HTTP_DUP,
)
self.assertDictEqual(
@ -1715,7 +1715,7 @@ class TestAsgiAttributes(unittest.TestCase):
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 @@ class TestAsgiAttributes(unittest.TestCase):
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 @@ class TestAsgiAttributes(unittest.TestCase):
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 @@ class TestAsgiAttributes(unittest.TestCase):
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),)

View File

@ -25,13 +25,13 @@ from opentelemetry.context import detach
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 @@ class _DjangoMiddleware(MiddlewareMixin):
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 @@ class _DjangoMiddleware(MiddlewareMixin):
)
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 @@ class _DjangoMiddleware(MiddlewareMixin):
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,

View File

@ -186,9 +186,9 @@ from starlette.routing import Match
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)

View File

@ -252,11 +252,11 @@ import opentelemetry.instrumentation.wsgi as otel_wsgi
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 _rewrapped_app(
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 _rewrapped_app(
)
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)

View File

@ -205,7 +205,6 @@ from wrapt import wrap_function_wrapper
from opentelemetry.instrumentation._semconv import (
_get_schema_url,
_HTTPStabilityMode,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_report_new,
@ -215,6 +214,7 @@ from opentelemetry.instrumentation._semconv import (
_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

View File

@ -87,7 +87,6 @@ from opentelemetry.instrumentation._semconv import (
_client_duration_attrs_old,
_filter_semconv_duration_attrs,
_get_schema_url,
_HTTPStabilityMode,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_report_new,
@ -100,6 +99,7 @@ from opentelemetry.instrumentation._semconv import (
_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 _instrument(
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 _instrument(
metric_labels,
_client_duration_attrs_old,
_client_duration_attrs_new,
_HTTPStabilityMode.HTTP,
_StabilityMode.HTTP,
)
duration_histogram_new.record(
elapsed_time, attributes=duration_attrs_new

View File

@ -90,7 +90,6 @@ from opentelemetry.instrumentation._semconv import (
_client_duration_attrs_old,
_filter_semconv_duration_attrs,
_get_schema_url,
_HTTPStabilityMode,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_report_new,
@ -99,6 +98,7 @@ from opentelemetry.instrumentation._semconv import (
_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 _instrument(
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)

View File

@ -98,7 +98,6 @@ from opentelemetry.instrumentation._semconv import (
_client_duration_attrs_old,
_filter_semconv_duration_attrs,
_get_schema_url,
_HTTPStabilityMode,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_report_new,
@ -111,6 +110,7 @@ from opentelemetry.instrumentation._semconv import (
_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

View File

@ -24,8 +24,8 @@ import urllib3.exceptions
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 @@ class TestURLLib3Instrumentor(TestBase):
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 @@ class TestURLLib3Instrumentor(TestBase):
}
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 @@ class TestURLLib3Instrumentor(TestBase):
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 @@ class TestURLLib3Instrumentor(TestBase):
}
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 @@ class TestURLLib3Instrumentor(TestBase):
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 @@ class TestURLLib3Instrumentor(TestBase):
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 @@ class TestURLLib3Instrumentor(TestBase):
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 @@ class TestURLLib3Instrumentor(TestBase):
)
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 @@ class TestURLLib3Instrumentor(TestBase):
)
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(

View File

@ -217,7 +217,6 @@ from opentelemetry.instrumentation._semconv import (
_filter_semconv_active_request_count_attr,
_filter_semconv_duration_attrs,
_get_schema_url,
_HTTPStabilityMode,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_report_new,
@ -237,6 +236,7 @@ from opentelemetry.instrumentation._semconv import (
_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 @@ class OpenTelemetryMiddleware:
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

View File

@ -24,12 +24,12 @@ import opentelemetry.instrumentation.wsgi as otel_wsgi
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 @@ class TestWsgiAttributes(unittest.TestCase):
attrs = otel_wsgi.collect_request_attributes(
self.environ,
_HTTPStabilityMode.HTTP,
_StabilityMode.HTTP,
)
self.assertDictEqual(
attrs,
@ -742,7 +742,7 @@ class TestWsgiAttributes(unittest.TestCase):
self.assertGreaterEqual(
otel_wsgi.collect_request_attributes(
self.environ,
_HTTPStabilityMode.HTTP,
_StabilityMode.HTTP,
).items(),
expected_new.items(),
)
@ -758,7 +758,7 @@ class TestWsgiAttributes(unittest.TestCase):
self.assertGreaterEqual(
otel_wsgi.collect_request_attributes(
self.environ,
_HTTPStabilityMode.HTTP,
_StabilityMode.HTTP,
).items(),
expected_new.items(),
)
@ -769,7 +769,7 @@ class TestWsgiAttributes(unittest.TestCase):
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 = (

View File

@ -109,23 +109,23 @@ OTEL_SEMCONV_STABILITY_OPT_IN = "OTEL_SEMCONV_STABILITY_OPT_IN"
class _OpenTelemetryStabilitySignalType:
HTTP = "http"
DATABASE = "database"
class _HTTPStabilityMode(Enum):
# http - emit the new, stable HTTP and networking conventions ONLY
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
class _StabilityMode(Enum):
DEFAULT = "default"
HTTP = "http"
HTTP_DUP = "http/dup"
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

View File

@ -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)