Skip to main content
Requires Ruby SDK v0.6.0 or later.
Span customizers let you change span data inside your application before the Ruby 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 an object with an on_span_export method 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 Ruby 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. Define an on_span_export(span) method. It receives each completed span and returns the span to export. 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.
    • Change the span and return it. span.attributes is a writable hash, so you can replace or delete entries in place. You can also change other fields, such as span.name. To leave a span unchanged, return it as is.
  2. Register an instance. Pass it in the span_customizers: array of Braintrust.init.
This example replaces email addresses in the input, output, and metadata of every span:
In Braintrust, the draft-reply span’s input shows {"to": "[EMAIL]"}. The customizer redacts spans from Ruby SDK integrations the same way, as long as you require the integration’s gem before you call Braintrust.init.
Don’t combine customizers with require "braintrust/setup", gem "braintrust", require: "braintrust/setup", or braintrust exec. Each of these calls Braintrust.init without customizers, so a later Braintrust.init(span_customizers: ...) adds a second export pipeline, and the first one still sends every span unredacted. To use customizers, add gem "braintrust" to your Gemfile, require your provider gems, and then call Braintrust.init(span_customizers: ...) yourself.
Braintrust.init auto-instruments the provider gems that are already loaded when you call it.

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 an array of strings.
  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. Write the merged values back to span.attributes and return the span.
This example adds an environment metadata key and a production tag:
If on_span_export raises an error, 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: Raise an error 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. List them in span_customizers: in the order you want them to run. Each customizer receives the span that the previous one returned.
This example runs RedactEmails, then AddEnvironment:

How customizers run

  • Every exported span: The SDK runs customizers on every span it exports, including spans from integrations, evaluations, and spans you create with the OpenTelemetry API. If you set filter_ai_spans: true or BRAINTRUST_OTEL_FILTER_AI_SPANS=true, the SDK exports only spans whose name or attributes start with gen_ai., braintrust., llm., ai., or traceloop.. Customizers don’t run on the spans that this filter or your span_filter_funcs drop.
  • Once per completed span: The SDK runs each customizer once per span, after the span ends, on the background export thread. Keep customizers fast, because slow customizers delay exports. Spans that your customizer creates, for example by calling an instrumented client, aren’t traced.
  • Identity: Customizers can change anything except the trace ID, span ID, and parent span ID, which are read-only. To return a different span instead, build it from span.to_span_data.dup so it keeps the same IDs, and return that OpenTelemetry::SDK::Trace::SpanData.
Customizers fail closed. The SDK drops the whole export batch, and logs Span customization failed; batch not sent, when a customizer:
  • Raises an error
  • Returns nil or anything other than the span or an OpenTelemetry::SDK::Trace::SpanData, 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. The logged error names the customizer’s class and the error class, but not the error message, which can contain span data. To record failure details, log them inside your customizer.

Limitations

  • Code only: You register customizers in code, with Braintrust.init. There’s no environment variable for them, and braintrust/setup and braintrust exec can’t register them.
  • Objects, not blocks: Each entry in span_customizers: must respond to on_span_export. Procs and lambdas don’t, so Braintrust.init raises an ArgumentError for them, and for any other object without the method.
  • No custom exporter: Customizers run in the SDK’s exporter. If you pass both span_customizers: and exporter: to Braintrust.init, it raises an ArgumentError.
  • Fixed at initialization: Braintrust.init copies the span_customizers: array, so later changes to the array don’t apply.
  • Console output: Spans that BRAINTRUST_ENABLE_TRACE_CONSOLE_LOG prints aren’t customized.

Next steps