@@ -145,6 +145,62 @@ def from_map(cls, maxlen, mapping):
145
145
Link = namedtuple ("Link" , ("context" , "attributes" ))
146
146
147
147
148
+ class SpanProcessor :
149
+ def on_start (self , span : "Span" ) -> None :
150
+ """Called when a :class:`Span` is started.
151
+
152
+ This method is called synchronously on the execution thread, therefore
153
+ it should not block or throw an exception.
154
+
155
+ Args:
156
+ span: The :class:`Span` that just started.
157
+ """
158
+
159
+ def on_end (self , span : "Span" ) -> None :
160
+ """Called when a :class:`Span` is ended.
161
+
162
+ This method is called synchronously on the execution thread, therefore
163
+ it should not block or throw an exception.
164
+
165
+ Args:
166
+ span: The :class:`Span` that just ended.
167
+ """
168
+
169
+ def shutdown (self ) -> None :
170
+ """Called when a :class:`Tracer` is shutdown."""
171
+
172
+
173
+ # Used when no SpanProcessor has been added to the Tracer
174
+ _NO_OP_SPAN_PROCESSOR = SpanProcessor ()
175
+
176
+
177
+ class MultiSpanProcessor (SpanProcessor ):
178
+ """Implementation of :class:`SpanProcessor` that forwards all received
179
+ events to a list of `SpanProcessor`.
180
+ """
181
+
182
+ def __init__ (self ):
183
+ # use a tuple to avoid race conditions when adding a new span and
184
+ # interating throw it on "on_start" and "on_end".
185
+ self ._span_processors = ()
186
+
187
+ def add_span_processor (self , span_processor : SpanProcessor ):
188
+ """Adds a SpanProcessor to the list handled by this instance."""
189
+ self ._span_processors = self ._span_processors + (span_processor ,)
190
+
191
+ def on_start (self , span : "Span" ) -> None :
192
+ for sp in self ._span_processors :
193
+ sp .on_start (span )
194
+
195
+ def on_end (self , span : "Span" ) -> None :
196
+ for sp in self ._span_processors :
197
+ sp .on_end (span )
198
+
199
+ def on_shutdown (self ) -> None :
200
+ for sp in self ._span_processors :
201
+ sp .shutdown ()
202
+
203
+
148
204
class Span (trace_api .Span ):
149
205
"""See `opentelemetry.trace.Span`.
150
206
@@ -161,6 +217,8 @@ class Span(trace_api.Span):
161
217
attributes: The span's attributes to be exported
162
218
events: Timestamped events to be exported
163
219
links: Links to other spans to be exported
220
+ span_processor: `SpanProcessor` to invoke when starting and ending
221
+ this `Span`.
164
222
"""
165
223
166
224
# Initialize these lazily assuming most spans won't have them.
@@ -179,6 +237,7 @@ def __init__(
179
237
attributes : types .Attributes = None , # TODO
180
238
events : typing .Sequence [Event ] = None , # TODO
181
239
links : typing .Sequence [Link ] = None , # TODO
240
+ span_processor : SpanProcessor = _NO_OP_SPAN_PROCESSOR ,
182
241
) -> None :
183
242
184
243
self .name = name
@@ -190,6 +249,7 @@ def __init__(
190
249
self .attributes = attributes
191
250
self .events = events
192
251
self .links = links
252
+ self .span_processor = span_processor
193
253
194
254
if attributes is None :
195
255
self .attributes = Span .empty_attributes
@@ -247,10 +307,12 @@ def add_link(
247
307
def start (self ):
248
308
if self .start_time is None :
249
309
self .start_time = util .time_ns ()
310
+ self .span_processor .on_start (self )
250
311
251
312
def end (self ):
252
313
if self .end_time is None :
253
314
self .end_time = util .time_ns ()
315
+ self .span_processor .on_end (self )
254
316
255
317
def update_name (self , name : str ) -> None :
256
318
self .name = name
@@ -286,6 +348,8 @@ def __init__(self, name: str = "") -> None:
286
348
if name :
287
349
slot_name = "{}.current_span" .format (name )
288
350
self ._current_span_slot = Context .register_slot (slot_name )
351
+ self ._active_span_processor = _NO_OP_SPAN_PROCESSOR
352
+ self ._lock = threading .Lock ()
289
353
290
354
def get_current_span (self ):
291
355
"""See `opentelemetry.trace.Tracer.get_current_span`."""
@@ -325,7 +389,12 @@ def create_span(
325
389
parent_context .trace_options ,
326
390
parent_context .trace_state ,
327
391
)
328
- return Span (name = name , context = context , parent = parent )
392
+ return Span (
393
+ name = name ,
394
+ context = context ,
395
+ parent = parent ,
396
+ span_processor = self ._active_span_processor ,
397
+ )
329
398
330
399
@contextmanager
331
400
def use_span (self , span : "Span" ) -> typing .Iterator ["Span" ]:
@@ -339,5 +408,15 @@ def use_span(self, span: "Span") -> typing.Iterator["Span"]:
339
408
self ._current_span_slot .set (span_snapshot )
340
409
span .end ()
341
410
411
+ def add_span_processor (self , span_processor : SpanProcessor ) -> None :
412
+ """Registers a new :class:`SpanProcessor` for this `Tracer`.
413
+
414
+ The span processors are invoked in the same order they are registered.
415
+ """
416
+ with self ._lock :
417
+ if self ._active_span_processor is _NO_OP_SPAN_PROCESSOR :
418
+ self ._active_span_processor = MultiSpanProcessor ()
419
+ self ._active_span_processor .add_span_processor (span_processor )
420
+
342
421
343
422
tracer = Tracer ()
0 commit comments