Skip to main content
Requires Go SDK v0.17.1 or later.
Span customizers let you change span data inside your application before the Go SDK exports it to Braintrust, for example to redact sensitive values such as email addresses or credentials, or to add tags and metadata. A span customizer is a function that the SDK calls on each span after the span ends. The SDK runs customizers on every span it exports, including spans from integrations and spans you create with the OpenTelemetry API.
The Go SDK has no masking function, so span customizers are how you remove sensitive data before it leaves your application. To redact data after Braintrust receives it instead, see Protect sensitive data.

Register a customizer

To create a customizer and register it with the SDK:
  1. Write the function. It receives each completed span as an OpenTelemetry sdktrace.ReadOnlySpan and returns the span to export. In the function:
    • Read the attributes you want to change. Integrations store the span’s content as JSON strings in braintrust.input_json (input), braintrust.output_json (output), and braintrust.metadata (metadata). Braintrust parses these into the span’s fields.
    • Return a replacement span with the new attributes. ReadOnlySpan can’t be modified, so wrap the span in a struct that keeps everything the same except its attributes. The customizedSpan type in the example below does this: it embeds the original span and overrides Attributes() to return the new list. To leave a span unchanged, return it as is.
  2. Register the function. Wrap it in a config.SpanCustomizer and pass it to braintrust.New with the braintrust.WithSpanCustomizers option.
This example replaces email addresses in the input, output, and metadata of every span:
main.go
In Braintrust, the draft-reply span’s input shows {"to": "[EMAIL]"}. The customizer redacts spans from Go SDK integrations the same way.

Add tags and metadata

To add metadata or tags to a span without losing the values the integration recorded:
  1. Read the existing values. braintrust.metadata holds the span’s whole metadata object as a JSON string, and braintrust.tags holds its tags as a string slice.
  2. Merge in your values. Add your keys to the parsed metadata and your tags to the existing list. Replacing either attribute drops what was there.
  3. Return a replacement span with the merged attributes, using customizedSpan from the previous example.
This example adds an environment metadata key and a production tag:
#skip-compile
Returning an error strips the span’s content, as described in When a customizer fails. Choose the behavior that fits each customizer:
  • Customizers that add data, like this one: Return the span unchanged when you can’t process it. This example does that when the existing metadata isn’t valid JSON.
  • Customizers that redact data: Return an error when you can’t redact a span, so the SDK strips its content instead of sending unredacted data.

Chain customizers

To run more than one customizer:
  1. Write each customizer separately, so each one handles one change and you can test and reuse it on its own.
  2. Pass them to WithSpanCustomizers in the order you want them to run. Each customizer receives the span that the previous one returned. If you pass WithSpanCustomizers more than once, the SDK appends to the list.
This example runs redactEmails, then addEnvironment:
#skip-compile

How customizers run

  • Every exported span: The SDK runs customizers on every span it exports, including spans you create with the OpenTelemetry API. If you set braintrust.WithFilterAISpans(true), the SDK exports only root spans and spans whose name or attributes start with gen_ai., braintrust., llm., ai., or traceloop.. Customizers don’t run on the spans the filter drops.
  • Once per completed span: The SDK runs each customizer once per span, after the span ends, and can call it from a background goroutine. Make customizers safe for concurrent use, and don’t hold on to or change the ReadOnlySpan values they receive or return.
  • Before attachment upload: The SDK runs customizers before it converts inline base64 data in span attributes to attachments and uploads them. Customizers see the inline base64 data, not attachment references, so removing or replacing it keeps the file out of Braintrust.
  • Identity: Customizers can change anything except the trace ID, span ID, parent trace ID, and parent span ID.

When a customizer fails

Customizers fail closed for the affected span. A customizer fails when it:
  • Returns an error.
  • Panics. The SDK recovers the panic, so your application keeps running.
  • Returns nil.
  • Changes a span’s trace, span, or parent IDs.
When a customizer fails on a span, the SDK skips the remaining customizers for that span and exports a stripped record in its place:
  • Kept: The trace ID, span ID, parent span ID, start and end times, and the braintrust.parent attribute, so the record stays in the same trace and project.
  • Removed: Everything else, including the span name, input, output, metadata, tags, events, and attachments. The SDK never exports the original content as a fallback.
  • Added: context.customizer_error is set to true, so you can find stripped records in Braintrust.
Other spans in the same export batch are customized and exported as usual. A customizer failure doesn’t make the export or ForceFlush return an error, so a successful flush doesn’t mean every customizer succeeded. The SDK logs span customization failed; span content stripped at most once per process. The log message identifies the failing customizer by its position in the list but doesn’t include the error it returned or any span data. To record failure details, log them inside your customizer.

Limitations

  • Console output: Spans that braintrust.WithEnableTraceConsoleLog(true) prints aren’t customized.
  • Code only: You register customizers in code. There’s no environment variable for them.

Next steps