Class: ActiveAgent::Telemetry::Span

Inherits:
Object
  • Object
show all
Defined in:
lib/active_agent/telemetry/span.rb

Overview

Represents a single span in a trace.

Spans capture discrete operations within a trace, such as LLM calls, tool invocations, or prompt rendering. Each span has timing, attributes, and can have child spans.

Examples:

Creating a span

span = Span.new("llm.generate", trace_id: trace.trace_id)
span.set_attribute("provider", "anthropic")
span.set_attribute("model", "claude-3-5-sonnet")
span.set_tokens(input: 100, output: 50)
span.finish

Constant Summary collapse

TYPES =

Span types for categorization

{
  root: "root",           # Root span for entire generation
  prompt: "prompt",       # Prompt preparation/rendering
  llm: "llm",             # LLM API call
  tool: "tool",           # Tool invocation
  thinking: "thinking",   # Extended thinking (Anthropic)
  embedding: "embedding", # Embedding generation
  error: "error"          # Error handling
}.freeze
STATUS =

Span status codes

{
  unset: "UNSET",
  ok: "OK",
  error: "ERROR"
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, trace_id:, parent_span_id: nil, span_type: :root, **attributes) ⇒ Span

Creates a new span.

Parameters:

  • name (String)

    Span name

  • trace_id (String)

    Parent trace ID

  • parent_span_id (String, nil) (defaults to: nil)

    Parent span ID

  • span_type (Symbol) (defaults to: :root)

    Type of span

  • attributes (Hash)

    Initial attributes



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/active_agent/telemetry/span.rb', line 80

def initialize(name, trace_id:, parent_span_id: nil, span_type: :root, **attributes)
  @span_id = SecureRandom.hex(8)
  @trace_id = trace_id
  @parent_span_id = parent_span_id
  @name = name
  @span_type = TYPES[span_type] || span_type.to_s
  @start_time = Time.current
  @end_time = nil
  @attributes = attributes.transform_keys(&:to_s)
  @children = []
  @status = STATUS[:unset]
  @status_message = nil
  @events = []
  @tokens = { input: 0, output: 0, thinking: 0, total: 0 }
end

Instance Attribute Details

#attributesHash (readonly)

Returns Span attributes.

Returns:

  • (Hash)

    Span attributes



59
60
61
# File 'lib/active_agent/telemetry/span.rb', line 59

def attributes
  @attributes
end

#childrenArray<Span> (readonly)

Returns Child spans.

Returns:

  • (Array<Span>)

    Child spans



62
63
64
# File 'lib/active_agent/telemetry/span.rb', line 62

def children
  @children
end

#end_timeTime? (readonly)

Returns When the span ended.

Returns:

  • (Time, nil)

    When the span ended



56
57
58
# File 'lib/active_agent/telemetry/span.rb', line 56

def end_time
  @end_time
end

#eventsArray<Hash> (readonly)

Returns Events recorded during the span.

Returns:

  • (Array<Hash>)

    Events recorded during the span



71
72
73
# File 'lib/active_agent/telemetry/span.rb', line 71

def events
  @events
end

#nameString (readonly)

Returns Span name (e.g., “llm.generate”, “tool.get_weather”).

Returns:

  • (String)

    Span name (e.g., “llm.generate”, “tool.get_weather”)



47
48
49
# File 'lib/active_agent/telemetry/span.rb', line 47

def name
  @name
end

#parent_span_idString? (readonly)

Returns Parent span ID.

Returns:

  • (String, nil)

    Parent span ID



44
45
46
# File 'lib/active_agent/telemetry/span.rb', line 44

def parent_span_id
  @parent_span_id
end

#span_idString (readonly)

Returns Unique identifier for this span.

Returns:

  • (String)

    Unique identifier for this span



38
39
40
# File 'lib/active_agent/telemetry/span.rb', line 38

def span_id
  @span_id
end

#span_typeString (readonly)

Returns Span type from TYPES.

Returns:

  • (String)

    Span type from TYPES



50
51
52
# File 'lib/active_agent/telemetry/span.rb', line 50

def span_type
  @span_type
end

#start_timeTime (readonly)

Returns When the span started.

Returns:

  • (Time)

    When the span started



53
54
55
# File 'lib/active_agent/telemetry/span.rb', line 53

def start_time
  @start_time
end

#statusString (readonly)

Returns Status code from STATUS.

Returns:

  • (String)

    Status code from STATUS



65
66
67
# File 'lib/active_agent/telemetry/span.rb', line 65

def status
  @status
end

#status_messageString? (readonly)

Returns Status message.

Returns:

  • (String, nil)

    Status message



68
69
70
# File 'lib/active_agent/telemetry/span.rb', line 68

def status_message
  @status_message
end

#trace_idString (readonly)

Returns Trace ID this span belongs to.

Returns:

  • (String)

    Trace ID this span belongs to



41
42
43
# File 'lib/active_agent/telemetry/span.rb', line 41

def trace_id
  @trace_id
end

Instance Method Details

#add_event(name, attributes = {}) ⇒ self

Adds an event to the span.

Parameters:

  • name (String)

    Event name

  • attributes (Hash) (defaults to: {})

    Event attributes

Returns:

  • (self)


195
196
197
198
199
200
201
202
# File 'lib/active_agent/telemetry/span.rb', line 195

