diff --git a/CHANGELOG.md b/CHANGELOG.md index 16f54fea2d..2c9bec4e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3419](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3419)) - `opentelemetry-instrumentation` don't print duplicated conflict log error message ([#3432](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3432)) +- `opentelemetry-instrumentation-grpc` Check for None result in gRPC + ([#3380](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3381)) ## Version 1.32.0/0.53b0 (2025-04-10) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py index e27c9e826f..adc713678c 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py @@ -165,7 +165,7 @@ def _intercept(self, request, metadata, client_info, invoker): span.record_exception(exc) raise exc finally: - if not result: + if result is None: span.end() return self._trace_result(span, rpc_info, result) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor.py index 9fb922a615..5be0141c3b 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor.py @@ -13,6 +13,8 @@ # limitations under the License. # pylint:disable=cyclic-import +from unittest import mock + import grpc import opentelemetry.instrumentation.grpc @@ -26,6 +28,7 @@ ) from opentelemetry.instrumentation.utils import suppress_instrumentation from opentelemetry.propagate import get_global_textmap, set_global_textmap +from opentelemetry.sdk.trace import Span as SdkSpan from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.test.mock_textmap import MockTextMapPropagator from opentelemetry.test.test_base import TestBase @@ -269,6 +272,32 @@ def test_error_stream_stream(self): trace.StatusCode.ERROR, ) + def test_client_interceptor_falsy_response( + self, + ): + """ensure that client interceptor closes the span only once even if the response is falsy.""" + + with mock.patch.object(SdkSpan, "end") as span_end_mock: + tracer_provider, _exporter = self.create_tracer_provider() + tracer = tracer_provider.get_tracer(__name__) + + interceptor = OpenTelemetryClientInterceptor(tracer) + + def invoker(_request, _metadata): + return {} + + request = Request(client_id=1, request_data="data") + interceptor.intercept_unary( + request, + {}, + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=None, + ), + invoker=invoker, + ) + self.assertEqual(span_end_mock.call_count, 1) + def test_client_interceptor_trace_context_propagation( self, ): # pylint: disable=no-self-use