Skip to content

Commit 1541021

Browse files
authored
azure-core-tracing-otel: update suppression and fix context management (Azure#39994)
1 parent 8d3838d commit 1541021

File tree

2 files changed

+258
-57
lines changed

2 files changed

+258
-57
lines changed

sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/opentelemetry_span/__init__.py

+56-29
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from opentelemetry import context, trace
1010
from opentelemetry.trace import (
1111
Span,
12+
Status,
13+
StatusCode,
1214
Tracer,
1315
NonRecordingSpan,
1416
SpanKind as OpenTelemetrySpanKind,
@@ -47,6 +49,40 @@
4749
_ERROR_SPAN_ATTRIBUTE = "error.type"
4850

4951

52+
class _SuppressionContextManager(ContextManager):
53+
def __init__(self, span: "OpenTelemetrySpan"):
54+
self._span = span
55+
self._context_token: Optional[object] = None
56+
self._current_ctxt_manager: Optional[ContextManager[Span]] = None
57+
58+
def __enter__(self) -> Any:
59+
ctx = context.get_current()
60+
if not isinstance(self._span.span_instance, NonRecordingSpan):
61+
if self._span.kind in (SpanKind.INTERNAL, SpanKind.CLIENT, SpanKind.PRODUCER):
62+
# This is a client call that's reported for SDK service method.
63+
# We're going to suppress all nested spans reported in the context of this call.
64+
# We're not suppressing anything in the scope of SERVER or CONSUMER spans because
65+
# those wrap user code which may do HTTP requests and call other SDKs.
66+
ctx = context.set_value(_SUPPRESSED_SPAN_FLAG, True, ctx)
67+
# Since core already instruments HTTP calls, we need to suppress any automatic HTTP instrumentation
68+
# provided by other libraries to prevent duplicate spans. This has no effect if no automatic HTTP
69+
# instrumentation libraries are being used.
70+
ctx = context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True, ctx)
71+
72+
# Since the span is not suppressed, let's keep a reference to it in the context so that children spans
73+
# always have access to the last non-suppressed parent span.
74+
ctx = context.set_value(_LAST_UNSUPPRESSED_SPAN, self._span, ctx)
75+
ctx = trace.set_span_in_context(self._span._span_instance, ctx)
76+
self._context_token = context.attach(ctx)
77+
78+
return self
79+
80+
def __exit__(self, exc_type, exc_value, traceback):
81+
if self._context_token:
82+
context.detach(self._context_token)
83+
self._context_token = None
84+
85+
5086
class OpenTelemetrySpan(HttpSpanMixin, object):
5187
"""OpenTelemetry plugin for Azure client libraries.
5288
@@ -71,8 +107,7 @@ def __init__(
71107
links: Optional[List["CoreLink"]] = None,
72108
**kwargs: Any,
73109
) -> None:
74-
self._context_tokens = []
75-
self._current_ctxt_manager: Optional[ContextManager[Span]] = None
110+
self._current_ctxt_manager: Optional[_SuppressionContextManager] = None
76111

77112
# TODO: Once we have additional supported versions, we should add a way to specify the version.
78113
self._schema_version = OpenTelemetrySchema.get_latest_version()
@@ -113,12 +148,6 @@ def __init__(
113148
self._span_instance = NonRecordingSpan(context=self.get_current_span().get_span_context())
114149
return
115150

116-
if otel_kind == OpenTelemetrySpanKind.CLIENT:
117-
# Since core already instruments HTTP calls, we need to suppress any automatic HTTP instrumentation
118-
# provided by other libraries to prevent duplicate spans. This has no effect if no automatic HTTP
119-
# instrumentation libraries are being used.
120-
self._context_tokens.append(context.attach(context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True)))
121-
122151
current_tracer = trace.get_tracer(
123152
__name__,
124153
__version__,
@@ -239,19 +268,8 @@ def kind(self, value: SpanKind) -> None:
239268
)
240269

241270
def __enter__(self) -> "OpenTelemetrySpan":
242-
# Start the span.
243-
if not isinstance(self.span_instance, NonRecordingSpan):
244-
if self.kind == SpanKind.INTERNAL:
245-
# Suppress INTERNAL spans within this context.
246-
self._context_tokens.append(context.attach(context.set_value(_SUPPRESSED_SPAN_FLAG, True)))
247-
248-
# Since the span is not suppressed, let's keep a reference to it in the context so that children spans
249-
# always have access to the last non-suppressed parent span.
250-
self._context_tokens.append(context.attach(context.set_value(_LAST_UNSUPPRESSED_SPAN, self)))
251-
252-
self._current_ctxt_manager = trace.use_span(self._span_instance, end_on_exit=True)
253-
if self._current_ctxt_manager:
254-
self._current_ctxt_manager.__enter__()
271+
self._current_ctxt_manager = _SuppressionContextManager(self)
272+
self._current_ctxt_manager.__enter__()
255273
return self
256274

257275
def __exit__(self, exception_type, exception_value, traceback) -> None:
@@ -260,12 +278,20 @@ def __exit__(self, exception_type, exception_value, traceback) -> None:
260278
module = exception_type.__module__ if exception_type.__module__ != "builtins" else ""
261279
error_type = f"{module}.{exception_type.__qualname__}" if module else exception_type.__qualname__
262280
self.add_attribute(_ERROR_SPAN_ATTRIBUTE, error_type)
281+
282+
self.span_instance.set_status(
283+
Status(
284+
status_code=StatusCode.ERROR,
285+
description=f"{error_type}: {exception_value}",
286+
)
287+
)
288+
289+
self.finish()
290+
291+
# end the context manager.
263292
if self._current_ctxt_manager:
264293
self._current_ctxt_manager.__exit__(exception_type, exception_value, traceback)
265294
self._current_ctxt_manager = None
266-
for token in self._context_tokens:
267-
context.detach(token)
268-
self._context_tokens.remove(token)
269295

270296
def start(self) -> None:
271297
# Spans are automatically started at their creation with OpenTelemetry.
@@ -274,9 +300,6 @@ def start(self) -> None:
274300
def finish(self) -> None:
275301
"""Set the end time for a span."""
276302
self.span_instance.end()
277-
for token in self._context_tokens:
278-
context.detach(token)
279-
self._context_tokens.remove(token)
280303

281304
def to_header(self) -> Dict[str, str]:
282305
"""
@@ -370,15 +393,19 @@ def get_current_tracer(cls) -> Tracer:
370393
return trace.get_tracer(__name__, __version__)
371394

372395
@classmethod
373-
def change_context(cls, span: Span) -> ContextManager[Span]:
396+
def change_context(cls, span: Union[Span, "OpenTelemetrySpan"]) -> ContextManager:
374397
"""Change the context for the life of this context manager.
375398
376399
:param span: The span to use as the current span
377400
:type span: ~opentelemetry.trace.Span
378401
:return: A context manager to use for the duration of the span
379402
:rtype: contextmanager
380403
"""
381-
return trace.use_span(span, end_on_exit=False)
404+
405+
if isinstance(span, Span):
406+
return trace.use_span(span, end_on_exit=False)
407+
408+
return _SuppressionContextManager(span)
382409

383410
@classmethod
384411
def set_current_span(cls, span: Span) -> None: # pylint: disable=docstring-missing-return,docstring-missing-rtype

0 commit comments

Comments
 (0)