Skip to content

Commit 2425499

Browse files
feat: add _on_ending hook support to span processors
- Implemented _on_ending method in AISpanProcessor and BraintrustSpanProcessor to forward pre-end hooks. - Added a helper function _forward_on_ending to handle compatibility with different OpenTelemetry SDK versions. - Updated tests to verify the presence and functionality of the new _on_ending method.
1 parent d66be16 commit 2425499

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

py/src/braintrust/otel/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ def get_tracer_provider():
4343
FILTER_PREFIXES = ("gen_ai.", "braintrust.", "llm.", "ai.", "traceloop.")
4444

4545

46+
def _forward_on_ending(processor, span) -> None:
47+
"""
48+
Forward OpenTelemetry's optional _on_ending hook when available.
49+
50+
Newer OpenTelemetry SDK versions call _on_ending before on_end. Older SDK
51+
versions may not implement the hook on wrapped processors.
52+
"""
53+
on_ending = getattr(processor, "_on_ending", None)
54+
if callable(on_ending):
55+
on_ending(span)
56+
57+
4658
class AISpanProcessor:
4759
"""
4860
A span processor that filters spans to only export filtered telemetry.
@@ -79,6 +91,13 @@ def on_end(self, span):
7991
if self._should_keep_filtered_span(span):
8092
self._processor.on_end(span)
8193

94+
def _on_ending(self, span):
95+
"""
96+
Forward pre-end hook for kept spans when the wrapped processor supports it.
97+
"""
98+
if self._should_keep_filtered_span(span):
99+
_forward_on_ending(self._processor, span)
100+
82101
def shutdown(self):
83102
"""Shutdown the inner processor."""
84103
self._processor.shutdown()
@@ -322,6 +341,10 @@ def on_end(self, span):
322341
"""Forward span end events to the inner processor."""
323342
self._processor.on_end(span)
324343

344+
def _on_ending(self, span):
345+
"""Forward pre-end hook when the wrapped processor supports it."""
346+
_forward_on_ending(self._processor, span)
347+
325348
def shutdown(self):
326349
"""Shutdown the inner processor."""
327350
self._processor.shutdown()

py/src/braintrust/test_otel.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@ def test_braintrust_otel_filter_ai_spans_environment_variable():
191191
# Verify it has the expected attributes
192192
assert hasattr(filter_processor, "_processor")
193193
assert hasattr(filter_processor, "_custom_filter")
194+
assert hasattr(filter_processor, "_on_ending")
194195
assert hasattr(filter_processor, "_should_keep_filtered_span")
196+
assert callable(filter_processor._on_ending)
195197
assert callable(filter_processor._should_keep_filtered_span)
196198

197199
finally:
@@ -216,10 +218,12 @@ def test_braintrust_span_processor_class():
216218
# Should have the span processor interface
217219
assert hasattr(processor, "on_start")
218220
assert hasattr(processor, "on_end")
221+
assert hasattr(processor, "_on_ending")
219222
assert hasattr(processor, "shutdown")
220223
assert hasattr(processor, "force_flush")
221224
assert callable(processor.on_start)
222225
assert callable(processor.on_end)
226+
assert callable(processor._on_ending)
223227
assert callable(processor.shutdown)
224228
assert callable(processor.force_flush)
225229

@@ -235,6 +239,7 @@ def test_braintrust_span_processor_class():
235239
# Should have the same interface
236240
assert hasattr(processor_with_filtering, "on_start")
237241
assert hasattr(processor_with_filtering, "on_end")
242+
assert hasattr(processor_with_filtering, "_on_ending")
238243
assert hasattr(processor_with_filtering, "shutdown")
239244
assert hasattr(processor_with_filtering, "force_flush")
240245

0 commit comments

Comments
 (0)