Class: Datadog::Tracing::SpanOperation

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

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.

Constant Summary

Constants included from Metadata::Tagging

Metadata::Tagging::ENSURE_AGENT_TAGS, Metadata::Tagging::NUMERIC_TAG_SIZE_RANGE

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Metadata

included

Methods included from Metadata::Tagging

#clear_metric, #clear_tag, #get_metric, #get_tag, #has_tag?, #meta, #metrics, #set_metric, #set_tag, #set_tags, #tags

Methods included from Metadata::Errors

#set_error_tags

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



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

def end_time
  @end_time
end

#eventsObject (readonly)

Keep span reference private: we don't want users modifying the finalized span from the operation after it has been finished.



508
509
510
# File 'lib/datadog/tracing/span_operation.rb', line 508

def events
  @events
end

#idObject (readonly)

Span attributes



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



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

#parentObject

Stored only for service_entry calculation. Use parent_id for the effective parent span id.



514
515
516
# File 'lib/datadog/tracing/span_operation.rb', line 514

def parent
  @parent
end

#parent_idObject (readonly)

Span attributes



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

#spanObject (readonly)

Keep span reference private: we don't want users modifying the finalized span from the operation after it has been finished.



508
509
510
# File 'lib/datadog/tracing/span_operation.rb', line 508

def span
  @span
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



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



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

#build_spanObject

Create a Span from the operation which represents the finalized measurement. We #dup here to prevent mutation by reference; when this span is returned, we don't want this SpanOperation to modify it further.



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/datadog/tracing/span_operation.rb', line 520

def build_span
  Span.new(
    @name,
    duration: duration,
    end_time: @end_time,
    id: @id,
    meta: Core::Utils::SafeDup.frozen_or_dup(meta),
    metrics: Core::Utils::SafeDup.frozen_or_dup(metrics),
    metastruct: Core::Utils::SafeDup.frozen_or_dup(metastruct),
    parent_id: @parent_id,
    resource: @resource,
    service: @service,
    start_time: @start_time,
    status: @status,
    type: @type,
    trace_id: @trace_id,
    links: @links,
    events: @span_events,
    service_entry: parent.nil? || (service && parent.service != service)
  )
end

#durationObject

Returns:

  • (Object)


290
291
292
293
294
295
296
297
298
299
300
# File 'lib/datadog/tracing/span_operation.rb', line 290

def duration
  # Steep: https://github.com/soutaro/steep/issues/477
  # @type ivar @duration_end: Time
  # @type ivar @duration_start: Time
  return @duration_end - @duration_start if @duration_start && @duration_end

  # Steep: https://github.com/soutaro/steep/issues/477
  # @type ivar @end_time: Time
  # @type ivar @start_time: Time
  @end_time - @start_time if @start_time && @end_time
end

#duration_markerObject

Returns:

  • (Object)


564
565
566
# File 'lib/datadog/tracing/span_operation.rb', line 564

def duration_marker
  Core::Utils::Time.get_time
end

#duration_nanoInteger

Used for serialization

Returns:

  • (Integer)

    in nanoseconds since Epoch



580
581
582
# File 'lib/datadog/tracing/span_operation.rb', line 580

def duration_nano
  (duration * 1e9).to_i
end

#finish(end_time = nil) ⇒ Object

Parameters:

  • end_time (Object, nil) (defaults to: nil)

Returns:

  • (Object)


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

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

  # Stop timing
  stop(end_time)

  # Allow subscribers to enrich the span before it is finalized.
  events.before_finish.publish(self)

  # 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)


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

def finished?
  !span.nil?
end

#get_collector_or_initialize { ... } ⇒ Datadog::ErrorTracking::Collector

Yields:

Yield Returns:

  • (Datadog::ErrorTracking::Collector)

Returns:

  • (Datadog::ErrorTracking::Collector)


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

def get_collector_or_initialize
  @collector ||= yield
end

#measure {|arg0| ... } ⇒ Object

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (Object)

Returns:

  • (Object)

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
201
202
# 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 => e
      logger.debug { "Failed to start span: #{e.class}: #{e.message}" }
    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).
      # @type var e: Exception?
      # Steep: https://github.com/soutaro/steep/issues/919
      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



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/datadog/tracing/span_operation.rb', line 361

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

#record_exception(exception, attributes: {}) ⇒ void

This method returns an undefined value.

Record an exception during the execution of this span. Multiple exceptions can be recorded on a span.

Parameters:

  • exception (Exception)

    The exception to record

  • attributes (optional Hash{String => String, Numeric, Boolean, Array<String, Numeric, Boolean>}) (defaults to: {})

    One or more key:value pairs, where the keys must be strings and the values may be (array of) string, boolean or numeric type.



317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/datadog/tracing/span_operation.rb', line 317

def record_exception(exception, attributes: {})
  exc = Core::Error.build_from(exception)

  event_attributes = {
    'exception.type' => exc.type,
    'exception.message' => exc.message,
    'exception.stacktrace' => exc.backtrace,
  }

  # Steep: Caused by wrong declaration, should be the same parameters as `merge`
  # https://github.com/ruby/rbs/blob/3d0fb3a7fdde60af7120e875fe3bd7237b5b6a88/core/hash.rbs#L1468
  @span_events << SpanEvent.new('exception', attributes: event_attributes.merge!(attributes)) # steep:ignore ArgumentTypeMismatch
end

#root?Boolean

Returns:

  • (Boolean)


252
253
254
# File 'lib/datadog/tracing/span_operation.rb', line 252

def root?
  parent_id == 0
end

#set_error(e) ⇒ Object

Parameters:

  • e (Object)

Returns:

  • (Object)


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

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

#start(start_time = nil) ⇒ self

Parameters:

  • start_time (Object, nil) (defaults to: nil)

Returns:

  • (self)


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

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

#start_time_nanoInteger

Used for serialization

Returns:

  • (Integer)

    in nanoseconds since Epoch



570
571
572
573
574
575
576
# File 'lib/datadog/tracing/span_operation.rb', line 570

def start_time_nano
  return 0 if @start_time.nil?

  # Steep: https://github.com/soutaro/steep/issues/477
  # @type ivar @start_time: Time
  @start_time.to_i * 1000000000 + @start_time.nsec
end

#started?Object

Return whether the duration is started or not



243
244
245
# File 'lib/datadog/tracing/span_operation.rb', line 243

def started?
  !@start_time.nil?
end

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

Mark the span stopped at the current time



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

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?Object

Return whether the duration is stopped or not.



248
249
250
# File 'lib/datadog/tracing/span_operation.rb', line 248

def stopped?
  !@end_time.nil?
end

#to_hashObject

Return the hash representation of the current span.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/datadog/tracing/span_operation.rb', line 337

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.



332
333
334
# File 'lib/datadog/tracing/span_operation.rb', line 332

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