Class: ActiveAgents::Telemetry::Span

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

Overview

A single operation inside a trace, serialized to the /v1/traces wire format. Adapters build these rather than hand-rolling hashes, so a span from the RubyLLM adapter and a span from any future adapter are shaped identically on the wire.

Constant Summary collapse

TYPES =
%w[root prompt llm tool thinking embedding error].freeze
OK =
"OK"
ERROR =
"ERROR"
UNSET =
"UNSET"
ZERO_TOKENS =
{ "input" => 0, "output" => 0, "thinking" => 0, "total" => 0 }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, type:, trace_id: nil, parent_span_id: nil, span_id: nil, attributes: {}, start_time: nil) ⇒ Span

Returns a new instance of Span.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/activeagents/telemetry/span.rb', line 23

def initialize(name, type:, trace_id: nil, parent_span_id: nil, span_id: nil, attributes: {}, start_time: nil)
  @span_id = span_id || SecureRandom.hex(8)
  @trace_id = trace_id
  @parent_span_id = parent_span_id
  @name = name.to_s
  @type = type.to_s
  @attributes = stringify(attributes)
  @start_time = start_time || Time.now
  @end_time = nil
  @status = UNSET
  @status_message = nil
  @tokens = ZERO_TOKENS.dup
  @events = []
  @children = []
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



20
21
22
# File 'lib/activeagents/telemetry/span.rb', line 20

def attributes
  @attributes
end

#childrenObject (readonly)

Returns the value of attribute children.



20
21
22
# File 'lib/activeagents/telemetry/span.rb', line 20

def children
  @children
end

#end_timeObject

Returns the value of attribute end_time.



21
22
23
# File 'lib/activeagents/telemetry/span.rb', line 21

def end_time
  @end_time
end

#eventsObject (readonly)

Returns the value of attribute events.



20
21
22
# File 'lib/activeagents/telemetry/span.rb', line 20

def events
  @events
end

#nameObject (readonly)

Returns the value of attribute name.



20
21
22
# File 'lib/activeagents/telemetry/span.rb', line 20

def name
  @name
end

#parent_span_idObject

Returns the value of attribute parent_span_id.



21
22
23
# File 'lib/activeagents/telemetry/span.rb', line 21

def parent_span_id
  @parent_span_id
end

#span_idObject (readonly)

Returns the value of attribute span_id.



20
21
22
# File 'lib/activeagents/telemetry/span.rb', line 20

def span_id
  @span_id
end

#start_timeObject

Returns the value of attribute start_time.



21
22
23
# File 'lib/activeagents/telemetry/span.rb', line 21

def start_time
  @start_time
end

#statusObject

Returns the value of attribute status.



21
22
23
# File 'lib/activeagents/telemetry/span.rb', line 21

def status
  @status
end

#status_messageObject (readonly)

Returns the value of attribute status_message.



20
21
22
# File 'lib/activeagents/telemetry/span.rb', line 20

def status_message
  @status_message
end

#trace_idObject

Returns the value of attribute trace_id.



39
40
41
# File 'lib/activeagents/telemetry/span.rb', line 39

def trace_id
  @trace_id
end

#typeObject (readonly)

Returns the value of attribute type.



20
21
22
# File 'lib/activeagents/telemetry/span.rb', line 20

def type
  @type
end

Instance Method Details

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



89
90
91
92
# File 'lib/activeagents/telemetry/span.rb', line 89

def add_event(name, attributes = {})
  @events << { "name" => name.to_s, "timestamp" => iso8601(Time.now), "attributes" => stringify(attributes) }
  self
end

#add_span(name, type: "root", **options) ⇒ Object

Builds a child span sharing this span's trace. Instrumentation that holds only a span — not the enclosing trace — nests through this; Trace#to_h flattens the tree back out for the wire.



51
52
53
54
55
# File 'lib/activeagents/telemetry/span.rb', line 51

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

#add_tokens(other) ⇒ Object

Adds another span's token counts to this one — how a turn-level llm span accumulates the rounds it covers.



83
84
85
86
87
# File 'lib/activeagents/telemetry/span.rb', line 83

def add_tokens(other)
  counts = other.respond_to?(:tokens) ? other.tokens : other
  @tokens = @tokens.merge(stringify(counts)) { |_key, carried, added| carried.to_i + added.to_i }
  self
end

#duration_msObject



121
122
123
124
125
# File 'lib/activeagents/telemetry/span.rb', line 121

def duration_ms
  return nil unless finished?

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

#finish(at: nil) ⇒ Object



111
112
113
114
115
# File 'lib/activeagents/telemetry/span.rb', line 111

def finish(at: nil)
  @end_time = at || Time.now
  @status = OK if @status == UNSET
  self
end

#finished?Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/activeagents/telemetry/span.rb', line 117

def finished?
  !@end_time.nil?
end

#measureObject

Runs the block, recording its outcome and timing on the span.



146
147
148
149
150
151
152
153
154
155
# File 'lib/activeagents/telemetry/span.rb', line 146

def measure
  result = yield(self)
  set_status(:ok) if @status == UNSET
  result
rescue StandardError => e
  record_error(e)
  raise
ensure
  finish
end

#record_error(error, message_limit: 200) ⇒ Object

Records an error without ever putting a backtrace or an untruncated message on the wire — telemetry payloads leave the app's trust boundary.



103
104
105
106
107
108
109
# File 'lib/activeagents/telemetry/span.rb', line 103

def record_error(error, message_limit: 200)
  @status = ERROR
  @status_message = truncate(error.message.to_s, message_limit)
  set_attribute("error.type", error.class.name)
  set_attribute("error.message", truncate(error.message.to_s, message_limit))
  self
end

#set_attribute(key, value) ⇒ Object



57
58
59
60
# File 'lib/activeagents/telemetry/span.rb', line 57

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

#set_attributes(attrs) ⇒ Object



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

def set_attributes(attrs)
  @attributes.merge!(stringify(attrs))
  self
end

#set_status(code, message = nil) ⇒ Object

Parameters:

  • code (Symbol, String)

    :ok, :error, or :unset



95
96
97
98
99
# File 'lib/activeagents/telemetry/span.rb', line 95

def set_status(code, message = nil)
  @status = { ok: OK, error: ERROR, unset: UNSET }.fetch(code.to_s.downcase.to_sym, UNSET)
  @status_message = message
  self
end

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



67
68
69
70
71
72
73
74
75
# File 'lib/activeagents/telemetry/span.rb', line 67

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

#to_hObject



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/activeagents/telemetry/span.rb', line 127

def to_h
  {
    "span_id" => span_id,
    "trace_id" => trace_id,
    "parent_span_id" => parent_span_id,
    "name" => name,
    "type" => type,
    "start_time" => iso8601(start_time),
    "end_time" => end_time ? iso8601(end_time) : nil,
    "duration_ms" => duration_ms,
    "status" => status,
    "status_message" => status_message,
    "attributes" => attributes,
    "tokens" => tokens,
    "events" => events
  }
end

#tokensObject



77
78
79
# File 'lib/activeagents/telemetry/span.rb', line 77

def tokens
  @tokens.dup
end