Module: Braintrust::Trace

Defined in:
lib/braintrust/trace.rb,
lib/braintrust/trace/attachment.rb,
lib/braintrust/trace/span_filter.rb,
lib/braintrust/trace/span_exporter.rb,
lib/braintrust/trace/span_processor.rb,
lib/braintrust/contrib/openai/deprecated.rb,
lib/braintrust/contrib/ruby_llm/deprecated.rb,
lib/braintrust/contrib/anthropic/deprecated.rb,
lib/braintrust/contrib/ruby_openai/deprecated.rb

Defined Under Namespace

Modules: AlexRudall, Anthropic, Contrib, OpenAI, SpanFilter Classes: Attachment, SpanExporter, SpanProcessor

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.exit_hook_registeredObject

Returns the value of attribute exit_hook_registered.



17
18
19
# File 'lib/braintrust/trace.rb', line 17

def exit_hook_registered
  @exit_hook_registered
end

Class Method Details

.build_filters(config) ⇒ Array<Proc>

Build filters array from config

Parameters:

  • config (Config, nil)

    Configuration object

Returns:

  • (Array<Proc>)

    Array of filter functions



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/braintrust/trace.rb', line 126

def self.build_filters(config)
  filters = []

  # Add custom filters first (they have priority)
  if config&.span_filter_funcs&.any?
    filters.concat(config.span_filter_funcs)
  end

  # Add AI filter if enabled
  if config&.filter_ai_spans
    filters << SpanFilter.method(:ai_filter)
  end

  filters
end

.enable(tracer_provider, state: nil, exporter: nil, config: nil) ⇒ Object

Raises:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/braintrust/trace.rb', line 84

def self.enable(tracer_provider, state: nil, exporter: nil, config: nil)
  state ||= Braintrust.current_state
  raise Error, "No state available" unless state

  # Get config from state if available
  config ||= state.respond_to?(:config) ? state.config : nil

  # Create OTLP HTTP exporter unless override provided
  exporter ||= SpanExporter.new(
    endpoint: "#{state.api_url}/otel/v1/traces",
    api_key: state.api_key
  )

  # Use SimpleSpanProcessor for InMemorySpanExporter (testing), BatchSpanProcessor for production
  span_processor = if exporter.is_a?(OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter)
    OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(exporter)
  else
    OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter)
  end

  # Build filters array from config
  filters = build_filters(config)

  # Wrap span processor in our custom span processor to add Braintrust attributes and filters
  processor = SpanProcessor.new(span_processor, state, filters)

  # Register with tracer provider
  tracer_provider.add_span_processor(processor)

  # Console debug if enabled
  if ENV["BRAINTRUST_ENABLE_TRACE_CONSOLE_LOG"]
    console_exporter = OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter.new
    console_processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(console_exporter)
    tracer_provider.add_span_processor(console_processor)
  end

  self
end

.flush_spansBoolean

Flush buffered spans from the global tracer provider. Forces immediate export of any spans buffered by BatchSpanProcessor.

Returns:

  • (Boolean)

    true if flush succeeded, false otherwise



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/braintrust/trace.rb', line 70

def self.flush_spans
  provider = OpenTelemetry.tracer_provider
  return false unless provider.respond_to?(:force_flush)

  Log.debug("Flushing spans")
  begin
    provider.force_flush
    true
  rescue => e
    Log.debug("Failed to flush spans: #{e.message}")
    false
  end
end

Generate a permalink URL for a span to view in the Braintrust UI Returns an empty string if the permalink cannot be generated

Parameters:

  • span (OpenTelemetry::Trace::Span)

    The span to generate a permalink for

Returns:

  • (String)

    The permalink URL, or empty string if an error occurs



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/braintrust/trace.rb', line 146

