@@ -145,6 +145,68 @@ def from_map(cls, maxlen, mapping):
145
145
Link = namedtuple ("Link" , ("context" , "attributes" ))
146
146
147
147
148
+ class SpanProcessor :
149
+ """Interface which allows hooks for SDK's `Span`s start and end method
150
+ invocations.
151
+
152
+ Span processors can be registered directly using
153
+ :func:`~Tracer:add_span_processor` and they are invoked in the same order
154
+ as they were registered.
155
+ """
156
+
157
+ def on_start (self , span : "Span" ) -> None :
158
+ """Called when a :class:`Span` is started.
159
+
160
+ This method is called synchronously on the thread that starts the
161
+ span, therefore it should not block or throw an exception.
162
+
163
+ Args:
164
+ span: The :class:`Span` that just started.
165
+ """
166
+
167
+ def on_end (self , span : "Span" ) -> None :
168
+ """Called when a :class:`Span` is ended.
169
+
170
+ This method is called synchronously on the thread that ends the
171
+ span, therefore it should not block or throw an exception.
172
+
173
+ Args:
174
+ span: The :class:`Span` that just ended.
175
+ """
176
+
177
+ def shutdown (self ) -> None :
178
+ """Called when a :class:`Tracer` is shutdown."""
179
+
180
+
181
+ class MultiSpanProcessor (SpanProcessor ):
182
+ """Implementation of :class:`SpanProcessor` that forwards all received
183
+ events to a list of `SpanProcessor`.
184
+ """
185
+
186
+ def __init__ (self ):
187
+ # use a tuple to avoid race conditions when adding a new span and
188
+ # iterating through it on "on_start" and "on_end".
189
+ self ._span_processors = ()
190
+ self ._lock = threading .Lock ()
191
+
192
+ def add_span_processor (self , span_processor : SpanProcessor ):
193
+ """Adds a SpanProcessor to the list handled by this instance."""
194
+ with self ._lock :
195
+ self ._span_processors = self ._span_processors + (span_processor ,)
196
+
197
+ def on_start (self , span : "Span" ) -> None :
198
+ for sp in self ._span_processors :
199
+ sp .on_start (span )
200
+
201
+ def on_end (self , span : "Span" ) -> None :
202
+ for sp in self ._span_processors :
203
+ sp .on_end (span )
204
+
205
+ def shutdown (self ) -> None :
206
+ for sp in self ._span_processors :
207
+ sp .shutdown ()
208
+
209
+
148
210
class Span (trace_api .Span ):
149
211
"""See `opentelemetry.trace.Span`.
150
212
@@ -161,6 +223,8 @@ class Span(trace_api.Span):
161
223
attributes: The span's attributes to be exported
162
224
events: Timestamped events to be exported
163
225
links: Links to other spans to be exported
226
+ span_processor: `SpanProcessor` to invoke when starting and ending
227
+ this `Span`.
164
228
"""
165
229
166
230
# Initialize these lazily assuming most spans won't have them.
@@ -179,6 +243,7 @@ def __init__(
179
243
attributes : types .Attributes = None , # TODO
180
244
events : typing .Sequence [Event ] = None , # TODO
181
245
links : typing .Sequence [Link ] = None , # TODO
246
+ span_processor : SpanProcessor = SpanProcessor (),
182
247
) -> None :
183
248
184
249
self .name = name
@@ -190,6 +255,7 @@ def __init__(
190
255
self .attributes = attributes
191
256
self .events = events
192
257
self .links = links
258
+ self .span_processor = span_processor
193
259
194
260
if attributes is None :
195
261
self .attributes = Span .empty_attributes
@@ -247,10 +313,12 @@ def add_link(
247
313
def start (self ):
248
314
if self .start_time is None :
249
315
self .start_time = util .time_ns ()
316
+ self .span_processor .on_start (self )
250
317
251
318
def end (self ):
252
319
if self .end_time is None :
253
320
self .end_time = util .time_ns ()
321
+ self .span_processor .on_end (self )
254
322
255
323
def update_name (self , name : str ) -> None :
256
324
self .name = name
@@ -286,6 +354,7 @@ def __init__(self, name: str = "") -> None:
286
354
if name :
287
355
slot_name = "{}.current_span" .format (name )
288
356
self ._current_span_slot = Context .register_slot (slot_name )
357
+ self ._active_span_processor = MultiSpanProcessor ()
289
358
290
359
def get_current_span (self ):
291
360
"""See `opentelemetry.trace.Tracer.get_current_span`."""
@@ -325,7 +394,12 @@ def create_span(
325
394
parent_context .trace_options ,
326
395
parent_context .trace_state ,
327
396
)
328
- return Span (name = name , context = context , parent = parent )
397
+ return Span (
398
+ name = name ,
399
+ context = context ,
400
+ parent = parent ,
401
+ span_processor = self ._active_span_processor ,
402
+ )
329
403
330
404
@contextmanager
331
405
def use_span (self , span : "Span" ) -> typing .Iterator ["Span" ]:
@@ -339,5 +413,15 @@ def use_span(self, span: "Span") -> typing.Iterator["Span"]:
339
413
self ._current_span_slot .set (span_snapshot )
340
414
span .end ()
341
415
416
+ def add_span_processor (self , span_processor : SpanProcessor ) -> None :
417
+ """Registers a new :class:`SpanProcessor` for this `Tracer`.
418
+
419
+ The span processors are invoked in the same order they are registered.
420
+ """
421
+
422
+ # no lock here because MultiSpanProcessor.add_span_processor is
423
+ # thread safe
424
+ self ._active_span_processor .add_span_processor (span_processor )
425
+
342
426
343
427
tracer = Tracer ()
0 commit comments