Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions py/src/braintrust/otel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ def add_braintrust_span_processor(
filter_ai_spans: bool = False,
custom_filter=None,
headers: dict[str, str] | None = None,
exporter=None,
):
processor = BraintrustSpanProcessor(
api_key=api_key,
Expand All @@ -218,6 +219,7 @@ def add_braintrust_span_processor(
filter_ai_spans=filter_ai_spans,
custom_filter=custom_filter,
headers=headers,
exporter=exporter,
)
tracer_provider.add_span_processor(processor)

Expand All @@ -235,6 +237,10 @@ class BraintrustSpanProcessor:

> processor = BraintrustSpanProcessor(filter_ai_spans=True)
> provider.add_span_processor(processor)

> custom_exporter = OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")
> processor = BraintrustSpanProcessor(exporter=custom_exporter)
> provider.add_span_processor(processor)
"""

def __init__(
Expand All @@ -245,6 +251,7 @@ def __init__(
filter_ai_spans: bool = False,
custom_filter=None,
headers: dict[str, str] | None = None,
exporter=None,
SpanProcessor: type | None = None,
):
"""
Expand All @@ -257,23 +264,27 @@ def __init__(
filter_ai_spans: Whether to enable AI span filtering. Defaults to False.
custom_filter: Optional custom filter function for filtering.
headers: Additional headers to include in requests.
exporter: Optional pre-configured OpenTelemetry exporter instance.
When provided, api_key/parent/api_url/headers are ignored.
SpanProcessor: Optional span processor class (BatchSpanProcessor or SimpleSpanProcessor). Defaults to BatchSpanProcessor.
"""
# Create the exporter
# Convert api_url to the full endpoint URL that OtelExporter expects
exporter_url = None
if api_url:
exporter_url = f"{api_url.rstrip('/')}/otel/v1/traces"

self._exporter = OtelExporter(url=exporter_url, api_key=api_key, parent=parent, headers=headers)

# Create the processor chain
if not OTEL_AVAILABLE:
raise ImportError(
"OpenTelemetry packages are not installed. "
"Install optional OpenTelemetry dependencies with: pip install braintrust[otel]"
)

if exporter is not None:
self._exporter = exporter
else:
# Create the default Braintrust exporter.
# Convert api_url to the full endpoint URL that OtelExporter expects.
exporter_url = None
if api_url:
exporter_url = f"{api_url.rstrip('/')}/otel/v1/traces"

self._exporter = OtelExporter(url=exporter_url, api_key=api_key, parent=parent, headers=headers)

if SpanProcessor is None:
SpanProcessor = BatchSpanProcessor

Expand Down Expand Up @@ -355,7 +366,7 @@ def force_flush(self, timeout_millis=30000):

@property
def exporter(self):
"""Access to the underlying OtelExporter."""
"""Access to the underlying span exporter."""
return self._exporter

@property
Expand Down
14 changes: 14 additions & 0 deletions py/src/braintrust/test_otel.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ def test_braintrust_span_processor_class():
pytest.skip("OpenTelemetry SDK not fully installed, skipping test")

from braintrust.otel import BraintrustSpanProcessor
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter

# Test basic processor without filtering
with pytest.MonkeyPatch.context() as m:
Expand Down Expand Up @@ -243,6 +245,18 @@ def test_braintrust_span_processor_class():
assert hasattr(processor_with_filtering, "shutdown")
assert hasattr(processor_with_filtering, "force_flush")

# Test processor with custom exporter injection
custom_exporter = InMemorySpanExporter()
with pytest.MonkeyPatch.context() as m:
m.delenv("BRAINTRUST_API_KEY", raising=False)
m.delenv("BRAINTRUST_PARENT", raising=False)
processor_with_custom_exporter = BraintrustSpanProcessor(
exporter=custom_exporter,
SpanProcessor=SimpleSpanProcessor,
)

assert processor_with_custom_exporter.exporter is custom_exporter

# Test processor with custom parameters
with pytest.MonkeyPatch.context() as m:
m.setenv("BRAINTRUST_API_KEY", "test-api-key")
Expand Down
Loading