Skip to content

Commit d8e751e

Browse files
committed
fix(shim): Map OpenTracing span.kind tag to OTel SpanKind
Extract span.kind from OpenTracing tags, map to the corresponding OpenTelemetry SpanKind, and remove it from attributes to avoid duplication. Unrecognized kinds default to SpanKind.INTERNAL. Fixes #2549
1 parent 9c270da commit d8e751e

File tree

4 files changed

+81
-0
lines changed

4 files changed

+81
-0
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
INVALID_SPAN_CONTEXT,
118118
Link,
119119
NonRecordingSpan,
120+
SpanKind,
120121
TracerProvider,
121122
get_current_span,
122123
set_span_in_context,
@@ -667,6 +668,16 @@ def start_span(
667668

668669
parent_span_context = set_span_in_context(parent)
669670

671+
# Extract span.kind from OpenTracing tags and map to OTel SpanKind.
672+
# The span.kind tag is removed from tags as it is not a regular
673+
# attribute but controls the span's kind in OpenTelemetry.
674+
kind = SpanKind.INTERNAL
675+
if tags is not None and "span.kind" in tags:
676+
mapped_kind = util.opentracing_kind_to_otel_kind(tags["span.kind"])
677+
if mapped_kind is not None:
678+
kind = mapped_kind
679+
del tags["span.kind"]
680+
670681
# The OpenTracing API expects time values to be `float` values which
671682
# represent the number of seconds since the epoch. OpenTelemetry
672683
# represents time values as nanoseconds since the epoch.
@@ -677,6 +688,7 @@ def start_span(
677688
span = self._otel_tracer.start_span(
678689
operation_name,
679690
context=parent_span_context,
691+
kind=kind,
680692
links=valid_links,
681693
attributes=tags,
682694
start_time=start_time_ns,

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

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

15+
from opentelemetry.trace import SpanKind
16+
1517
# A default event name to be used for logging events when a better event name
1618
# can't be derived from the event's key-value pairs.
1719
DEFAULT_EVENT_NAME = "log"
@@ -52,3 +54,20 @@ def event_name_from_kv(key_values):
5254
return DEFAULT_EVENT_NAME
5355

5456
return key_values["event"]
57+
58+
59+
_OPENTRACING_TO_OTEL_KIND = {
60+
"client": SpanKind.CLIENT,
61+
"server": SpanKind.SERVER,
62+
"producer": SpanKind.PRODUCER,
63+
"consumer": SpanKind.CONSUMER,
64+
}
65+
66+
67+
def opentracing_kind_to_otel_kind(opentracing_kind):
68+
"""Maps an OpenTracing span.kind tag value to the corresponding
69+
OpenTelemetry SpanKind.
70+
71+
Returns None if the kind is not recognized.
72+
"""
73+
return _OPENTRACING_TO_OTEL_KIND.get(opentracing_kind)

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
MockTextMapPropagator,
3636
NOOPTextMapPropagator,
3737
)
38+
from opentelemetry.trace import SpanKind
3839

3940

4041
class TestShim(TestCase):
@@ -643,6 +644,34 @@ def test_active(self):
643644
# Verify no span is active.
644645
self.assertIsNone(self.shim.active_span)
645646

647+
def test_span_kind_from_tags(self):
648+
"""Test that span.kind OpenTracing tag is mapped to OTel SpanKind."""
649+
test_cases = [
650+
({"span.kind": "consumer"}, SpanKind.CONSUMER),
651+
({"span.kind": "producer"}, SpanKind.PRODUCER),
652+
({"span.kind": "client"}, SpanKind.CLIENT),
653+
({"span.kind": "server"}, SpanKind.SERVER),
654+
({"span.kind": "unknown_kind"}, SpanKind.INTERNAL),
655+
({"other_tag": "value"}, SpanKind.INTERNAL),
656+
(None, SpanKind.INTERNAL),
657+
]
658+
659+
for tags, expected_kind in test_cases:
660+
with self.subTest(tags=tags, expected_kind=expected_kind):
661+
with self.shim.start_active_span(
662+
"TestSpanKind", tags=tags
663+
) as scope:
664+
self.assertEqual(scope.span.unwrap().kind, expected_kind)
665+
666+
def test_span_kind_tag_removed_from_attributes(self):
667+
"""Test that span.kind tag is removed from span attributes after extraction."""
668+
with self.shim.start_active_span(
669+
"TestSpanKind", tags={"span.kind": "client", "other": "value"}
670+
) as scope:
671+
self.assertEqual(scope.span.unwrap().kind, SpanKind.CLIENT)
672+
self.assertNotIn("span.kind", scope.span.unwrap().attributes)
673+
self.assertEqual(scope.span.unwrap().attributes["other"], "value")
674+
646675
def test_mixed_mode(self):
647676
"""Test that span parent-child relationship is kept between
648677
OpenTelemetry and the OpenTracing shim"""

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
from opentelemetry.shim.opentracing_shim.util import (
1919
DEFAULT_EVENT_NAME,
2020
event_name_from_kv,
21+
opentracing_kind_to_otel_kind,
2122
time_seconds_from_ns,
2223
time_seconds_to_ns,
2324
)
25+
from opentelemetry.trace import SpanKind
2426

2527

2628
class TestUtil(TestCase):
@@ -54,6 +56,25 @@ def test_time_seconds_from_ns(self):
5456

5557
self.assertEqual(result, time_nanoseconds / 1e9)
5658

59+
def test_opentracing_kind_to_otel_kind(self):
60+
"""Test mapping of OpenTracing kind tags to OTel SpanKind."""
61+
test_cases = {
62+
"client": SpanKind.CLIENT,
63+
"server": SpanKind.SERVER,
64+
"producer": SpanKind.PRODUCER,
65+
"consumer": SpanKind.CONSUMER,
66+
"unknown": None,
67+
"": None,
68+
}
69+
for opentracing_kind, expected in test_cases.items():
70+
with self.subTest(
71+
opentracing_kind=opentracing_kind, expected=expected
72+
):
73+
self.assertEqual(
74+
opentracing_kind_to_otel_kind(opentracing_kind),
75+
expected,
76+
)
77+
5778
def test_time_conversion_precision(self):
5879
"""Verify time conversion from seconds to nanoseconds and vice versa is
5980
accurate enough.

0 commit comments

Comments
 (0)