File: tracer.rb

package info (click to toggle)
ruby-jaeger-client 1.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 624 kB
  • sloc: ruby: 3,381; makefile: 6; sh: 4
file content (214 lines) | stat: -rw-r--r-- 7,696 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
136
137
138
139
140
141
142
143
144
145
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# frozen_string_literal: true

module Jaeger
  class Tracer
    def initialize(reporter:, sampler:, injectors:, extractors:)
      @reporter = reporter
      @sampler = sampler
      @injectors = injectors
      @extractors = extractors
      @scope_manager = ScopeManager.new
    end

    # @return [ScopeManager] the current ScopeManager, which may be a no-op
    #   but may not be nil.
    attr_reader :scope_manager

    # @return [Span, nil] the active span. This is a shorthand for
    #   `scope_manager.active.span`, and nil will be returned if
    #   Scope#active is nil.
    def active_span
      scope = scope_manager.active
      scope&.span
    end

    # Starts a new span.
    #
    # This is similar to #start_active_span, but the returned Span will not
    # be registered via the ScopeManager.
    #
    # @param operation_name [String] The operation name for the Span
    # @param child_of [SpanContext, Span] SpanContext that acts as a parent to
    #   the newly-started Span. If a Span instance is provided, its
    #   context is automatically substituted. See [Reference] for more
    #   information.
    #
    #   If specified, the `references` parameter must be omitted.
    # @param references [Array<Reference>] An array of reference
    #   objects that identify one or more parent SpanContexts.
    # @param start_time [Time] When the Span started, if not now
    # @param tags [Hash] Tags to assign to the Span at start time
    # @param ignore_active_scope [Boolean] whether to create an implicit
    #   References#CHILD_OF reference to the ScopeManager#active.
    #
    # @yield [Span] If passed an optional block, start_span will yield the
    #   newly-created span to the block. The span will be finished automatically
    #   after the block is executed.
    # @return [Span, Object] If passed an optional block, start_span will return
    #  the block's return value, otherwise it returns the newly-started Span
    #  instance, which has not been automatically registered via the
    #  ScopeManager
    def start_span(operation_name,
                   child_of: nil,
                   references: nil,
                   start_time: Time.now,
                   tags: {},
                   ignore_active_scope: false,
                   **)
      context, sampler_tags = prepare_span_context(
        operation_name: operation_name,
        child_of: child_of,
        references: references,
        ignore_active_scope: ignore_active_scope
      )
      span = Span.new(
        context,
        operation_name,
        @reporter,
        start_time: start_time,
        references: references,
        tags: tags.merge(sampler_tags)
      )

      if block_given?
        begin
          yield(span)
        ensure
          span.finish
        end
      else
        span
      end
    end

    # Creates a newly started and activated Scope
    #
    # If the Tracer's ScopeManager#active is not nil, no explicit references
    # are provided, and `ignore_active_scope` is false, then an inferred
    # References#CHILD_OF reference is created to the ScopeManager#active's
    # SpanContext when start_active is invoked.
    #
    # @param operation_name [String] The operation name for the Span
    # @param child_of [SpanContext, Span] SpanContext that acts as a parent to
    #   the newly-started Span. If a Span instance is provided, its
    #   context is automatically substituted. See [Reference] for more
    #   information.
    #
    #   If specified, the `references` parameter must be omitted.
    # @param references [Array<Reference>] An array of reference
    #   objects that identify one or more parent SpanContexts.
    # @param start_time [Time] When the Span started, if not now
    # @param tags [Hash] Tags to assign to the Span at start time
    # @param ignore_active_scope [Boolean] whether to create an implicit
    #   References#CHILD_OF reference to the ScopeManager#active.
    # @param finish_on_close [Boolean] whether span should automatically be
    #   finished when Scope#close is called
    # @yield [Scope] If an optional block is passed to start_active_span it
    #   will yield the newly-started Scope. If `finish_on_close` is true then the
    #   Span will be finished automatically after the block is executed.
    # @return [Scope, Object] If passed an optional block, start_active_span
    #   returns the block's return value, otherwise it returns the newly-started
    #   and activated Scope
    def start_active_span(operation_name,
                          child_of: nil,
                          references: nil,
                          start_time: Time.now,
                          tags: {},
                          ignore_active_scope: false,
                          finish_on_close: true,
                          **)
      span = start_span(
        operation_name,
        child_of: child_of,
        references: references,
        start_time: start_time,
        tags: tags,
        ignore_active_scope: ignore_active_scope
      )
      scope = @scope_manager.activate(span, finish_on_close: finish_on_close)

      if block_given?
        begin
          yield scope
        ensure
          scope.close
        end
      else
        scope
      end
    end

    # Inject a SpanContext into the given carrier
    #
    # @param span_context [SpanContext]
    # @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK]
    # @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
    def inject(span_context, format, carrier)
      @injectors.fetch(format).each do |injector|
        injector.inject(span_context, carrier)
      end
    end

    # Extract a SpanContext in the given format from the given carrier.
    #
    # @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK]
    # @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
    # @return [SpanContext] the extracted SpanContext or nil if none could be found
    def extract(format, carrier)
      @extractors
        .fetch(format)
        .lazy
        .map { |extractor| extractor.extract(carrier) }
        .reject(&:nil?)
        .first
    end

    private

    def prepare_span_context(operation_name:, child_of:, references:, ignore_active_scope:)
      context =
        context_from_child_of(child_of) ||
        context_from_references(references) ||
        context_from_active_scope(ignore_active_scope)

      if context
        [SpanContext.create_from_parent_context(context), {}]
      else
        trace_id = TraceId.generate
        is_sampled, tags = @sampler.sample(
          trace_id: trace_id,
          operation_name: operation_name
        )
        span_context = SpanContext.new(
          trace_id: trace_id,
          span_id: trace_id,
          flags: is_sampled ? SpanContext::Flags::SAMPLED : SpanContext::Flags::NONE
        )
        [span_context, tags]
      end
    end

    def context_from_child_of(child_of)
      return nil unless child_of

      child_of.respond_to?(:context) ? child_of.context : child_of
    end

    def context_from_references(references)
      return nil if !references || references.none?

      # Prefer CHILD_OF reference if present
      ref = references.detect do |reference|
        reference.type == OpenTracing::Reference::CHILD_OF
      end
      (ref || references[0]).context
    end

    def context_from_active_scope(ignore_active_scope)
      return if ignore_active_scope

      active_scope = @scope_manager.active
      active_scope&.span&.context
    end
  end
end