9
9
from opentelemetry import context , trace
10
10
from opentelemetry .trace import (
11
11
Span ,
12
+ Status ,
13
+ StatusCode ,
12
14
Tracer ,
13
15
NonRecordingSpan ,
14
16
SpanKind as OpenTelemetrySpanKind ,
47
49
_ERROR_SPAN_ATTRIBUTE = "error.type"
48
50
49
51
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
+
50
86
class OpenTelemetrySpan (HttpSpanMixin , object ):
51
87
"""OpenTelemetry plugin for Azure client libraries.
52
88
@@ -71,8 +107,7 @@ def __init__(
71
107
links : Optional [List ["CoreLink" ]] = None ,
72
108
** kwargs : Any ,
73
109
) -> None :
74
- self ._context_tokens = []
75
- self ._current_ctxt_manager : Optional [ContextManager [Span ]] = None
110
+ self ._current_ctxt_manager : Optional [_SuppressionContextManager ] = None
76
111
77
112
# TODO: Once we have additional supported versions, we should add a way to specify the version.
78
113
self ._schema_version = OpenTelemetrySchema .get_latest_version ()
@@ -113,12 +148,6 @@ def __init__(
113
148
self ._span_instance = NonRecordingSpan (context = self .get_current_span ().get_span_context ())
114
149
return
115
150
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
-
122
151
current_tracer = trace .get_tracer (
123
152
__name__ ,
124
153
__version__ ,
@@ -239,19 +268,8 @@ def kind(self, value: SpanKind) -> None:
239
268
)
240
269
241
270
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__ ()
255
273
return self
256
274
257
275
def __exit__ (self , exception_type , exception_value , traceback ) -> None :
@@ -260,12 +278,20 @@ def __exit__(self, exception_type, exception_value, traceback) -> None:
260
278
module = exception_type .__module__ if exception_type .__module__ != "builtins" else ""
261
279
error_type = f"{ module } .{ exception_type .__qualname__ } " if module else exception_type .__qualname__
262
280
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.
263
292
if self ._current_ctxt_manager :
264
293
self ._current_ctxt_manager .__exit__ (exception_type , exception_value , traceback )
265
294
self ._current_ctxt_manager = None
266
- for token in self ._context_tokens :
267
- context .detach (token )
268
- self ._context_tokens .remove (token )
269
295
270
296
def start (self ) -> None :
271
297
# Spans are automatically started at their creation with OpenTelemetry.
@@ -274,9 +300,6 @@ def start(self) -> None:
274
300
def finish (self ) -> None :
275
301
"""Set the end time for a span."""
276
302
self .span_instance .end ()
277
- for token in self ._context_tokens :
278
- context .detach (token )
279
- self ._context_tokens .remove (token )
280
303
281
304
def to_header (self ) -> Dict [str , str ]:
282
305
"""
@@ -370,15 +393,19 @@ def get_current_tracer(cls) -> Tracer:
370
393
return trace .get_tracer (__name__ , __version__ )
371
394
372
395
@classmethod
373
- def change_context (cls , span : Span ) -> ContextManager [ Span ] :
396
+ def change_context (cls , span : Union [ Span , "OpenTelemetrySpan" ] ) -> ContextManager :
374
397
"""Change the context for the life of this context manager.
375
398
376
399
:param span: The span to use as the current span
377
400
:type span: ~opentelemetry.trace.Span
378
401
:return: A context manager to use for the duration of the span
379
402
:rtype: contextmanager
380
403
"""
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 )
382
409
383
410
@classmethod
384
411
def set_current_span (cls , span : Span ) -> None : # pylint: disable=docstring-missing-return,docstring-missing-rtype
0 commit comments