diff --git a/CHANGELOG.md b/CHANGELOG.md index 6003d1a7947..9497876944d 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)) - Fix intermittent CI failures in `getting-started` and `tracecontext` jobs caused by GitHub git CDN SHA propagation lag by installing contrib packages from the already-checked-out local copy instead of a second git clone ([#4958](https://github.com/open-telemetry/opentelemetry-python/pull/4958)) - `opentelemetry-sdk`: fix type annotations on `MetricReader` and related types @@ -21,8 +23,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")