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.



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

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::TraceId.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



26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def end_time
  @end_time
end

#idObject (readonly) Also known as: span_id

Span attributes



26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def id
  @id
end

#nameString

Operation name.

Returns:

  • (String)


26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def name
  @name
end

#parent_idObject (readonly)

Span attributes



26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def parent_id
  @parent_id
end

#resourceString

Span resource.

Returns:

  • (String)

    String



26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def resource
  @resource
end

#serviceString

Service name.

Returns:

  • (String)

    String



26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def service
  @service
end

#start_timeObject

Span attributes



26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def start_time
  @start_time
end

#statusObject

Returns the value of attribute status.



37
38
39
# File 'lib/datadog/tracing/span_operation.rb', line 37

def status
  @status
end

#trace_idObject (readonly)

Span attributes



26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def trace_id
  @trace_id
end

#typeString Also known as: span_type

Span type.

Returns:

  • (String)

    String



26
27
28
# File 'lib/datadog/tracing/span_operation.rb', line 26

def type
  @type
end

Instance Method Details

#durationObject



264
265
266
267
# File 'lib/datadog/tracing/span_operation.rb', line 264

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



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

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)


260
261
262
# File 'lib/datadog/tracing/span_operation.rb', line 260

def finished?
  !span.nil?
end

#measureObject

Raises:

  • (ArgumentError)


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

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



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

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



269
270
271
272
# File 'lib/datadog/tracing/span_operation.rb', line 269

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

#start(start_time = nil) ⇒ Object



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

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



224
225
226
# File 'lib/datadog/tracing/span_operation.rb', line 224

def started?
  !@start_time.nil?
end

#stop(stop_time = nil) ⇒ Object

Mark the span stopped at the current time



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

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.



229
230
231
# File 'lib/datadog/tracing/span_operation.rb', line 229

def stopped?
  !@end_time.nil?
end

#to_hashObject

Return the hash representation of the current span.



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

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.



275
276
277
# File 'lib/datadog/tracing/span_operation.rb', line 275

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