Class: Datadog::Tracing::SpanOperation

Inherits:
Object
  • Object
show all
Includes:
Metadata
Defined in:
lib/datadog/tracing/span_operation.rb

Overview

Represents the act of taking a span measurement. It gives a Span a context which can be used to build a Span. When completed, it yields the Span.

Defined Under Namespace

Classes: AlreadyStartedError, Events

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Metadata

included

Constructor Details

#initialize(name, logger: Datadog.logger, events: nil, on_error: nil, parent_id: 0, resource: name, service: nil, start_time: nil, tags: nil, trace_id: nil, type: nil, links: nil, span_events: nil, id: nil) ⇒ SpanOperation

Returns a new instance of SpanOperation.



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
# File 'lib/datadog/tracing/span_operation.rb', line 44

def initialize(
  name,
  logger: Datadog.logger,
  events: nil,
  on_error: nil,
  parent_id: 0,
  resource: name,
  service: nil,
  start_time: nil,
  tags: nil,
  trace_id: nil,
  type: nil,
  links: nil,
  span_events: nil,
  id: nil
)
  @logger = logger

  # Ensure dynamically created strings are UTF-8 encoded.
  #
  # All strings created in Ruby land are UTF-8. The only sources of non-UTF-8 string are:
  # * Strings explicitly encoded as non-UTF-8.
  # * Some natively created string, although most natively created strings are UTF-8.
  self.name = name
  self.service = service
  self.type = type
  self.resource = resource

  @id = id.nil? ? Tracing::Utils.next_id : id
  @parent_id = parent_id || 0
  @trace_id = trace_id || Tracing::Utils::TraceId.next_id

  @status = 0
  # stores array of span links
  @links = links || []
  # stores array of span events
  @span_events = span_events || []

  # start_time and end_time track wall clock. In Ruby, wall clock
  # has less accuracy than monotonic clock, so if possible we look to only use wall clock
  # to measure duration when a time is supplied by the user, or if monotonic clock
  # is unsupported.
  @start_time = nil
  @end_time = nil

  # duration_start and duration_end track monotonic clock, and may remain nil in cases where it
  # is known that we have to use wall clock to measure duration.
  @duration_start = nil
  @duration_end = nil

  # Set tags if provided.
  set_tags(tags) if tags

  # Some other SpanOperation-specific behavior
  @events = events || Events.new(logger: logger)
  @span = nil

  if on_error.nil?
    # Nothing, default error handler is already set up.
  elsif on_error.is_a?(Proc)
    # Subscribe :on_error event
    @events.on_error.wrap_default(&on_error)
  else
    logger.warn("on_error argument to SpanOperation ignored because is not a Proc: #{on_error}")
  end

  # Start the span with start time, if given.
  start(start_time) if start_time
end

Instance Attribute Details

#end_timeObject

Span attributes NOTE: In the future, we should drop the me



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def end_time
  @end_time
end

#idObject (readonly)

Span attributes NOTE: In the future, we should drop the me



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def id
  @id
end

Returns the value of attribute links.



42
43
44
# File 'lib/datadog/tracing/span_operation.rb', line 42

def links
  @links
end

#loggerObject (readonly)

Span attributes NOTE: In the future, we should drop the me



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def logger
  @logger
end

#nameString

Operation name.

Returns:

  • (String)


30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def name
  @name
end

#parent_idObject (readonly)

Span attributes NOTE: In the future, we should drop the me



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def parent_id
  @parent_id
end

#resourceString

Span resource.

Returns:

  • (String)

    String



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def resource
  @resource
end

#serviceString

Service name.

Returns:

  • (String)

    String



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def service
  @service
end

#span_eventsObject

Returns the value of attribute span_events.



42
43
44
# File 'lib/datadog/tracing/span_operation.rb', line 42

def span_events
  @span_events
end

#start_timeObject

Span attributes NOTE: In the future, we should drop the me



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def start_time
  @start_time
end

#statusObject

Returns the value of attribute status.



42
43
44
# File 'lib/datadog/tracing/span_operation.rb', line 42

def status
  @status
end

#trace_idObject (readonly)

Span attributes NOTE: In the future, we should drop the me



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def trace_id
  @trace_id
end

#typeString

Span type.

Returns:

  • (String)

    String



30
31
32
# File 'lib/datadog/tracing/span_operation.rb', line 30

def type
  @type
end

Instance Method Details

#durationObject



289
290
291
292
293
# File 'lib/datadog/tracing/span_operation.rb', line 289

def duration
  return @duration_end - @duration_start if @duration_start && @duration_end

  @end_time - @start_time if @start_time && @end_time
end

#finish(end_time = nil) ⇒ Object



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/datadog/tracing/span_operation.rb', line 268

def finish(end_time = nil)
  # Returned memoized span if already finished
  return span if finished?

  # Stop timing
  stop(end_time)

  # Build span
  # Memoize for performance reasons
  @span = build_span

  # Trigger after_finish event
  events.after_finish.publish(span, self)

  span
end

#finished?Boolean

Returns:

  • (Boolean)


285
286
287
# File 'lib/datadog/tracing/span_operation.rb', line 285

def finished?
  !span.nil?
end

#get_collector_or_initializeObject



144
145
146
# File 'lib/datadog/tracing/span_operation.rb', line 144

def get_collector_or_initialize
  @collector ||= yield
end

#measureObject

Raises:

  • (ArgumentError)


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
# File 'lib/datadog/tracing/span_operation.rb', line 148

