@@ -151,6 +151,61 @@ def from_map(cls, maxlen, mapping):
151
151
Link = namedtuple ('Link' , ('context' , 'attributes' ))
152
152
153
153
154
+ class SpanProcessor ():
155
+ def on_start (self , span : 'Span' ) -> None :
156
+ """Called when a :class:`Span` is started.
157
+
158
+ This method is called synchronously on the execution thread, therefore
159
+ it should not block or throw an exception.
160
+
161
+ Args:
162
+ span: The :class:`Span` that just started.
163
+ """
164
+
165
+ def on_end (self , span : 'Span' ) -> None :
166
+ """Called when a :class:`Span` is ended.
167
+
168
+ This method is called synchronously on the execution thread, therefore
169
+ it should not block or throw an exception.
170
+
171
+ Args:
172
+ span: The :class:`Span` that just ended.
173
+ """
174
+
175
+ def shutdown (self ) -> None :
176
+ """Called when a :class:`Tracer` is shutdown."""
177
+
178
+
179
+ # Used when no SpanProcessor has been added to the Tracer
180
+ _NO_OP_SPAN_PROCESSOR = SpanProcessor ()
181
+
182
+
183
+ class MultiSpanProcessor (SpanProcessor ):
184
+ """Implementation of :class:`SpanProcessor` that forwards all received
185
+ events to a list of `SpanProcessor`.
186
+ """
187
+ def __init__ (self ):
188
+ # use a tuple to avoid race conditions when adding a new span and
189
+ # interating throw it on "on_start" and "on_end".
190
+ self ._span_processors = ()
191
+
192
+ def add_span_processor (self , span_processor : SpanProcessor ):
193
+ """Adds a SpanProcessor to the list handled by this instance."""
194
+ self ._span_processors = self ._span_processors + (span_processor , )
195
+
196
+ def on_start (self , span : 'Span' ) -> None :
197
+ for sp in self ._span_processors :
198
+ sp .on_start (span )
199
+
200
+ def on_end (self , span : 'Span' ) -> None :
201
+ for sp in self ._span_processors :
202
+ sp .on_end (span )
203
+
204
+ def on_shutdown (self ) -> None :
205
+ for sp in self ._span_processors :
206
+ sp .shutdown ()
207
+
208
+
154
209
class Span (trace_api .Span ):
155
210
"""See `opentelemetry.trace.Span`.
156
211
@@ -167,6 +222,8 @@ class Span(trace_api.Span):
167
222
attributes: The span's attributes to be exported
168
223
events: Timestamped events to be exported
169
224
links: Links to other spans to be exported
225
+ span_processor: `SpanProcessor` to invoke when starting and ending
226
+ this `Span`.
170
227
"""
171
228
172
229
# Initialize these lazily assuming most spans won't have them.
@@ -184,6 +241,7 @@ def __init__(self: 'Span',
184
241
attributes : types .Attributes = None , # TODO
185
242
events : typing .Sequence [Event ] = None , # TODO
186
243
links : typing .Sequence [Link ] = None , # TODO
244
+ span_processor : SpanProcessor = _NO_OP_SPAN_PROCESSOR
187
245
) -> None :
188
246
189
247
self .name = name
@@ -195,6 +253,7 @@ def __init__(self: 'Span',
195
253
self .attributes = attributes
196
254
self .events = events
197
255
self .links = links
256
+ self .span_processor = span_processor
198
257
199
258
if attributes is None :
200
259
self .attributes = Span .empty_attributes
@@ -256,10 +315,12 @@ def add_link(self: 'Span',
256
315
def start (self ):
257
316
if self .start_time is None :
258
317
self .start_time = util .time_ns ()
318
+ self .span_processor .on_start (self )
259
319
260
320
def end (self ):
261
321
if self .end_time is None :
262
322
self .end_time = util .time_ns ()
323
+ self .span_processor .on_end (self )
263
324
264
325
def update_name (self , name : str ) -> None :
265
326
self .name = name
@@ -297,6 +358,8 @@ def __init__(self,
297
358
if name :
298
359
slot_name = '{}.current_span' .format (name )
299
360
self ._current_span_slot = Context .register_slot (slot_name )
361
+ self ._active_span_processor = _NO_OP_SPAN_PROCESSOR
362
+ self ._lock = threading .Lock ()
300
363
301
364
def get_current_span (self ):
302
365
"""See `opentelemetry.trace.Tracer.get_current_span`."""
@@ -334,7 +397,8 @@ def create_span(self,
334
397
span_id ,
335
398
parent_context .trace_options ,
336
399
parent_context .trace_state )
337
- return Span (name = name , context = context , parent = parent )
400
+ return Span (name = name , context = context , parent = parent ,
401
+ span_processor = self ._active_span_processor )
338
402
339
403
@contextmanager
340
404
def use_span (self , span : 'Span' ) -> typing .Iterator ['Span' ]:
@@ -348,5 +412,15 @@ def use_span(self, span: 'Span') -> typing.Iterator['Span']:
348
412
self ._current_span_slot .set (span_snapshot )
349
413
span .end ()
350
414
415
+ def add_span_processor (self , span_processor : SpanProcessor ) -> None :
416
+ """Registers a new :class:`SpanProcessor` for this `Tracer`.
417
+
418
+ The span processors are invoked in the same order they are registered.
419
+ """
420
+ with self ._lock :
421
+ if self ._active_span_processor is _NO_OP_SPAN_PROCESSOR :
422
+ self ._active_span_processor = MultiSpanProcessor ()
423
+ self ._active_span_processor .add_span_processor (span_processor )
424
+
351
425
352
426
tracer = Tracer ()
0 commit comments