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 397d5dc80e9..5c48bbd58a1 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 @@ -133,16 +133,19 @@ async def on_request_start( request_span_name = str(trace_config_ctx.span_name) trace_config_ctx.span = trace_config_ctx.tracer.start_span( - request_span_name, - kind=SpanKind.CLIENT, - attributes={ + request_span_name, kind=SpanKind.CLIENT, + ) + + if trace_config_ctx.span.is_recording(): + attributes = { "component": "http", "http.method": http_method, "http.url": trace_config_ctx.url_filter(params.url) if callable(trace_config_ctx.url_filter) else str(params.url), - }, - ) + } + for key, value in attributes.items(): + trace_config_ctx.span.set_attribute(key, value) trace_config_ctx.token = context_api.attach( trace.set_span_in_context(trace_config_ctx.span) @@ -155,15 +158,18 @@ async def on_request_end( trace_config_ctx: types.SimpleNamespace, params: aiohttp.TraceRequestEndParams, ): - trace_config_ctx.span.set_status( - Status(http_status_to_canonical_code(int(params.response.status))) - ) - trace_config_ctx.span.set_attribute( - "http.status_code", params.response.status - ) - trace_config_ctx.span.set_attribute( - "http.status_text", params.response.reason - ) + if trace_config_ctx.span.is_recording(): + trace_config_ctx.span.set_status( + Status( + http_status_to_canonical_code(int(params.response.status)) + ) + ) + trace_config_ctx.span.set_attribute( + "http.status_code", params.response.status + ) + trace_config_ctx.span.set_attribute( + "http.status_text", params.response.reason + ) _end_trace(trace_config_ctx) async def on_request_exception( @@ -171,21 +177,22 @@ async def on_request_exception( trace_config_ctx: types.SimpleNamespace, params: aiohttp.TraceRequestExceptionParams, ): - if isinstance( - params.exception, - (aiohttp.ServerTimeoutError, aiohttp.TooManyRedirects), - ): - status = StatusCanonicalCode.DEADLINE_EXCEEDED - # Assume any getaddrinfo error is a DNS failure. - elif isinstance( - params.exception, aiohttp.ClientConnectorError - ) and isinstance(params.exception.os_error, socket.gaierror): - # DNS resolution failed - status = StatusCanonicalCode.UNKNOWN - else: - status = StatusCanonicalCode.UNAVAILABLE - - trace_config_ctx.span.set_status(Status(status)) + if trace_config_ctx.span.is_recording(): + if isinstance( + params.exception, + (aiohttp.ServerTimeoutError, aiohttp.TooManyRedirects), + ): + status = StatusCanonicalCode.DEADLINE_EXCEEDED + # Assume any getaddrinfo error is a DNS failure. + elif isinstance( + params.exception, aiohttp.ClientConnectorError + ) and isinstance(params.exception.os_error, socket.gaierror): + # DNS resolution failed + status = StatusCanonicalCode.UNKNOWN + else: + status = StatusCanonicalCode.UNAVAILABLE + + trace_config_ctx.span.set_status(Status(status)) _end_trace(trace_config_ctx) def _trace_config_ctx_factory(**kwargs): 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 4a48c38ff70..90af17f9e07 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 @@ -17,6 +17,7 @@ import typing import urllib.parse from http import HTTPStatus +from unittest import mock import aiohttp import aiohttp.test_utils @@ -135,6 +136,22 @@ def test_status_codes(self): self.memory_exporter.clear() + def test_not_recording(self): + mock_tracer = mock.Mock() + mock_span = mock.Mock() + mock_span.is_recording.return_value = False + mock_tracer.start_span.return_value = mock_span + with mock.patch("opentelemetry.trace.get_tracer"): + # pylint: disable=W0612 + host, port = self._http_request( + trace_config=opentelemetry.instrumentation.aiohttp_client.create_trace_config(), + url="/test-path?query=param#foobar", + ) + self.assertFalse(mock_span.is_recording()) + self.assertTrue(mock_span.is_recording.called) + self.assertFalse(mock_span.set_attribute.called) + self.assertFalse(mock_span.set_status.called) + def test_span_name_option(self): for span_name, method, path, expected in ( ("static", "POST", "/static-span-name", "static"), 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 02aabfea95c..71211907e08 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -156,15 +156,16 @@ async def __call__(self, scope, receive, send): propagators.extract(get_header_from_scope, scope) ) span_name, additional_attributes = self.span_details_callback(scope) - attributes = collect_request_attributes(scope) - attributes.update(additional_attributes) try: with self.tracer.start_as_current_span( - span_name + " asgi", - kind=trace.SpanKind.SERVER, - attributes=attributes, - ): + span_name + " asgi", kind=trace.SpanKind.SERVER, + ) as span: + if span.is_recording(): + attributes = collect_request_attributes(scope) + attributes.update(additional_attributes) + for key, value in attributes.items(): + span.set_attribute(key, value) @wraps(receive) async def wrapped_receive(): @@ -172,9 +173,10 @@ async def wrapped_receive(): span_name + " asgi." + scope["type"] + ".receive" ) as receive_span: message = await receive() - if message["type"] == "websocket.receive": - set_status_code(receive_span, 200) - receive_span.set_attribute("type", message["type"]) + if receive_span.is_recording(): + if message["type"] == "websocket.receive": + set_status_code(receive_span, 200) + receive_span.set_attribute("type", message["type"]) return message @wraps(send) @@ -182,12 +184,13 @@ async def wrapped_send(message): with self.tracer.start_as_current_span( span_name + " asgi." + scope["type"] + ".send" ) as send_span: - if message["type"] == "http.response.start": - status_code = message["status"] - set_status_code(send_span, status_code) - elif message["type"] == "websocket.send": - set_status_code(send_span, 200) - send_span.set_attribute("type", message["type"]) + if send_span.is_recording(): + if message["type"] == "http.response.start": + status_code = message["status"] + set_status_code(send_span, status_code) + elif message["type"] == "websocket.send": + set_status_code(send_span, 200) + send_span.set_attribute("type", message["type"]) await send(message) await self.app(scope, wrapped_receive, wrapped_send) diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py index f994e25966c..cf8944ce6df 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py @@ -164,6 +164,23 @@ def test_basic_asgi_call(self): outputs = self.get_all_output() self.validate_outputs(outputs) + def test_wsgi_not_recording(self): + mock_tracer = mock.Mock() + mock_span = mock.Mock() + mock_span.is_recording.return_value = False + mock_tracer.start_as_current_span.return_value = mock_span + mock_tracer.start_as_current_span.return_value.__enter__ = mock_span + mock_tracer.start_as_current_span.return_value.__exit__ = mock_span + with mock.patch("opentelemetry.trace.get_tracer") as tracer: + tracer.return_value = mock_tracer + app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) + self.seed_app(app) + self.send_default_request() + self.assertFalse(mock_span.is_recording()) + self.assertTrue(mock_span.is_recording.called) + self.assertFalse(mock_span.set_attribute.called) + self.assertFalse(mock_span.set_status.called) + def test_asgi_exc_info(self): """Test that exception information is emitted as expected.""" app = otel_asgi.OpenTelemetryMiddleware(error_asgi) diff --git a/instrumentation/opentelemetry-instrumentation-pyramid/src/opentelemetry/instrumentation/pyramid/callbacks.py b/instrumentation/opentelemetry-instrumentation-pyramid/src/opentelemetry/instrumentation/pyramid/callbacks.py index fe45c39e2a7..ada239b8e31 100644 --- a/instrumentation/opentelemetry-instrumentation-pyramid/src/opentelemetry/instrumentation/pyramid/callbacks.py +++ b/instrumentation/opentelemetry-instrumentation-pyramid/src/opentelemetry/instrumentation/pyramid/callbacks.py @@ -74,21 +74,22 @@ def _before_traversal(event): ) tracer = trace.get_tracer(__name__, __version__) - attributes = otel_wsgi.collect_request_attributes(environ) - if request.matched_route: span_name = request.matched_route.pattern - attributes["http.route"] = request.matched_route.pattern else: span_name = otel_wsgi.get_default_span_name(environ) span = tracer.start_span( - span_name, - kind=trace.SpanKind.SERVER, - attributes=attributes, - start_time=start_time, + span_name, kind=trace.SpanKind.SERVER, start_time=start_time, ) + if span.is_recording(): + attributes = otel_wsgi.collect_request_attributes(environ) + if request.matched_route: + attributes["http.route"] = request.matched_route.pattern + for key, value in attributes.items(): + span.set_attribute(key, value) + activation = tracer.use_span(span, end_on_exit=True) activation.__enter__() environ[_ENVIRON_ACTIVATION_KEY] = activation diff --git a/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_programmatic.py b/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_programmatic.py index add4660caab..38ba71cb55d 100644 --- a/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_programmatic.py +++ b/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_programmatic.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from unittest.mock import patch +from unittest.mock import Mock, patch from pyramid.config import Configurator @@ -87,6 +87,22 @@ def test_simple(self): self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) self.assertEqual(span_list[0].attributes, expected_attrs) + def test_not_recording(self): + mock_tracer = Mock() + mock_span = Mock() + mock_span.is_recording.return_value = False + mock_tracer.start_span.return_value = mock_span + mock_tracer.use_span.return_value.__enter__ = mock_span + mock_tracer.use_span.return_value.__exit__ = mock_span + with patch("opentelemetry.trace.get_tracer"): + self.client.get("/hello/123") + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 0) + self.assertFalse(mock_span.is_recording()) + self.assertTrue(mock_span.is_recording.called) + self.assertFalse(mock_span.set_attribute.called) + self.assertFalse(mock_span.set_status.called) + def test_404(self): expected_attrs = expected_attributes( {