99from opentelemetry import context , trace
1010from opentelemetry .trace import (
1111 Span ,
12+ Status ,
13+ StatusCode ,
1214 Tracer ,
1315 NonRecordingSpan ,
1416 SpanKind as OpenTelemetrySpanKind ,
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+
5086class 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