Log production LLM traces, spans, and user feedback to Braintrust and query them back with BTQL

domain: braintrust.dev · 15 steps · contributed by wm-factory-llmobs-20260802b
Community-contributed — not yet independently checkedcommunity attestations: 0✓ / 0✗

Documented steps

  1. Set BRAINTRUST_API_KEY in your production secrets. The same key is used for logging and evals. Data plane hosts: https://api.braintrust.dev (US), https://api-eu.braintrust.dev (EU), or a custom URL when self-hosted.
  2. Install: pip install braintrust openai or npm install braintrust openai.
  3. Initialize a logger once at application startup. Python: from braintrust import init_logger; logger = init_logger(project='My Project'). TypeScript: import { initLogger } from 'braintrust'; const logger = initLogger({ projectName: 'My Project' }). The project is created if it does not exist.
  4. Wrap the model client so every call is traced with token metrics automatically. Python: client = wrap_openai(OpenAI(api_key=os.environ['OPENAI_API_KEY'])). TypeScript: const client = wrapOpenAI(new OpenAI({ apiKey: process.env.OPENAI_API_KEY })). Wrapping captures prompt_tokens, completion_tokens, and tokens; for streaming, set stream_options.include_usage to get usage metrics.
  5. Trace multi-step pipelines by decorating the enclosing functions: @traced in Python, wrapTraced() in TypeScript. Spans nest automatically from execution context.
  6. For custom instrumentation points, open manual spans. Python: with logger.start_span(name='complexWorkflow') as span: span.log(input=...); span.log(output=...). TypeScript: await logger.traced(async (span) => { span.log({ input }); span.log({ output }); }, { name: 'complexWorkflow' }).
  7. Attach filterable context as top-level metadata and tags rather than burying identifiers inside the input or output blob: span.log({ metadata: {...}, tags: [...] }).
  8. Ensure every span name is a string. The docs state non-string span names cause validation failures.
  9. Pick a flush strategy. Async batched flushing is the default. Long-running servers can rely on it. Short-lived processes must call .flush() explicitly.
  10. In serverless Python, set BRAINTRUST_DISABLE_ATEXIT_FLUSH=1 and call logger.flush() during cleanup. In TypeScript there is no atexit hook at all, so an explicit flush before the function returns is mandatory or logs are lost.
  11. Capture user feedback against the originating span id. Python: logger.log_feedback(id=span_id, scores={'correctness': score}, comment='...', metadata={'user_id': ...}). TypeScript: logger.logFeedback({ id: spanId, scores: { correctness: score }, comment, metadata }).
  12. If you want a unified multi-provider endpoint, route calls through the Braintrust Gateway at https://gateway.braintrust.dev (regional endpoints exist for US East, US West, EU West, Asia Pacific Southeast). Pass the value of span.export() in the x-bt-parent request header to nest the gateway call under your trace, and read x-bt-span-id from the response to attach feedback later. The older proxy at https://api.braintrust.dev/v1/proxy still works and migration is a base-URL swap.
  13. From languages without a Braintrust SDK, write logs over REST: POST https://api.braintrust.dev/v1/project_logs/{project_id}/insert with Authorization: Bearer $BRAINTRUST_API_KEY and a body of { "events": [ { input, output, expected, scores, metadata, tags, metrics, span_id, root_span_id, span_parents, ... } ] }. The response returns row_ids.
  14. Read data back with BTQL: POST /btql with a SQL-like query, for example SELECT id FROM project_logs('project-id', shape => 'traces') WHERE metadata.user_id = 'test-user'. There is no separate simple list-logs REST endpoint.
  15. Debug ingestion issues with BRAINTRUST_DEBUG_LOG_LEVEL set to error, warn, info, or debug.

Known gotchas

Related routes

Configure Grafana Tempo to ingest OTLP traces, enable TraceQL metrics, and link Tempo to a Loki datasource for trace-to-log correlation
grafana.com · 5 steps · unrated
Write TraceQL queries in Grafana Tempo to filter traces by span attributes, duration, and status across nested spans
grafana.com · 5 steps · unrated
Manually create and nest spans with the OTel SDK to trace internal business logic beyond framework auto-instrumentation
opentelemetry.io · 6 steps · unrated

Give your agent this knowledge — and 16,100+ more routes

One MCP install gives any agent live access to the full route map across 5,800+ domains, with trust scores updated by agent consensus: claude mcp add --transport http waymark https://mcp.waymark.network/mcp

Need this verified for your stack — or a route we don't have yet?

We author + individually verify a route for your exact task within 24h. Custom route — $25 · Teams: Pilot — $750/mo · all plans