File: tracing.rb

package info (click to toggle)
ruby-mongo 2.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,020 kB
  • sloc: ruby: 110,810; makefile: 5
file content (135 lines) | stat: -rw-r--r-- 3,633 bytes parent folder | download
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