> ## Documentation Index
> Fetch the complete documentation index at: https://docs.braintrust.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Link Braintrust and OpenTelemetry spans

> Combine Braintrust and OpenTelemetry spans in one trace within an application or across services

To combine [OpenTelemetry](https://opentelemetry.io/docs/) spans with Braintrust spans, [share context within a process](#share-context-within-a-process) or [propagate context across services](#propagate-context-across-services), depending on where the spans are created. You can use both approaches in the same application. Configure [trace export](https://docs.braintrust.dev/integrations/sdk-integrations/opentelemetry/send-traces-and-logs.md) before following these examples.

## Share context within a process

Compatibility mode stores the active span in OpenTelemetry's context, so Braintrust and OpenTelemetry instrumentation contribute to the same trace within a process. For example, use it to nest OpenTelemetry spans under a Braintrust evaluation span.

**TypeScript**

Call `setupOtelCompat()` before creating any loggers or spans, and use `AsyncLocalStorageContextManager` to nest spans under their parent evaluation spans:

> [!NOTE]
>
> Compatibility mode requires the `@braintrust/otel` package, v0.1.0 or later.

> [!NOTE]
>
> [Span customizers](https://docs.braintrust.dev/sdks/typescript/redact-sensitive-data.md#opentelemetry-spans) run in compat mode with `@braintrust/otel` v1.1.0 or later and `braintrust` v3.38.0 or later. Earlier versions don't apply customizers in compat mode. In `@braintrust/otel` v1.0.2, `setupOtelCompat()` also logs a `console.error` if span customizers are registered.

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
import { trace, context } from "@opentelemetry/api";
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
import { setupOtelCompat, BraintrustSpanProcessor } from "@braintrust/otel";

// Setup context manager to group span
// Set up context manager to group spans
contextManager.enable();
context.setGlobalContextManager(contextManager);

const braintrustProcessor = new BraintrustSpanProcessor({
  parent: "project_name:my-braintrust-project",
  filterAISpans: true,
});

const provider = new BasicTracerProvider({
  spanProcessors: [braintrustProcessor]
});
trace.setGlobalTracerProvider(provider);

// Call this first, before any logger or span creation
setupOtelCompat();

async function task(input: string): Promise<string> {
  const tracer = trace.getTracer("my-service");

  return await tracer.startActiveSpan("otel.task", async (span) => {
    span.setAttribute("input", input);
    const result = `Processed: ${input}`;
    span.setAttribute("output", result);
    span.end();
    return result;
  });
}

await Eval("OTEL Integration Example", {
  data: [
    { input: "test1", expected: "Processed: test1" },
    { input: "test2", expected: "Processed: test2" },
  ],
  task,
});
```

**Python**

Set `BRAINTRUST_OTEL_COMPAT=true` before importing Braintrust:

> [!NOTE]
>
> Compatibility mode requires the `braintrust[otel]` package, v0.3.0 or later. In v0.26.0 and later, the OTel-compatible ID and export format is the default, but to also get compatibility mode's context sharing, set `BRAINTRUST_OTEL_COMPAT=true`.

```python Python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import os

# Enable OTel compatibility before imports
os.environ["BRAINTRUST_OTEL_COMPAT"] = "true"
os.environ["BRAINTRUST_API_KEY"] = "<your-api-key>"

from braintrust import Eval
from braintrust.otel import BraintrustSpanProcessor
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider

# Set up OTel tracing
provider = TracerProvider()
provider.add_span_processor(BraintrustSpanProcessor(parent="project_name:my-project"))
trace.set_tracer_provider(provider)

def task(input):
    tracer = trace.get_tracer(__name__)

    # This OTel span will nest under the Braintrust eval span
    with tracer.start_as_current_span("otel.task") as span:
        span.set_attribute("input", input)
        result = f"Processed: {input}"
        span.set_attribute("output", result)
        return result

Eval(
    "OTEL Integration Example",
    data=[
        {"input": "test1", "expected": "Processed: test1"},
        {"input": "test2", "expected": "Processed: test2"},
    ],
    task=task,
)
```

## Propagate context across services

Pass the parent span's context to the receiving service so its spans join the same trace. Choose the example that matches which SDK creates the parent span.

**TypeScript**

These examples require `@braintrust/otel` v0.1.0 or later and use `fetch` to pass context in HTTP headers.

**Python**

These examples require `braintrust[otel]` v0.3.5 or later and use `requests` to pass context in HTTP headers.

> [!NOTE]
>
> When linking Python and TypeScript services, align their [ID formats](#id-and-export-format).

### Braintrust parent to OpenTelemetry child

Export the Braintrust span context and use it to create an OpenTelemetry context.

**TypeScript**

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { initLogger } from "braintrust";
import { contextFromSpanExport } from "@braintrust/otel";
import * as api from "@opentelemetry/api";

// Service A: Create Braintrust span and export context
const logger = initLogger({ projectName: "my-project" });
await logger.traced(async (span) => {
  const exported = await span.export();
  // Send to Service B via HTTP
  await fetch("https://service-b/api", {
    headers: { "x-braintrust-context": exported },
  });
});

// Service B: Receive request and create OTel span as child
const exported = req.headers.get("x-braintrust-context");
const ctx = contextFromSpanExport(exported);
await api.context.with(ctx, async () => {
  const tracer = api.trace.getTracer("service-b");
  await tracer.startActiveSpan("service_b", async (span) => {
    // This span is now a child of the Braintrust span
    span.end();
  });
});
```

**Python**

```python Python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import braintrust
from braintrust.otel import context_from_span_export
from opentelemetry import context as otel_context
from opentelemetry import trace

# Service A: Create Braintrust span and export context
project = braintrust.init_logger(project="my-project")
with project.start_span(name="service_a") as span:
    exported = span.export()
    # Send to Service B via HTTP
    import requests

    requests.post("https://service-b/api", headers={"x-braintrust-context": exported})

# Service B: Receive request and create OTel span as child
exported = request.headers.get("x-braintrust-context")
ctx = context_from_span_export(exported)
token = otel_context.attach(ctx)
try:
    tracer = trace.get_tracer(__name__)
    with tracer.start_as_current_span("service_b") as span:
        # This span is now a child of the Braintrust span
        pass
finally:
    otel_context.detach(token)
```

### OpenTelemetry parent to Braintrust child

Propagate the OpenTelemetry context using W3C Trace Context headers.

**TypeScript**

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { addSpanParentToBaggage, parentFromHeaders } from "@braintrust/otel";
import * as api from "@opentelemetry/api";

// Service A: Create OTel span and export headers
const tracer = api.trace.getTracer("service-a");
await tracer.startActiveSpan("service_a", async (span) => {
  // Add Braintrust parent to baggage for propagation
  const ctx = addSpanParentToBaggage(span);

  // Export W3C trace context headers and send to Service B
  const headers: Record<string, string> = {};
  api.propagation.inject(ctx, headers);
  await fetch("https://service-b/api", { headers });

  span.end();
});

// Service B: Receive request and create Braintrust span as child
const parent = parentFromHeaders(req.headers);
await logger.traced(
  async (span) => {
    // This span is now a child of the OTel span
  },
  { name: "service_b", parent },
);
```

**Python**

```python Python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
from braintrust.otel import add_span_parent_to_baggage, parent_from_headers
from opentelemetry import trace
from opentelemetry.propagate import inject

# Service A: Create OTel span and export headers
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("service_a") as span:
    # Add Braintrust parent to baggage for propagation
    add_span_parent_to_baggage(span)

    # Export W3C trace context headers and send to Service B
    headers = {}
    inject(headers)
    import requests

    requests.post("https://service-b/api", headers=headers)

# Service B: Receive request and create Braintrust span as child
parent = parent_from_headers(request.headers)
with project.start_span(name="service_b", parent=parent) as span:
    # This span is now a child of the OTel span
    pass
```

If you have the Braintrust parent as a string (for example, `"project_name:my-project"`) rather than a span, use `add_parent_to_baggage(parent)` (Python) instead of `add_span_parent_to_baggage(span)`.

The examples use HTTP, but you can also transmit trace context through message queue metadata, gRPC metadata, or another inter-service transport that supports context propagation.

> [accordion] ID format compatibility
>
> Braintrust can represent span and trace IDs as OTel-compatible hexadecimal values and serialize them in a shared export format, so exported spans interoperate with OpenTelemetry.
>
> > [!NOTE]
> >
> > For distributed tracing between Python and TypeScript services, make sure both services use the same format. Either set `BRAINTRUST_LEGACY_IDS=true` on the Python service, or enable compatibility mode on the TypeScript service.
>
> **TypeScript**
>
> The TypeScript SDK reads and exports only the legacy ID and export format by default. Enable compatibility mode with `setupOtelCompat()` to read either format (auto-detecting) and export the OTel-compatible format, including when propagating spans through `x-bt-parent` or other distributed-tracing channels.
>
> **Python**
>
> The Python SDK v0.3.0 and later reads either ID and export format (auto-detecting), including spans propagated through `x-bt-parent` or other distributed-tracing channels. Version v0.26.0 and later exports the OTel-compatible format by default. To export Braintrust's legacy UUID-style format instead, set `BRAINTRUST_LEGACY_IDS=true`.

**TypeScript**

> [!TIP]
>
> The TypeScript examples use the `@braintrust/otel` package to link OpenTelemetry and Braintrust spans. If you only need to propagate Braintrust trace context between TypeScript services over HTTP, the core `braintrust` package has native W3C helpers, [`injectTraceContext`](https://docs.braintrust.dev/sdks/typescript/api-reference#injecttracecontext) and [`extractTraceContextFromHeaders`](https://docs.braintrust.dev/sdks/typescript/api-reference#extracttracecontextfromheaders), that emit and read standard `traceparent` headers without the extra package.

## Resources

* [Send traces and logs](https://docs.braintrust.dev/integrations/sdk-integrations/opentelemetry/send-traces-and-logs.md).
* [Attributes and events](https://docs.braintrust.dev/integrations/sdk-integrations/opentelemetry/attributes.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.