> ## Documentation Index
> Fetch the complete documentation index at: https://braintrust.dev/docs/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](/docs/integrations/sdk-integrations/opentelemetry/send-traces-and-logs) 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.

<View title="TypeScript" icon="/images/sdk-icons/typescript.svg">
  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>

  ```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,
  });
  ```
</View>

<View title="Python" icon="/images/sdk-icons/python.svg">
  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`.
  </Note>

  ```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,
  )
  ```
</View>

## 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.

<View title="TypeScript" icon="/images/sdk-icons/typescript.svg">
  These examples require `@braintrust/otel` v0.1.0 or later and use `fetch` to pass context in HTTP headers.
</View>

<View title="Python" icon="/images/sdk-icons/python.svg">
  These examples require `braintrust[otel]` v0.3.5 or later and use `requests` to pass context in HTTP headers.
</View>

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

### Braintrust parent to OpenTelemetry child

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

<View title="TypeScript" icon="/images/sdk-icons/typescript.svg">
  <CodeGroup>
    ```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();
      });
    });
    ```
  </CodeGroup>
</View>

<View title="Python" icon="/images/sdk-icons/python.svg">
  <CodeGroup>
    ```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)
    ```
  </CodeGroup>
</View>

### OpenTelemetry parent to Braintrust child

Propagate the OpenTelemetry context using W3C Trace Context headers.

<View title="TypeScript" icon="/images/sdk-icons/typescript.svg">
  <CodeGroup>
    ```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 },
    );
    ```
  </CodeGroup>
</View>

<View title="Python" icon="/images/sdk-icons/python.svg">
  <CodeGroup>
    ```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
    ```
  </CodeGroup>

  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)`.
</View>

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.

<span id="id-and-export-format" />

<Accordion title="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.
  </Note>

  <View title="TypeScript" icon="/images/sdk-icons/typescript.svg">
    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.
  </View>

  <View title="Python" icon="/images/sdk-icons/python.svg">
    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`.
  </View>
</Accordion>

<View title="TypeScript" icon="/images/sdk-icons/typescript.svg">
  <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`](/docs/sdks/typescript/api-reference#injecttracecontext) and [`extractTraceContextFromHeaders`](/docs/sdks/typescript/api-reference#extracttracecontextfromheaders), that emit and read standard `traceparent` headers without the extra package.
  </Tip>
</View>

## Resources

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