1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 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 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
# frozen_string_literal: true
module Tracing
Error = Class.new(StandardError)
class Span
attr_reader :tracer, :name, :attributes, :with_parent, :kind, :finished, :nested
attr_accessor :status
def initialize(tracer, name, attributes = {}, with_parent: nil, kind: :internal)
@tracer = tracer
@name = name
@attributes = attributes
@with_parent = with_parent
@kind = kind
@finished = false
@nested = []
end
def set_attribute(key, value)
@attributes[key] = value
end
# rubocop:disable Lint/UnusedMethodArgument
def record_exception(exception, attributes: nil)
set_attribute('exception.type', exception.class.to_s)
set_attribute('exception.message', exception.message)
set_attribute(
'exception.stacktrace',
exception.full_message(highlight: false, order: :top).encode('UTF-8', invalid: :replace, undef: :replace,
replace: '�')
)
end
# rubocop:enable Lint/UnusedMethodArgument
def finish
raise Tracing::Error, 'Span already finished' if @finished
@finished = true
tracer.finish_span(self)
end
def inspect
"#<Tracing::Span name=#{@name.inspect} attributes=#{@attributes.inspect}>"
end
end
# Mock OpenTelemetry::Context to store and retrieve spans
class Context
attr_reader :span
def initialize(span)
@span = span
end
end
class Tracer
attr_reader :spans
def initialize
@spans = []
@stack = []
@active_context = nil
end
def start_span(name, attributes: {}, with_parent: nil, kind: :internal)
parent = resolve_parent(with_parent)
Span.new(self, name, attributes, with_parent: parent, kind: kind).tap do |span|
@spans << span
@stack << span
end
end
def finish_span(span)
raise Error, 'Span not found' unless @spans.include?(span)
@stack.pop if @stack.last == span
end
def span_hierarchy
# Build a mapping of all spans by their object_id for quick lookup
span_map = {}.compare_by_identity
@spans.each do |span|
span_map[span] = span
end
# Build the hierarchy by attaching children to their parents
root_spans = []
@spans.each do |span|
if span.with_parent.nil?
# This is a root span
root_spans << span
else
# Find the parent span and add this span to its nested array
parent = span_map[span.with_parent]
unless parent
raise Error, "Parent span not found for span #{span.name} (parent object_id: #{span.with_parent.object_id})"
end
parent.nested << span
end
end
root_spans
end
private
# Resolve the parent span from various input types
def resolve_parent(with_parent)
return @stack.last if with_parent.nil?
case with_parent
when Tracing::Context
# Extract span from our mock Context
with_parent.span
when OpenTelemetry::Context
# Extract span from OpenTelemetry::Context
# The OpenTelemetry context stores the span using a specific key
# We need to extract it using the OpenTelemetry::Trace API
begin
OpenTelemetry::Trace.current_span(with_parent)
rescue StandardError
# Fallback: try to extract from instance variables
with_parent.instance_variable_get(:@entries)&.values&.first
end
else
with_parent
end
end
end
end
|