Skip to content

Commit 1d18404

Browse files
committed
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 open-telemetry#2549 Made-with: Cursor
1 parent fb94553 commit 1d18404

5 files changed

Lines changed: 141 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
## Unreleased
1414

15+
- `opentelemetry-opentracing-shim`: Map OpenTracing `span.kind` tag to OpenTelemetry SpanKind
16+
([#2549](https://github.com/open-telemetry/opentelemetry-python/issues/2549))
1517
- `opentelemetry-sdk`: fix type annotations on `MetricReader` and related types
1618
([#4938](https://github.com/open-telemetry/opentelemetry-python/pull/4938/))
1719

1820
## Version 1.40.0/0.61b0 (2026-03-04)
1921

2022
- `opentelemetry-sdk`: deprecate `LoggingHandler` in favor of `opentelemetry-instrumentation-logging`, see `opentelemetry-instrumentation-logging` documentation
2123
([#4919](https://github.com/open-telemetry/opentelemetry-python/pull/4919))
22-
- `opentelemetry-sdk`: Clarify log processor error handling expectations in documentation
23-
([#4915](https://github.com/open-telemetry/opentelemetry-python/pull/4915))
2424
- bump semantic-conventions to v1.40.0
2525
([#4941](https://github.com/open-telemetry/opentelemetry-python/pull/4941))
2626
- Add stale PR GitHub Action

shim/opentelemetry-opentracing-shim/src/opentelemetry/shim/opentracing_shim/__init__.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
Tracer,
100100
UnsupportedFormatException,
101101
)
102+
from opentracing import tags as ot_tags
102103
from typing_extensions import deprecated
103104

104105
from opentelemetry.baggage import get_baggage, set_baggage
@@ -674,13 +675,21 @@ def start_span(
674675
if start_time_ns is not None:
675676
start_time_ns = util.time_seconds_to_ns(start_time)
676677

677-
span = self._otel_tracer.start_span(
678-
operation_name,
679-
context=parent_span_context,
680-
links=valid_links,
681-
attributes=tags,
682-
start_time=start_time_ns,
683-
)
678+
# Extract span kind from OpenTracing tags if present.
679+
# OpenTracing uses the "span.kind" tag to indicate span kind, while
680+
# OpenTelemetry uses a dedicated kind argument.
681+
span_kwargs: dict = {
682+
"context": parent_span_context,
683+
"links": valid_links,
684+
"attributes": tags,
685+
"start_time": start_time_ns,
686+
}
687+
if tags is not None and ot_tags.SPAN_KIND in tags:
688+
span_kwargs["kind"] = util.opentracing_kind_to_otel_kind(
689+
tags[ot_tags.SPAN_KIND]
690+
)
691+
692+
span = self._otel_tracer.start_span(operation_name, **span_kwargs)
684693

685694
context = SpanContextShim(span.get_span_context())
686695
return SpanShim(self, context, span)

shim/opentelemetry-opentracing-shim/src/opentelemetry/shim/opentracing_shim/util.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,22 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from opentracing import tags as ot_tags
16+
17+
from opentelemetry.trace import SpanKind
18+
1519
# A default event name to be used for logging events when a better event name
1620
# can't be derived from the event's key-value pairs.
1721
DEFAULT_EVENT_NAME = "log"
1822

23+
# Mapping from OpenTracing span kind tag values to OpenTelemetry SpanKind.
24+
_OPENTRACING_TO_OTEL_KIND = {
25+
ot_tags.SPAN_KIND_RPC_CLIENT: SpanKind.CLIENT,
26+
ot_tags.SPAN_KIND_RPC_SERVER: SpanKind.SERVER,
27+
ot_tags.SPAN_KIND_CONSUMER: SpanKind.CONSUMER,
28+
ot_tags.SPAN_KIND_PRODUCER: SpanKind.PRODUCER,
29+
}
30+
1931

2032
def time_seconds_to_ns(time_seconds):
2133
"""Converts a time value in seconds to a time value in nanoseconds.
@@ -52,3 +64,19 @@ def event_name_from_kv(key_values):
5264
return DEFAULT_EVENT_NAME
5365

5466
return key_values["event"]
67+
68+
69+
def opentracing_kind_to_otel_kind(opentracing_kind: str) -> SpanKind:
70+
"""Converts an OpenTracing span kind tag value to an OpenTelemetry SpanKind.
71+
72+
Args:
73+
opentracing_kind: The value of the OpenTracing ``span.kind`` tag.
74+
Expected values are ``client``, ``server``, ``consumer``, or
75+
``producer``.
76+
77+
Returns:
78+
The corresponding :class:`opentelemetry.trace.SpanKind`. Returns
79+
``SpanKind.INTERNAL`` if the input is not a recognized OpenTracing
80+
span kind.
81+
"""
82+
return _OPENTRACING_TO_OTEL_KIND.get(opentracing_kind, SpanKind.INTERNAL)

shim/opentelemetry-opentracing-shim/tests/test_shim.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,61 @@ def test_mixed_mode(self):
668668
scope.span.unwrap().parent,
669669
opentelemetry_span.context,
670670
)
671+
672+
def test_span_kind_from_tags(self):
673+
"""Test that span.kind OpenTracing tag is converted to OTel SpanKind."""
674+
675+
# Test consumer kind
676+
with self.shim.start_active_span(
677+
"TestSpanKind1", tags={"span.kind": "consumer"}
678+
) as scope:
679+
self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.CONSUMER)
680+
681+
# Test producer kind
682+
with self.shim.start_active_span(
683+
"TestSpanKind2", tags={"span.kind": "producer"}
684+
) as scope:
685+
self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.PRODUCER)
686+
687+
# Test client kind
688+
with self.shim.start_active_span(
689+
"TestSpanKind3", tags={"span.kind": "client"}
690+
) as scope:
691+
self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.CLIENT)
692+
693+
# Test server kind
694+
with self.shim.start_active_span(
695+
"TestSpanKind4", tags={"span.kind": "server"}
696+
) as scope:
697+
self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.SERVER)
698+
699+
# Test unknown kind defaults to INTERNAL
700+
with self.shim.start_active_span(
701+
"TestSpanKind5", tags={"span.kind": "unknown"}
702+
) as scope:
703+
self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.INTERNAL)
704+
705+
# Test no span.kind tag defaults to INTERNAL
706+
with self.shim.start_active_span(
707+
"TestSpanKind6", tags={"foo": "bar"}
708+
) as scope:
709+
self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.INTERNAL)
710+
711+
# Test no tags at all defaults to INTERNAL
712+
with self.shim.start_active_span("TestSpanKind7") as scope:
713+
self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.INTERNAL)
714+
715+
def test_span_kind_tag_preserved_in_attributes(self):
716+
"""Test that span.kind tag is also preserved as an attribute."""
717+
718+
with self.shim.start_active_span(
719+
"TestSpanKindAttr", tags={"span.kind": "consumer", "foo": "bar"}
720+
) as scope:
721+
# Verify the span kind is correctly set
722+
self.assertEqual(scope.span.unwrap().kind, trace.SpanKind.CONSUMER)
723+
# Verify the span.kind tag is preserved as an attribute
724+
self.assertEqual(
725+
scope.span.unwrap().attributes.get("span.kind"), "consumer"
726+
)
727+
# Verify other attributes are also preserved
728+
self.assertEqual(scope.span.unwrap().attributes.get("foo"), "bar")

shim/opentelemetry-opentracing-shim/tests/test_util.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
from time import time, time_ns
1616
from unittest import TestCase
1717

18+
from opentracing import tags as ot_tags
19+
1820
from opentelemetry.shim.opentracing_shim.util import (
1921
DEFAULT_EVENT_NAME,
2022
event_name_from_kv,
23+
opentracing_kind_to_otel_kind,
2124
time_seconds_from_ns,
2225
time_seconds_to_ns,
2326
)
27+
from opentelemetry.trace import SpanKind
2428

2529

2630
class TestUtil(TestCase):
@@ -68,3 +72,36 @@ def test_time_conversion_precision(self):
6872
# TODO: This seems to work consistently, but we should find out the
6973
# biggest possible loss of precision.
7074
self.assertAlmostEqual(result, time_seconds, places=6)
75+
76+
def test_opentracing_kind_to_otel_kind(self):
77+
"""Test conversion from OpenTracing span.kind tag values to OTel SpanKind."""
78+
79+
# Test all valid OpenTracing span kinds
80+
self.assertEqual(
81+
opentracing_kind_to_otel_kind(ot_tags.SPAN_KIND_RPC_CLIENT),
82+
SpanKind.CLIENT,
83+
)
84+
self.assertEqual(
85+
opentracing_kind_to_otel_kind(ot_tags.SPAN_KIND_RPC_SERVER),
86+
SpanKind.SERVER,
87+
)
88+
self.assertEqual(
89+
opentracing_kind_to_otel_kind(ot_tags.SPAN_KIND_CONSUMER),
90+
SpanKind.CONSUMER,
91+
)
92+
self.assertEqual(
93+
opentracing_kind_to_otel_kind(ot_tags.SPAN_KIND_PRODUCER),
94+
SpanKind.PRODUCER,
95+
)
96+
97+
# Test unknown span kind defaults to INTERNAL
98+
self.assertEqual(
99+
opentracing_kind_to_otel_kind("unknown"),
100+
SpanKind.INTERNAL,
101+
)
102+
103+
# Test empty string defaults to INTERNAL
104+
self.assertEqual(
105+
opentracing_kind_to_otel_kind(""),
106+
SpanKind.INTERNAL,
107+
)

0 commit comments

Comments
 (0)