Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 17aa1e8

Browse files
authored
Update OpenCensus shim to update OpenCensus execution context (open-telemetry#3231)
* Update OpenCensus shim to update OpenCensus execution context Previously, the shim was only writing to OTel context. This updates it to write to both OC and OTel. Also updated the code to use the `span_context` provided when instrumentation instantiates the `Tracer`.
1 parent 3c98eee commit 17aa1e8

File tree

5 files changed

+243
-15
lines changed

5 files changed

+243
-15
lines changed

shim/opentelemetry-opencensus-shim/src/opentelemetry/shim/opencensus/_patch.py

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

15+
from functools import lru_cache
1516
from logging import getLogger
1617
from typing import Optional
1718

19+
from opencensus.trace.span_context import SpanContext
1820
from opencensus.trace.tracer import Tracer
1921
from opencensus.trace.tracers.noop_tracer import NoopTracer
2022

@@ -33,10 +35,19 @@ def install_shim(
3335
__version__,
3436
tracer_provider=tracer_provider,
3537
)
36-
shim_tracer = ShimTracer(NoopTracer(), otel_tracer=otel_tracer)
3738

38-
def fget_tracer(self) -> ShimTracer:
39-
return shim_tracer
39+
@lru_cache()
40+
def cached_shim_tracer(span_context: SpanContext) -> ShimTracer:
41+
return ShimTracer(
42+
NoopTracer(),
43+
oc_span_context=span_context,
44+
otel_tracer=otel_tracer,
45+
)
46+
47+
def fget_tracer(self: Tracer) -> ShimTracer:
48+
# self.span_context is how instrumentations pass propagated context into OpenCensus e.g.
49+
# https://github.com/census-instrumentation/opencensus-python/blob/fd064f438c5e490d25b004ee2545be55d2e28679/contrib/opencensus-ext-flask/opencensus/ext/flask/flask_middleware.py#L147-L153
50+
return cached_shim_tracer(self.span_context)
4051

4152
def fset_tracer(self, value) -> None:
4253
# ignore attempts to set the value
@@ -45,8 +56,8 @@ def fset_tracer(self, value) -> None:
4556
# Tracer's constructor sets self.tracer to either a NoopTracer or ContextTracer depending
4657
# on sampler:
4758
# https://github.com/census-instrumentation/opencensus-python/blob/2e08df591b507612b3968be8c2538dedbf8fab37/opencensus/trace/tracer.py#L63.
48-
# We monkeypatch Tracer.tracer with a property to return the shim instance instead. This
49-
# makes all instances of Tracer (even those already created) use the ShimTracer singleton.
59+
# We monkeypatch Tracer.tracer with a property to return a shim instance instead. This
60+
# makes all instances of Tracer (even those already created) use a ShimTracer.
5061
Tracer.tracer = property(fget_tracer, fset_tracer)
5162
_logger.info("Installed OpenCensus shim")
5263

shim/opentelemetry-opencensus-shim/src/opentelemetry/shim/opencensus/_shim_span.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
from typing import TYPE_CHECKING
1818

1919
import wrapt
20-
from opencensus.trace.base_span import BaseSpan
20+
from opencensus.trace import execution_context
21+
from opencensus.trace.blank_span import BlankSpan
2122
from opencensus.trace.span import SpanKind
2223
from opencensus.trace.status import Status
2324
from opencensus.trace.time_event import MessageEvent
@@ -62,7 +63,7 @@ def _opencensus_time_to_nanos(timestamp: str) -> int:
6263
class ShimSpan(wrapt.ObjectProxy):
6364
def __init__(
6465
self,
65-
wrapped: BaseSpan,
66+
wrapped: BlankSpan,
6667
*,
6768
otel_span: trace.Span,
6869
shim_tracer: "ShimTracer",
@@ -158,6 +159,9 @@ def __exit__(self, exception_type, exception_value, traceback):
158159
)
159160
# OpenCensus Span.__exit__() calls Tracer.end_span()
160161
# https://github.com/census-instrumentation/opencensus-python/blob/2e08df591b507612b3968be8c2538dedbf8fab37/opencensus/trace/span.py#L390
161-
# but that would cause the OTel span to be ended twice. Instead just detach it from
162-
# context directly.
162+
# but that would cause the OTel span to be ended twice. Instead, this code just copies
163+
# the context teardown from that method.
163164
context.detach(self._self_token)
165+
execution_context.set_current_span(
166+
self._self_shim_tracer.current_span()
167+
)

shim/opentelemetry-opencensus-shim/src/opentelemetry/shim/opencensus/_shim_tracer.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@
1515
import logging
1616

1717
import wrapt
18+
from opencensus.trace import execution_context
1819
from opencensus.trace.blank_span import BlankSpan
20+
from opencensus.trace.span_context import SpanContext
1921
from opencensus.trace.tracers.base import Tracer as BaseTracer
22+
from opencensus.trace.tracestate import Tracestate
2023

2124
from opentelemetry import context, trace
2225
from opentelemetry.shim.opencensus._shim_span import ShimSpan
2326

2427
_logger = logging.getLogger(__name__)
2528

2629
_SHIM_SPAN_KEY = context.create_key("opencensus-shim-span-key")
30+
_SAMPLED = trace.TraceFlags(trace.TraceFlags.SAMPLED)
2731

2832

2933
def set_shim_span_in_context(
@@ -36,12 +40,57 @@ def get_shim_span_in_context() -> ShimSpan:
3640
return context.get_value(_SHIM_SPAN_KEY)
3741

3842

43+
def set_oc_span_in_context(
44+
oc_span_context: SpanContext, ctx: context.Context
45+
) -> context.Context:
46+
"""Returns a new OTel context based on ctx with oc_span_context set as the current span"""
47+
48+
# If no SpanContext is passed to the opencensus.trace.tracer.Tracer, it creates a new one
49+
# with a random trace ID and a None span ID to be the parent:
50+
# https://github.com/census-instrumentation/opencensus-python/blob/2e08df591b507612b3968be8c2538dedbf8fab37/opencensus/trace/tracer.py#L47.
51+
#
52+
# OpenTelemetry considers this an invalid SpanContext and will ignore it, so we can just
53+
# return early
54+
if oc_span_context.span_id is None:
55+
return ctx
56+
57+
trace_id = int(oc_span_context.trace_id, 16)
58+
span_id = int(oc_span_context.span_id, 16)
59+
is_remote = oc_span_context.from_header
60+
trace_flags = (
61+
_SAMPLED if oc_span_context.trace_options.get_enabled() else None
62+
)
63+
trace_state = (
64+
trace.TraceState(tuple(oc_span_context.tracestate.items()))
65+
# OC SpanContext does not validate this type
66+
if isinstance(oc_span_context.tracestate, Tracestate)
67+
else None
68+
)
69+
70+
return trace.set_span_in_context(
71+
trace.NonRecordingSpan(
72+
trace.SpanContext(
73+
trace_id=trace_id,
74+
span_id=span_id,
75+
is_remote=is_remote,
76+
trace_flags=trace_flags,
77+
trace_state=trace_state,
78+
)
79+
)
80+
)
81+
82+
3983
# pylint: disable=abstract-method
4084
class ShimTracer(wrapt.ObjectProxy):
4185
def __init__(
42-
self, wrapped: BaseTracer, *, otel_tracer: trace.Tracer
86+
self,
87+
wrapped: BaseTracer,
88+
*,
89+
oc_span_context: SpanContext,
90+
otel_tracer: trace.Tracer
4391
) -> None:
4492
super().__init__(wrapped)
93+
self._self_oc_span_context = oc_span_context
4594
self._self_otel_tracer = otel_tracer
4695

4796
# For now, finish() is not implemented by the shim. It would require keeping a list of all
@@ -53,7 +102,15 @@ def span(self, name="span"):
53102
return self.start_span(name=name)
54103

55104
def start_span(self, name="span"):
56-
span = self._self_otel_tracer.start_span(name)
105+
parent_ctx = context.get_current()
106+
# If there is no current span in context, use the one provided to the OC Tracer at
107+
# creation time
108+
if trace.get_current_span(parent_ctx) is trace.INVALID_SPAN:
109+
parent_ctx = set_oc_span_in_context(
110+
self._self_oc_span_context, parent_ctx
111+
)
112+
113+
span = self._self_otel_tracer.start_span(name, context=parent_ctx)
57114
shim_span = ShimSpan(
58115
BlankSpan(name=name, context_tracer=self),
59116
otel_span=span,
@@ -67,20 +124,27 @@ def start_span(self, name="span"):
67124
# equivalent to the below. This can cause context to leak but is equivalent.
68125
# pylint: disable=protected-access
69126
shim_span._self_token = context.attach(ctx)
127+
# Also set it in OC's context, equivalent to
128+
# https://github.com/census-instrumentation/opencensus-python/blob/2e08df591b507612b3968be8c2538dedbf8fab37/opencensus/trace/tracers/context_tracer.py#L94
129+
execution_context.set_current_span(shim_span)
70130
return shim_span
71131

72132
def end_span(self):
73-
"""Finishes the current span in the context and pops restores the context from before
74-
the span was started.
133+
"""Finishes the current span in the context and restores the context from before the
134+
span was started.
75135
"""
76136
span = self.current_span()
77137
if not span:
78138
_logger.warning("No active span, cannot do end_span.")
79139
return
80140

81141
span.finish()
142+
82143
# pylint: disable=protected-access
83144
context.detach(span._self_token)
145+
# Also reset the OC execution_context, equivalent to
146+
# https://github.com/census-instrumentation/opencensus-python/blob/2e08df591b507612b3968be8c2538dedbf8fab37/opencensus/trace/tracers/context_tracer.py#L114-L117
147+
execution_context.set_current_span(self.current_span())
84148

85149
# pylint: disable=no-self-use
86150
def current_span(self):

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

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,21 @@
1616
import unittest
1717
from unittest.mock import patch
1818

19+
from opencensus.trace import trace_options, tracestate
1920
from opencensus.trace.blank_span import BlankSpan as OcBlankSpan
2021
from opencensus.trace.link import Link as OcLink
2122
from opencensus.trace.span import SpanKind
23+
from opencensus.trace.span_context import SpanContext
2224
from opencensus.trace.tracer import Tracer as OcTracer
2325
from opencensus.trace.tracers.noop_tracer import NoopTracer as OcNoopTracer
2426

27+
from opentelemetry import context, trace
2528
from opentelemetry.shim.opencensus import install_shim, uninstall_shim
2629
from opentelemetry.shim.opencensus._shim_span import ShimSpan
27-
from opentelemetry.shim.opencensus._shim_tracer import ShimTracer
30+
from opentelemetry.shim.opencensus._shim_tracer import (
31+
ShimTracer,
32+
set_oc_span_in_context,
33+
)
2834

2935

3036
class TestShim(unittest.TestCase):
@@ -126,3 +132,78 @@ def test_shim_span_contextmanager_calls_does_not_call_end(self):
126132
pass
127133

128134
spy_otel_span.end.assert_not_called()
135+
136+
def test_set_oc_span_in_context_no_span_id(self):
137+
# This won't create a span ID and is the default behavior if you don't pass a context
138+
# when creating the Tracer
139+
ctx = set_oc_span_in_context(SpanContext(), context.get_current())
140+
self.assertIs(trace.get_current_span(ctx), trace.INVALID_SPAN)
141+
142+
def test_set_oc_span_in_context_ids(self):
143+
ctx = set_oc_span_in_context(
144+
SpanContext(
145+
trace_id="ace0216bab2b7ba249761dbb19c871b7",
146+
span_id="1fead89ecf242225",
147+
),
148+
context.get_current(),
149+
)
150+
span_ctx = trace.get_current_span(ctx).get_span_context()
151+
152+
self.assertEqual(
153+
trace.format_trace_id(span_ctx.trace_id),
154+
"ace0216bab2b7ba249761dbb19c871b7",
155+
)
156+
self.assertEqual(
157+
trace.format_span_id(span_ctx.span_id), "1fead89ecf242225"
158+
)
159+
160+
def test_set_oc_span_in_context_remote(self):
161+
for is_from_remote in True, False:
162+
ctx = set_oc_span_in_context(
163+
SpanContext(
164+
trace_id="ace0216bab2b7ba249761dbb19c871b7",
165+
span_id="1fead89ecf242225",
166+
from_header=is_from_remote,
167+
),
168+
context.get_current(),
169+
)
170+
span_ctx = trace.get_current_span(ctx).get_span_context()
171+
self.assertEqual(span_ctx.is_remote, is_from_remote)
172+
173+
def test_set_oc_span_in_context_traceoptions(self):
174+
for oc_trace_options, expect in [
175+
# Not sampled
176+
(
177+
trace_options.TraceOptions("0"),
178+
trace.TraceFlags(trace.TraceFlags.DEFAULT),
179+
),
180+
# Sampled
181+
(
182+
trace_options.TraceOptions("1"),
183+
trace.TraceFlags(trace.TraceFlags.SAMPLED),
184+
),
185+
]:
186+
ctx = set_oc_span_in_context(
187+
SpanContext(
188+
trace_id="ace0216bab2b7ba249761dbb19c871b7",
189+
span_id="1fead89ecf242225",
190+
trace_options=oc_trace_options,
191+
),
192+
context.get_current(),
193+
)
194+
span_ctx = trace.get_current_span(ctx).get_span_context()
195+
self.assertEqual(span_ctx.trace_flags, expect)
196+
197+
def test_set_oc_span_in_context_tracestate(self):
198+
ctx = set_oc_span_in_context(
199+
SpanContext(
200+
trace_id="ace0216bab2b7ba249761dbb19c871b7",
201+
span_id="1fead89ecf242225",
202+
tracestate=tracestate.Tracestate({"hello": "tracestate"}),
203+
),
204+
context.get_current(),
205+
)
206+
span_ctx = trace.get_current_span(ctx).get_span_context()
207+
self.assertEqual(
208+
span_ctx.trace_state, trace.TraceState([("hello", "tracestate")])
209+
)

0 commit comments

Comments
 (0)