Skip to content

Commit c1112b2

Browse files
committed
implement w3c distributed tracing
implements the `distributed-tracing` sdk spec - default span/trace id gen to use otel-friendly 16/8 byte hex IDs - new inject/extract tools for w3c headers
1 parent 0d6c87d commit c1112b2

17 files changed

Lines changed: 1274 additions & 126 deletions

py/src/braintrust/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ def is_equal(expected, output):
5151
# Check env var at import time for auto-instrumentation
5252
import os
5353

54+
from .env import validate_id_config as _validate_id_config
55+
56+
57+
# Fail fast on mutually-exclusive ID configuration (e.g. BRAINTRUST_OTEL_COMPAT
58+
# together with BRAINTRUST_LEGACY_UUID_IDS) as early as possible on import.
59+
_validate_id_config()
60+
5461

5562
if os.getenv("BRAINTRUST_INSTRUMENT_THREADS", "").lower() in ("true", "1", "yes"):
5663
try:

py/src/braintrust/context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from dataclasses import dataclass
77
from typing import Any
88

9-
from .env import BraintrustEnv
9+
from .env import BraintrustEnv, validate_id_config
1010

1111

1212
@dataclass
@@ -120,6 +120,10 @@ def get_context_manager() -> ContextManager:
120120
Braintrust-only context manager by default.
121121
"""
122122

123+
# Fail fast if the ID configuration is inconsistent (belt-and-suspenders for
124+
# the import-time check, in case env vars were mutated after import).
125+
validate_id_config()
126+
123127
# Check if OTEL should be explicitly enabled via environment variable
124128
if BraintrustEnv.OTEL_COMPAT.get(False):
125129
try:

py/src/braintrust/env.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,42 @@ class BraintrustEnv:
163163
ALL_PUBLISH_PAYLOADS_DIR = EnvVar("BRAINTRUST_ALL_PUBLISH_PAYLOADS_DIR", EnvParser.STRING)
164164
DISABLE_ATEXIT_FLUSH = EnvVar("BRAINTRUST_DISABLE_ATEXIT_FLUSH", EnvParser.BOOL)
165165
OTEL_COMPAT = EnvVar("BRAINTRUST_OTEL_COMPAT", EnvParser.BOOL)
166+
# Opt out of the default OpenTelemetry-compatible hex span/trace IDs and use
167+
# legacy UUID-based IDs (and V3 span-component export) instead.
168+
LEGACY_UUID_IDS = EnvVar("BRAINTRUST_LEGACY_UUID_IDS", EnvParser.BOOL)
169+
170+
171+
class BraintrustEnvError(Exception):
172+
"""Raised when Braintrust environment variables are configured inconsistently."""
173+
174+
175+
def use_legacy_uuid_ids() -> bool:
176+
"""Return True if the SDK should generate legacy UUID-based span/trace IDs.
177+
178+
The default is OpenTelemetry-compatible hex IDs (16-byte trace id / 8-byte
179+
span id) with V4 span-component export. Setting BRAINTRUST_LEGACY_UUID_IDS
180+
opts back into UUID IDs with V3 export.
181+
182+
Note: BRAINTRUST_OTEL_COMPAT (which selects the OpenTelemetry context
183+
manager) requires hex IDs, so it always forces hex regardless of this
184+
function. The mutually-exclusive combination is rejected by
185+
validate_id_config().
186+
"""
187+
validate_id_config()
188+
if BraintrustEnv.OTEL_COMPAT.get(False):
189+
return False
190+
return BraintrustEnv.LEGACY_UUID_IDS.get(False)
191+
192+
193+
def validate_id_config() -> None:
194+
"""Fail fast on mutually-exclusive ID configuration.
195+
196+
BRAINTRUST_OTEL_COMPAT requires OpenTelemetry-compatible hex IDs, while
197+
BRAINTRUST_LEGACY_UUID_IDS forces legacy UUID IDs. They cannot both be set.
198+
"""
199+
if BraintrustEnv.OTEL_COMPAT.get(False) and BraintrustEnv.LEGACY_UUID_IDS.get(False):
200+
raise BraintrustEnvError(
201+
"BRAINTRUST_OTEL_COMPAT and BRAINTRUST_LEGACY_UUID_IDS are mutually "
202+
"exclusive: OTEL compatibility requires hex span IDs, but legacy mode "
203+
"forces UUID span IDs. Unset one of them."
204+
)

py/src/braintrust/functions/invoke.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from .._generated_types import FunctionTypeEnum
77
from ..bt_json import bt_dumps
8-
from ..logger import Exportable, _internal_get_global_state, get_span_parent_object, login, proxy_conn
8+
from ..logger import Exportable, _get_span_parent_object, _internal_get_global_state, login, proxy_conn
99
from ..types import Metadata
1010
from ..util import response_raise_for_status
1111
from .constants import INVOKE_API_VERSION
@@ -163,7 +163,7 @@ def invoke(
163163
force_login=force_login,
164164
)
165165

166-
parent = parent if isinstance(parent, str) else parent.export() if parent else get_span_parent_object().export()
166+
parent = parent if isinstance(parent, str) else parent.export() if parent else _get_span_parent_object().export()
167167

168168
function_id_args = {}
169169
if function_id is not None:

py/src/braintrust/functions/test_invoke.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def _invoke_with_messages(messages):
7474

7575
with (
7676
patch("braintrust.functions.invoke.login"),
77-
patch("braintrust.functions.invoke.get_span_parent_object") as mock_parent,
77+
patch("braintrust.functions.invoke._get_span_parent_object") as mock_parent,
7878
patch("braintrust.functions.invoke.proxy_conn", return_value=mock_conn),
7979
):
8080
mock_parent.return_value.export.return_value = "span-export"

py/src/braintrust/id_gen.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22
import uuid
33
from abc import ABC, abstractmethod
44

5-
from .env import BraintrustEnv
5+
from .env import use_legacy_uuid_ids
66

77

88
def get_id_generator():
99
"""Factory function that creates a new ID generator instance each time.
1010
1111
This eliminates global state and makes tests parallelizable.
1212
Each caller gets their own generator instance.
13+
14+
Defaults to OpenTelemetry-compatible hex IDs. Set BRAINTRUST_LEGACY_UUID_IDS
15+
to opt back into legacy UUID-based IDs.
1316
"""
14-
use_otel = BraintrustEnv.OTEL_COMPAT.get(False)
15-
return OTELIDGenerator() if use_otel else UUIDGenerator()
17+
return UUIDGenerator() if use_legacy_uuid_ids() else OTELIDGenerator()
1618

1719

1820
class IDGenerator(ABC):

0 commit comments

Comments
 (0)