Skip to content

feat: Add otel compatible distributed tracing#2141

Merged
Andrew Kent (realark) merged 10 commits into
mainfrom
ark/distributed-tracing
Jul 6, 2026
Merged

feat: Add otel compatible distributed tracing#2141
Andrew Kent (realark) merged 10 commits into
mainfrom
ark/distributed-tracing

Conversation

@realark

@realark Andrew Kent (realark) commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

implements: https://github.com/braintrustdata/braintrust-spec/blob/main/docs/features/distributed-tracing.md

OLD WAY: https://www.braintrust.dev/docs/instrument/advanced-tracing#trace-distributed-systems

import { currentSpan, initLogger, startSpan, traced } from "braintrust";

const logger = initLogger({ projectName: "my-project" });

// Client: Export the span
const processRequest = (request: Request) =>
  traced(
    async (span) =>
      fetch("/api/process", {
        method: "POST",
        body: JSON.stringify(request),
        headers: { "X-Trace-ID": await currentSpan().export() },
      }),
  );

// Server: Resume the trace
async function handleRequest(req: Request) {
  const traceId = req.headers.get("X-Trace-ID") ?? undefined;

  return traced(
    async (span) => {
      const result = await processData(req.body);
      span.log({ input: req.body, output: result });
      return result;
    },
    { parent: traceId },
  );
}

NOTE: the "old way" is still supported and backwards-compatible. Native SDKs can continue to link to each other in the manner, even if one is upgraded and the other is not. However if users with to link to otel sdks, they will have to change their code to use the new apis.

NEW WAY:

import {
  currentSpan,
  extractTraceContext,
  initLogger,
  startSpan,
  traced,
} from "braintrust";

const logger = initLogger({ projectName: "my-project" });

// Client: Export the span
const processRequest = (request: Request) =>
  traced(
    async (span) =>
      fetch("/api/process", {
        method: "POST",
        body: JSON.stringify(request),
        headers: currentSpan().inject({}), // <--- new inject api
      }),
  );

// Server: Resume the trace
async function handleRequest(req: Request) {
  return traced(
    async (span) => {
      const result = await processData(req.body);
      span.log({ input: req.body, output: result });
      return result;
    },
    { parent: extractTraceContext(req.headers) }, // <--- new extract api
  );
}

Other notes

  • confusing naming: this has the unfortunate effect of the root_span_id field essentially becoming the trace id rather than the id of the root span (i.e. root_span_id != rootSpan.id). This sucks, but it's already the case for otel sdks and py otel compat mode. So at least we're consistent.
  • legacy id flag: the sdk currently maintains two paths for id generation. it defaults to UUIDs and does hex IDs when we're in in otel compat mode. This changes the default to use hex IDs and has a flag to revert to the old behavior (BRAINTRUST_LEGACY_IDS). We could just rip out the old uuid logic entirely, but this approach seems like the least risky
  • to support passing through tracestate and flags, an additional field was added to SpanImpl (propagatedState)

examples

@realark Andrew Kent (realark) force-pushed the ark/distributed-tracing branch 5 times, most recently from 07d3832 to beb4968 Compare June 26, 2026 05:30
@realark Andrew Kent (realark) marked this pull request as ready for review June 26, 2026 05:30
Comment thread js/src/propagation.ts Outdated
* reduced to their first element, since these headers are single-valued.
*/
export function getHeader(
headers: Record<string, unknown> | null | undefined,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should also try to parse https://developer.mozilla.org/en-US/docs/Web/API/Headers here (we can duck type it).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Luca Forstner (@lforst) took care of this (thanks Luca!)

Comment thread js/src/propagation.ts
}
}

const members = btMember !== undefined ? [...relayed, btMember] : relayed;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like btMember is always added. Shouldn't we check to make sure it is not too large?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Luca Forstner (@lforst) took care of this (thanks Luca!)

Comment thread js/src/logger.ts
@lforst Luca Forstner (lforst) changed the title otel compatible distributed tracing feat: Add otel compatible distributed tracing Jul 2, 2026
@realark Andrew Kent (realark) force-pushed the ark/distributed-tracing branch 2 times, most recently from 1a846a0 to 2a945a4 Compare July 6, 2026 05:18
@realark Andrew Kent (realark) merged commit b061286 into main Jul 6, 2026
48 checks passed
@realark Andrew Kent (realark) deleted the ark/distributed-tracing branch July 6, 2026 20:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants