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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Metadata

included

Constructor Details

#initialize(name, child_of: nil, events: nil, on_error: nil, parent_id: 0, resource: name, service: nil, start_time: nil, tags: nil, trace_id: nil, type: nil) ⇒ SpanOperation

Returns a new instance of SpanOperation.



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

def initialize(
  name,
  child_of: nil,
  events: nil,
  on_error: nil,
  parent_id: 0,
  resource: name,
  service: nil,
  start_time: nil,
  tags: nil,
  trace_id: nil,
  type: nil
)
  # 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 = Tracing::Utils.next_id
  @parent_id = parent_id || 0
  @trace_id = trace_id || Tracing::Utils.next_id

  @status = 0

  # 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

  # Only set parent if explicitly provided.
  # We don't want it to override context-derived
  # IDs if it's a distributed trace w/o a parent span.
  parent = child_of
  self.parent = parent if parent

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

  # Subscribe :on_error event
  @events.on_error.wrap_default(&on_error) if on_error.is_a?(Proc)

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

Instance Attribute Details

#end_timeObject

Span attributes



28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def end_time
  @end_time
end

#idObject (readonly) Also known as: span_id

Span attributes



28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def id
  @id
end

#nameString

Operation name.

Returns:

  • (String)


28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def name
  @name
end

#parent_idObject (readonly)

Span attributes



28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def parent_id
  @parent_id
end

#resourceString

Span resource.

Returns:

  • (String)

    String



28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def resource
  @resource
end

#serviceString

Service name.

Returns:

  • (String)

    String



28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def service
  @service
end

#start_timeObject

Span attributes



28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def start_time
  @start_time
end

#statusObject

Returns the value of attribute status.



39
40
41
# File 'lib/datadog/tracing/span_operation.rb', line 39

def status
  @status
end

#trace_idObject (readonly)

Span attributes



28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def trace_id
  @trace_id
end

#typeString Also known as: span_type

Span type.

Returns:

  • (String)

    String



28
29
30
# File 'lib/datadog/tracing/span_operation.rb', line 28

def type
  @type
end

Instance Method Details

#durationObject



266
267
268
269
# File 'lib/datadog/tracing/span_operation.rb', line 266

def duration
  return @duration_end - @duration_start if @duration_start && @duration_end
  return @end_time - @start_time if @start_time && @end_time
end

#finish(end_time = nil) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/datadog/tracing/span_operation.rb', line 245

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)


262
263
264
# File 'lib/datadog/tracing/span_operation.rb', line 262

def finished?
  !span.nil?
end

#measureObject

Raises:

  • (ArgumentError)


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

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
      Datadog.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

    # 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



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

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: [', ']') do
      q.breakable
      q.seplist metrics.each do |key, value|
        q.text "#{key} => #{value}"
      end
    end
  end
end

#set_error(e) ⇒ Object



271
272
273
274
# File 'lib/datadog/tracing/span_operation.rb', line 271

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

#start(start_time = nil) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/datadog/tracing/span_operation.rb', line 187

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

Return whether the duration is started or not



226
227
228
# File 'lib/datadog/tracing/span_operation.rb', line 226

def started?
  !@start_time.nil?
end

#stop(stop_time = nil) ⇒ Object

Mark the span stopped at the current time



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/datadog/tracing/span_operation.rb', line 202

def stop(stop_time = 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)

  self
end

#stopped?Object

Return whether the duration is stopped or not.



231
232
233
# File 'lib/datadog/tracing/span_operation.rb', line 231

def stopped?
  !@end_time.nil?
end

#to_hashObject

Return the hash representation of the current span.



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/datadog/tracing/span_operation.rb', line 282

def to_hash
  h = {
    error: @status,
    id: @id,
    meta: meta,
    metrics: metrics,
    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.



277
278
279
# File 'lib/datadog/tracing/span_operation.rb', line 277

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