From fab489d63de07f37c2be876f7b88470495661627 Mon Sep 17 00:00:00 2001 From: cabaret-pro Date: Wed, 4 Mar 2026 21:34:20 -0500 Subject: [PATCH] feat(opentracing-shim): Map OpenTracing span.kind tag to OTel SpanKind The OpenTracing shim was not translating the span.kind tag to the OpenTelemetry SpanKind argument. This change extracts the span.kind tag from OpenTracing spans and maps it to the corresponding OTel SpanKind (CLIENT, SERVER, CONSUMER, PRODUCER, or INTERNAL). The span.kind tag is preserved in the span attributes to avoid modifying user data within the shim. Fixes #2549 Made-with: Cursor --- CHANGELOG.md | 4 +- .../shim/opentracing_shim/__init__.py | 32 +++++++--- .../tests/test_shim.py | 58 +++++++++++++++++++ 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f088cc321..22773e4ba62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- `opentelemetry-opentracing-shim`: Map OpenTracing `span.kind` tag to OpenTelemetry SpanKind + ([#2549](https://github.com/open-telemetry/opentelemetry-python/issues/2549)) - `opentelemetry-sdk`: fix type annotations on `MetricReader` and related types ([#4938](https://github.com/open-telemetry/opentelemetry-python/pull/4938/)) @@ -19,8 +21,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-sdk`: deprecate `LoggingHandler` in favor of `opentelemetry-instrumentation-logging`, see `opentelemetry-instrumentation-logging` documentation ([#4919](https://github.com/open-telemetry/opentelemetry-python/pull/4919)) -- `opentelemetry-sdk`: Clarify log processor error handling expectations in documentation - ([#4915](https://github.com/open-telemetry/opentelemetry-python/pull/4915)) - bump semantic-conventions to v1.40.0 ([#4941](https://github.com/open-telemetry/opentelemetry-python/pull/4941)) - Add stale PR GitHub Action diff --git a/shim/opentelemetry-opentracing-shim/src/opentelemetry/shim/opentracing_shim/__init__.py b/shim/opentelemetry-opentracing-shim/src/opentelemetry/shim/opentracing_shim/__init__.py index e7261a0d92f..af4bd57133b 100644 --- a/shim/opentelemetry-opentracing-shim/src/opentelemetry/shim/opentracing_shim/__init__.py +++ b/shim/opentelemetry-opentracing-shim/src/opentelemetry/shim/opentracing_shim/__init__.py @@ -99,6 +99,7 @@ Tracer, UnsupportedFormatException, ) +from opentracing import tags as ot_tags from typing_extensions import deprecated from opentelemetry.baggage import get_baggage, set_baggage @@ -117,6 +118,7 @@ INVALID_SPAN_CONTEXT, Link, NonRecordingSpan, + SpanKind, TracerProvider, get_current_span, set_span_in_context, @@ -130,6 +132,14 @@ logger = logging.getLogger(__name__) _SHIM_KEY = create_key("scope_shim") +# Mapping from OpenTracing span kind tag values to OpenTelemetry SpanKind. +_OPENTRACING_TO_OTEL_KIND = { + ot_tags.SPAN_KIND_RPC_CLIENT: SpanKind.CLIENT, + ot_tags.SPAN_KIND_RPC_SERVER: SpanKind.SERVER, + ot_tags.SPAN_KIND_CONSUMER: SpanKind.CONSUMER, + ot_tags.SPAN_KIND_PRODUCER: SpanKind.PRODUCER, +} + def create_tracer(otel_tracer_provider: TracerProvider) -> "TracerShim": """Creates a :class:`TracerShim` object from the provided OpenTelemetry @@ -674,13 +684,21 @@ def start_span( if start_time_ns is not None: start_time_ns = util.time_seconds_to_ns(start_time) - span = self._otel_tracer.start_span( - operation_name, - context=parent_span_context, - links=valid_links, - attributes=tags, - start_time=start_time_ns, - ) + # Extract span kind from OpenTracing tags if present. + # OpenTracing uses the "span.kind" tag to indicate span kind, while + # OpenTelemetry uses a dedicated kind argument. + span_kwargs: dict = { + "context": parent_span_context, + "links": valid_links, + "attributes": tags, + "start_time": start_time_ns, + } + if tags is not None and ot_tags.SPAN_KIND in tags: + span_kwargs["kind"] = _OPENTRACING_TO_OTEL_KIND.get( + tags[ot_tags.SPAN_KIND], SpanKind.INTERNAL + ) + + span = self._otel_tracer.start_span(operation_name, **span_kwargs) context = SpanContextShim(span.get_span_context()) return SpanShim(self, context, span) diff --git a/shim/opentelemetry-opentracing-shim/tests/test_shim.py b/shim/opentelemetry-opentracing-shim/tests/test_shim.py index 796a4e064b1..4fb4d7a353a 100644 --- a/shim/opentelemetry-opentracing-shim/tests/test_shim.py +++ b/shim/opentelemetry-opentracing-shim/tests/test_shim.py @@ -668,3 +668,61 @@ def test_mixed_mode(self): scope.span.unwrap().parent, opentelemetry_span.context, ) + + def test_span_kind_from_tags(self): + """Test that span.kind OpenTracing tag is converted to OTel SpanKind.""" + + # Test consumer kind + with self.shim.start_active_span( + "TestSpanKind1", tags={"span.kind": "consumer"} + ) as scope: + self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.CONSUMER) + + # Test producer kind + with self.shim.start_active_span( + "TestSpanKind2", tags={"span.kind": "producer"} + ) as scope: + self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.PRODUCER) + + # Test client kind + with self.shim.start_active_span( + "TestSpanKind3", tags={"span.kind": "client"} + ) as scope: + self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.CLIENT) + + # Test server kind + with self.shim.start_active_span( + "TestSpanKind4", tags={"span.kind": "server"} + ) as scope: + self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.SERVER) + + # Test unknown kind defaults to INTERNAL + with self.shim.start_active_span( + "TestSpanKind5", tags={"span.kind": "unknown"} + ) as scope: + self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.INTERNAL) + + # Test no span.kind tag defaults to INTERNAL + with self.shim.start_active_span( + "TestSpanKind6", tags={"foo": "bar"} + ) as scope: + self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.INTERNAL) + + # Test no tags at all defaults to INTERNAL + with self.shim.start_active_span("TestSpanKind7") as scope: + self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.INTERNAL) + + def test_span_kind_tag_preserved_in_attributes(self): + """Test that span.kind tag is also preserved as an attribute.""" + + with self.shim.start_active_span( + "TestSpanKindAttr", tags={"span.kind": "consumer", "foo": "bar"} + ) as scope: + # Verify the span kind is correctly set + self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.CONSUMER) + # Verify the span.kind tag is preserved as an attribute + self.assertEqual( + scope.span.unwrap().attributes.get("span.kind"), "consumer" + ) + # Verify other attributes are also preserved + self.assertEqual(scope.span.unwrap().attributes.get("foo"), "bar")