def add_event(name, attributes = {})
  @events << {
    name: name,
    timestamp: Time.current.iso8601(6),
    attributes: attributes.transform_keys(&:to_s)
  }
  self
end

#add_span(name, span_type: :root, **attributes) ⇒ Span

Creates a child span.

Parameters:

  • name (String)

    Child span name

  • span_type (Symbol) (defaults to: :root)

    Type of span

  • attributes (Hash)

    Span attributes

Returns:

  • (Span)

    The child span



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/active_agent/telemetry/span.rb', line 102

def add_span(name, span_type: :root, **attributes)
  child = Span.new(
    name,
    trace_id: trace_id,
    parent_span_id: span_id,
    span_type: span_type,
    **attributes
  )
  @children << child
  child
end

#duration_msFloat?

Returns the duration in milliseconds.

Returns:

  • (Float, nil)

    Duration or nil if not finished



223
224
225
226
227
# File 'lib/active_agent/telemetry/span.rb', line 223

def duration_ms
  return nil unless finished?

  ((@end_time - @start_time) * 1000).round(2)
end

#finishself

Marks the span as finished.

Returns:

  • (self)


207
208
209
210
211
# File 'lib/active_agent/telemetry/span.rb', line 207

def finish
  @end_time = Time.current
  set_status(:ok) if @status == STATUS[:unset]
  self
end

#finished?Boolean

Returns whether the span is finished.

Returns:

  • (Boolean)


216
217
218
# File 'lib/active_agent/telemetry/span.rb', line 216

def finished?
  !@end_time.nil?
end

#measure { ... } ⇒ Object

Executes a block and records timing/errors.

Yields:

  • Block to execute within the span

Returns:

  • (Object)

    Result of the block



255
256
257
258
259
260
261
262
263
264
# File 'lib/active_agent/telemetry/span.rb', line 255

def measure
  result = yield
  set_status(:ok)
  result
rescue StandardError => e
  record_error(e)
  raise
ensure
  finish
end

#record_error(error) ⇒ self

Records an error on the span.

Parameters:

  • error (Exception)

    The error to record

Returns:

  • (self)


175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/active_agent/telemetry/span.rb', line 175

def record_error(error)
  set_status(:error, error.message)
  set_attribute("error.type", error.class.name)
  set_attribute("error.message", error.message)
  set_attribute("error.backtrace", error.backtrace&.first(10)&.join("\n"))

  add_event("exception", {
    "exception.type" => error.class.name,
    "exception.message" => error.message,
    "exception.stacktrace" => error.backtrace&.join("\n")
  })

  self
end

#set_attribute(key, value) ⇒ self

Sets a single attribute.

Parameters:

  • key (String, Symbol)

    Attribute key

  • value (Object)

    Attribute value

Returns:

  • (self)


119
120
121
122
# File 'lib/active_agent/telemetry/span.rb', line 119

def set_attribute(key, value)
  @attributes[key.to_s] = value
  self
end

#set_attributes(attrs) ⇒ self

Sets multiple attributes at once.

Parameters:

  • attrs (Hash)

    Attributes to set

Returns:

  • (self)


128
129
130
131
# File 'lib/active_agent/telemetry/span.rb', line 128

def set_attributes(attrs)
  attrs.each { |k, v| set_attribute(k, v) }
  self
end

#set_status(code, message = nil) ⇒ self

Sets the span status.

Parameters:

  • code (Symbol)

    Status code (:ok, :error, :unset)

  • message (String, nil) (defaults to: nil)

    Optional status message

Returns:

  • (self)


165
166
167
168
169
# File 'lib/active_agent/telemetry/span.rb', line 165

def set_status(code, message = nil)
  @status = STATUS[code] || STATUS[:unset]
  @status_message = message
  self
end

#set_tokens(input: 0, output: 0, thinking: 0) ⇒ self

Sets token usage for LLM spans.

Parameters:

  • input (Integer) (defaults to: 0)

    Input token count

  • output (Integer) (defaults to: 0)

    Output token count

  • thinking (Integer) (defaults to: 0)

    Thinking token count (Anthropic extended thinking)

Returns:

  • (self)


139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/active_agent/telemetry/span.rb', line 139

def set_tokens(input: 0, output: 0, thinking: 0)
  @tokens = {
    input: input,
    output: output,
    thinking: thinking,
    total: input + output + thinking
  }
  set_attribute("tokens.input", input)
  set_attribute("tokens.output", output)
  set_attribute("tokens.thinking", thinking) if thinking > 0
  set_attribute("tokens.total", @tokens[:total])
  self
end

#to_hHash

Serializes the span for transmission.

Returns:

  • (Hash)

    Serialized span data



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/active_agent/telemetry/span.rb', line 232

def to_h
  {
    span_id: span_id,
    trace_id: trace_id,
    parent_span_id: parent_span_id,
    name: name,
    type: span_type,
    start_time: start_time.iso8601(6),
    end_time: end_time&.iso8601(6),
    duration_ms: duration_ms,
    status: status,
    status_message: status_message,
    attributes: attributes,
    tokens: tokens,
    events: events,
    children: children.map(&:to_h)
  }
end

#tokensHash

Returns token usage.

Returns:

  • (Hash)

    Token counts



156
157
158
# File 'lib/active_agent/telemetry/span.rb', line 156

def tokens
  @tokens.dup
end