From 76e91f8f5a12d0627b85a5d1f748963be887baf0 Mon Sep 17 00:00:00 2001 From: Jojin Date: Sat, 25 Jul 2026 12:45:18 +0530 Subject: [PATCH 1/2] opentelemetry-instrumentation-grpc: respect suppressed instrumentation in server interceptors Check is_instrumentation_enabled() in the sync and asyncio server interceptors before wrapping RPC handlers with telemetry, so no server spans are created while instrumentation is suppressed in the current context. This mirrors the client-side check added in #559 and covers the remaining server half of #476. Signed-off-by: Jojin --- .changelog/476.fixed | 1 + .../instrumentation/grpc/_aio_server.py | 4 ++ .../instrumentation/grpc/_server.py | 4 ++ .../tests/test_aio_server_interceptor.py | 53 +++++++++++++++++ .../tests/test_server_interceptor.py | 58 +++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 .changelog/476.fixed diff --git a/.changelog/476.fixed b/.changelog/476.fixed new file mode 100644 index 0000000000..ee927af8ab --- /dev/null +++ b/.changelog/476.fixed @@ -0,0 +1 @@ +`opentelemetry-instrumentation-grpc`: respect suppressed instrumentation in server interceptors diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_aio_server.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_aio_server.py index c7e073a4bc..411412ff6b 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_aio_server.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_aio_server.py @@ -10,6 +10,7 @@ except ImportError: from wrapt import ObjectProxy as BaseObjectProxy +from opentelemetry.instrumentation.utils import is_instrumentation_enabled from opentelemetry.semconv._incubating.attributes.rpc_attributes import ( RPC_GRPC_STATUS_CODE, ) @@ -71,6 +72,9 @@ class OpenTelemetryAioServerInterceptor( """ async def intercept_service(self, continuation, handler_call_details): + if not is_instrumentation_enabled(): + return await continuation(handler_call_details) + if self._filter is not None and not self._filter(handler_call_details): return await continuation(handler_call_details) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py index e55e6c0994..d04dc83f7e 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py @@ -18,6 +18,7 @@ from opentelemetry import trace from opentelemetry.context import attach, detach +from opentelemetry.instrumentation.utils import is_instrumentation_enabled from opentelemetry.propagate import extract from opentelemetry.semconv._incubating.attributes.net_attributes import ( NET_PEER_IP, @@ -268,6 +269,9 @@ def _start_span( ) def intercept_service(self, continuation, handler_call_details): + if not is_instrumentation_enabled(): + return continuation(handler_call_details) + if self._filter is not None and not self._filter(handler_call_details): return continuation(handler_call_details) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_aio_server_interceptor.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_aio_server_interceptor.py index c05c79f92e..868f3b7b02 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_aio_server_interceptor.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_aio_server_interceptor.py @@ -12,6 +12,7 @@ GrpcAioInstrumentorServer, aio_server_interceptor, ) +from opentelemetry.instrumentation.utils import suppress_instrumentation from opentelemetry.sdk import trace as trace_sdk from opentelemetry.semconv._incubating.attributes.net_attributes import ( NET_PEER_IP, @@ -55,6 +56,15 @@ async def ServerStreamingMethod(self, request, context): ) +class SuppressAioRpcInterceptor(grpc.aio.ServerInterceptor): + """An interceptor which runs the rest of the server interceptor chain + with instrumentation suppressed.""" + + async def intercept_service(self, continuation, handler_call_details): + with suppress_instrumentation(): + return await continuation(handler_call_details) + + async def run_with_test_server( runnable, servicer=Servicer(), interceptors=None ): @@ -339,6 +349,49 @@ async def request(channel): parent_span.context.trace_id, child_span.context.trace_id ) + async def test_suppress_instrumentation(self): + """Check that no span is created for a call when instrumentation + is suppressed.""" + rpc_call = "/GRPCTestServer/SimpleMethod" + + async def request(channel): + request = Request(client_id=1, request_data="test") + msg = request.SerializeToString() + return await channel.unary_unary(rpc_call)(msg) + + await run_with_test_server( + request, + interceptors=[ + SuppressAioRpcInterceptor(), + aio_server_interceptor(), + ], + ) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 0) + + async def test_suppress_instrumentation_streaming(self): + """Check that no span is created for a streaming call when + instrumentation is suppressed.""" + rpc_call = "/GRPCTestServer/ServerStreamingMethod" + + async def request(channel): + request = Request(client_id=1, request_data="test") + msg = request.SerializeToString() + async for response in channel.unary_stream(rpc_call)(msg): + print(response) + + await run_with_test_server( + request, + interceptors=[ + SuppressAioRpcInterceptor(), + aio_server_interceptor(), + ], + ) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 0) + async def test_span_lifetime(self): """Verify that the interceptor captures sub spans within the given trace""" diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor.py index d4e9f50c8b..4376b83953 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor.py @@ -18,6 +18,7 @@ GrpcInstrumentorServer, server_interceptor, ) +from opentelemetry.instrumentation.utils import suppress_instrumentation from opentelemetry.sdk import trace as trace_sdk from opentelemetry.semconv._incubating.attributes.net_attributes import ( NET_PEER_IP, @@ -59,6 +60,15 @@ def service(self, handler_call_details): return UnaryUnaryMethodHandler(self._unary_unary_handler) +class SuppressRpcInterceptor(grpc.ServerInterceptor): + """An interceptor which runs the rest of the server interceptor chain + with instrumentation suppressed.""" + + def intercept_service(self, continuation, handler_call_details): + with suppress_instrumentation(): + return continuation(handler_call_details) + + class Servicer(GRPCTestServerServicer): """Our test servicer""" @@ -389,6 +399,54 @@ def ServerStreamingMethod(self, request, context): parent_span.context.trace_id, child_span.context.trace_id ) + def test_suppress_instrumentation(self): + """Check that no span is created for a call when instrumentation + is suppressed.""" + + interceptor = server_interceptor() + + with self.server( + max_workers=1, + interceptors=[SuppressRpcInterceptor(), interceptor], + ) as (server, channel): + add_GRPCTestServerServicer_to_server(Servicer(), server) + + rpc_call = "/GRPCTestServer/SimpleMethod" + request = Request(client_id=1, request_data="test") + msg = request.SerializeToString() + try: + server.start() + channel.unary_unary(rpc_call)(msg) + finally: + server.stop(None) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 0) + + def test_suppress_instrumentation_streaming(self): + """Check that no span is created for a streaming call when + instrumentation is suppressed.""" + + interceptor = server_interceptor() + + with self.server( + max_workers=1, + interceptors=[SuppressRpcInterceptor(), interceptor], + ) as (server, channel): + add_GRPCTestServerServicer_to_server(Servicer(), server) + + rpc_call = "/GRPCTestServer/ServerStreamingMethod" + request = Request(client_id=1, request_data="test") + msg = request.SerializeToString() + try: + server.start() + list(channel.unary_stream(rpc_call)(msg)) + finally: + server.stop(None) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 0) + def test_span_lifetime(self): """Check that the span is active for the duration of the call.""" From 97602c601e97754ea21d80b7248faf97e036138d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Em=C3=ADdio?= <9735060+emdneto@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:19:19 -0300 Subject: [PATCH 2/2] Add changelog entry for issue 4870 --- .changelog/{476.fixed => 4870.fixed} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changelog/{476.fixed => 4870.fixed} (100%) diff --git a/.changelog/476.fixed b/.changelog/4870.fixed similarity index 100% rename from .changelog/476.fixed rename to .changelog/4870.fixed