def measure
  raise ArgumentError, 'Must provide block to measure!' unless block_given?
  # TODO: Should we just invoke the block and skip tracing instead?
  raise AlreadyStartedError if started?

  return_value = nil

  begin
    # If span fails to start, don't prevent the operation from
    # running, to minimize impact on normal application function.
    begin
      start
    rescue StandardError => e
      logger.debug { "Failed to start span: #{e}" }
    ensure
      # We should yield to the provided block when possible, as this
      # block is application code that we don't want to hinder.
      # * We don't yield during a fatal error, as the application is likely trying to
      #   end its execution (either due to a system error or graceful shutdown).
      return_value = yield(self) unless e && !e.is_a?(StandardError)
    end
  # rubocop:disable Lint/RescueException
  # Here we really want to catch *any* exception, not only StandardError,
  # as we really have no clue of what is in the block,
  # and it is user code which should be executed no matter what.
  # It's not a problem since we re-raise it afterwards so for example a
  # SignalException::Interrupt would still bubble up.
  rescue Exception => e
    # Stop the span first, so timing is a more accurate.
    # If the span failed to start, timing may be inaccurate,
    # but this is not really a serious concern.
    stop(exception: e)

    # Trigger the on_error event
    events.on_error.publish(self, e)

    # We must finish the span to trigger callbacks,
    # and build the final span.
    finish

    raise e
  # Use an ensure block here to make sure the span closes.
  # NOTE: It's not sufficient to use "else": when a function
  #       uses "return", it will skip "else".
  ensure
    # Finish the span
    # NOTE: If an error was raised, this "finish" might be redundant.
    finish unless finished?
  end
  # rubocop:enable Lint/RescueException

  return_value
end

#pretty_print(q) ⇒ Object

Return a human readable version of the span



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/datadog/tracing/span_operation.rb', line 330

def pretty_print(q)
  start_time = (self.start_time.to_f * 1e9).to_i
  end_time = (self.end_time.to_f * 1e9).to_i
  q.group 0 do
    q.breakable
    q.text "Name: #{@name}\n"
    q.text "Span ID: #{@id}\n"
    q.text "Parent ID: #{@parent_id}\n"
    q.text "Trace ID: #{@trace_id}\n"
    q.text "Type: #{@type}\n"
    q.text "Service: #{@service}\n"
    q.text "Resource: #{@resource}\n"
    q.text "Error: #{@status}\n"
    q.text "Start: #{start_time}\n"
    q.text "End: #{end_time}\n"
    q.text "Duration: #{duration.to_f if stopped?}\n"
    q.group(2, 'Tags: [', "]\n") do
      q.breakable
      q.seplist meta.each do |key, value|
        q.text "#{key} => #{value}"
      end
    end
    q.group(2, 'Metrics: [', "]\n") do
      q.breakable
      q.seplist metrics.each do |key, value|
        q.text "#{key} => #{value}"
      end
    end
    q.group(2, 'Metastruct: [', ']') do
      metastruct.pretty_print(q)
    end
  end
end

#root?Boolean

Returns:

  • (Boolean)


254
255
256
# File 'lib/datadog/tracing/span_operation.rb', line 254

def root?
  parent_id == 0
end

#set_error(e) ⇒ Object



295
296
297
298
# File 'lib/datadog/tracing/span_operation.rb', line 295

def set_error(e)
  @status = ::Ext::Errors::STATUS
  set_error_tags(e)
end

#start(start_time = nil) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/datadog/tracing/span_operation.rb', line 202

def start(start_time = nil)
  # Span can only be started once
  return self if started?

  # Trigger before_start event
  events.before_start.publish(self)

  # Start the span
  @start_time = start_time || Core::Utils::Time.now.utc
  @duration_start = start_time.nil? ? duration_marker : nil

  self
end

#started?Boolean

Return whether the duration is started or not

Returns:

  • (Boolean)


245
246
247
# File 'lib/datadog/tracing/span_operation.rb', line 245

def started?
  !@start_time.nil?
end

#stop(stop_time = nil, exception: nil) ⇒ Object

Mark the span stopped at the current time

steep:ignore:start Steep issue fixed in github.com/soutaro/steep/pull/1467



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/datadog/tracing/span_operation.rb', line 220

def stop(stop_time = nil, exception: nil)
  # A span should not be stopped twice. Note that this is not thread-safe,
  # stop is called from multiple threads, a given span might be stopped
  # several times. Again, one should not do this, so this test is more a
  # fallback to avoid very bad things and protect you in most common cases.
  return if stopped?

  now = Core::Utils::Time.now.utc

  # Provide a default start_time if unset.
  # Using `now` here causes duration to be 0; this is expected
  # behavior when start_time is unknown.
  start(stop_time || now) unless started?

  @end_time = stop_time || now
  @duration_end = stop_time.nil? ? duration_marker : nil

  # Trigger after_stop event
  events.after_stop.publish(self, exception)

  self
end

#stopped?Boolean

Return whether the duration is stopped or not.

Returns:

  • (Boolean)


250
251
252
# File 'lib/datadog/tracing/span_operation.rb', line 250

def stopped?
  !@end_time.nil?
end

#to_hashObject

Return the hash representation of the current span.



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/datadog/tracing/span_operation.rb', line 306

def to_hash
  h = {
    error: @status,
    id: @id,
    meta: meta,
    metrics: metrics,
    metastruct: metastruct,
    name: @name,
    parent_id: @parent_id,
    resource: @resource,
    service: @service,
    trace_id: @trace_id,
    type: @type
  }

  if stopped?
    h[:start] = start_time_nano
    h[:duration] = duration_nano
  end

  h
end

#to_sObject

Return a string representation of the span.



301
302
303
# File 'lib/datadog/tracing/span_operation.rb', line 301

def to_s
  "SpanOperation(name:#{@name},sid:#{@id},tid:#{@trace_id},pid:#{@parent_id})"
end