def self.permalink(span)
  return "" if span.nil?

  # Extract required attributes from span
  span_context = span.context
  trace_id = span_context.hex_trace_id
  span_id = span_context.hex_span_id

  # Get Braintrust attributes
  attributes = span.attributes if span.respond_to?(:attributes)
  unless attributes
    Log.error("Span does not support attributes")
    return ""
  end

  app_url = attributes[SpanProcessor::APP_URL_ATTR_KEY]
  org_name = attributes[SpanProcessor::ORG_ATTR_KEY]
  parent = attributes[SpanProcessor::PARENT_ATTR_KEY]

  # Validate required attributes
  unless app_url
    Log.error("Missing required attribute: #{SpanProcessor::APP_URL_ATTR_KEY}")
    return ""
  end

  unless org_name
    Log.error("Missing required attribute: #{SpanProcessor::ORG_ATTR_KEY}")
    return ""
  end

  unless parent
    Log.error("Missing required attribute: #{SpanProcessor::PARENT_ATTR_KEY}")
    return ""
  end

  # Parse parent to determine URL format
  parent_type, parent_id = parent.split(":", 2)
  unless parent_type && parent_id
    Log.error("Invalid parent format: #{parent}")
    return ""
  end

  # Build the permalink URL based on parent type
  if parent_type == "experiment_id"
    # For experiments: {app_url}/app/{org}/object?object_type=experiment&object_id={experiment_id}&r={trace_id}&s={span_id}
    "#{app_url}/app/#{org_name}/object?object_type=experiment&object_id=#{parent_id}&r=#{trace_id}&s=#{span_id}"
  else
    # For projects: {app_url}/app/{org}/p/{project}/logs?r={trace_id}&s={span_id}
    # parent_type is typically "project_name"
    "#{app_url}/app/#{org_name}/p/#{parent_id}/logs?r=#{trace_id}&s=#{span_id}"
  end
rescue => e
  Log.error("Failed to generate permalink: #{e.message}")
  ""
end

.register_exit_hookObject

Register an at_exit hook to flush spans before program exit. This ensures buffered spans in BatchSpanProcessor are exported. Only registers once, and only for the global tracer provider. Controlled by BRAINTRUST_FLUSH_ON_EXIT env var (default: true).



58
59
60
61
62
63
64
65
# File 'lib/braintrust/trace.rb', line 58

def self.register_exit_hook
  return if @exit_hook_registered
  return unless Internal::Env.flush_on_exit

  @exit_hook_registered = true
  at_exit { flush_spans }
  Log.debug("Registered at_exit hook for span flushing")
end

.setup(state, tracer_provider = nil, exporter: nil) ⇒ void

This method returns an undefined value.

Set up OpenTelemetry tracing with Braintrust

Parameters:

  • state (State)

    Braintrust state

  • tracer_provider (TracerProvider, nil) (defaults to: nil)

    Optional tracer provider

  • exporter (Exporter, nil) (defaults to: nil)

    Optional exporter override (for testing)



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/braintrust/trace.rb', line 25

def self.setup(state, tracer_provider = nil, exporter: nil)
  if tracer_provider
    # Use the explicitly provided tracer provider
    # DO NOT set as global - user is managing it themselves
    Log.debug("Using explicitly provided OpenTelemetry tracer provider")
  else
    # Check if global tracer provider is already a real TracerProvider
    current_provider = OpenTelemetry.tracer_provider

    if current_provider.is_a?(OpenTelemetry::SDK::Trace::TracerProvider)
      # Use existing provider
      Log.debug("Using existing OpenTelemetry tracer provider")
      tracer_provider = current_provider
    else
      # Create new provider and set as global
      tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new
      OpenTelemetry.tracer_provider = tracer_provider
      Log.debug("Created OpenTelemetry tracer provider")
    end

    # Register at_exit hook for global provider (only once)
    register_exit_hook
  end

  # Enable Braintrust tracing (adds span processor)
  config = state.config
  enable(tracer_provider, state: state, config: config, exporter: exporter)
end