Skip to main content
Requires Java SDK v0.3.25 or later.
Span customizers let you change span data inside your application before the Java SDK exports it to Braintrust, for example to redact sensitive values such as email addresses, credentials, or inline file data, or to add tags and metadata. A span customizer is a class 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 Java 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 class. Implement SpanCustomizer and override onSpanExport(SpanData span). The method receives each completed span as OpenTelemetry SpanData and returns the span to export. SpanCustomizer isn’t a functional interface, so you can’t use a lambda. In the method:
    • 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. SpanData can’t be modified, so return a copy that keeps everything the same except its attributes. The withAttributes() helper in the example below does this: it wraps the span in a DelegatingSpanData and overrides getAttributes() to return the new attributes. To leave a span unchanged, return it as is.
  2. Register the class. Call addSpanCustomizer() on BraintrustConfig.Builder, then pass the config to Braintrust.get(config).
This example replaces email addresses in the input, output, and metadata of every span:
RedactEmails.java #skip-compile
In Braintrust, the draft-reply span’s input shows {"to": "[EMAIL]"}. The customizer redacts spans from Java 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 array.
  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 the withAttributes() helper from the previous example.
This example adds an environment metadata key and a production tag. It uses Jackson to parse the metadata, so add com.fasterxml.jackson.core:jackson-databind to your build’s dependencies. It also reuses the imports from the previous example:
#skip-compile
If onSpanExport() throws an exception, the SDK drops the whole export batch, as described in How customizers run. 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: Throw an exception when you can’t redact a span, so the SDK drops the batch 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. Call addSpanCustomizer() once for each, in the order you want them to run. Each customizer receives the span that the previous one returned.
This example runs RedactEmailAddresses, 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 enable BRAINTRUST_FILTER_AI_SPANS, the SDK exports only spans with an attribute that starts 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, on the background export thread.
  • Before attachment upload: The SDK runs customizers before it converts inline base64 data in span attributes to attachments and uploads them. A customizer can remove file data before it leaves your process.
  • Identity: Customizers can change anything except the trace ID, span ID, and parent span ID, and must not return null.
Customizers fail closed. The SDK drops the whole export batch, and logs Failed to customize spans for export, when a customizer:
  • Throws an exception
  • Returns null, which drops the batch, not just that span
  • Changes a span’s trace, span, or parent span ID
A batch is the group of spans the SDK exports together, so it can include spans from other traces.

Limitations

  • Code only: You register customizers in code, on BraintrustConfig. There’s no environment variable or system property for them.
  • Java agent: The Java agent configures itself only from BRAINTRUST_* environment variables, and no environment variable registers a customizer. To use customizers with the agent, also set up the SDK in code with Braintrust.get(config) before your application makes any instrumented calls. The agent’s spans then run through your customizers too.
  • One config per process: The first call to Braintrust.get() or Braintrust.get(config) in a process sets the SDK’s configuration. Later calls return that same instance and ignore the config you pass, so register every customizer in the first call.

Next steps