Class: Datadog::Span

Inherits:
Object
  • Object
show all
Includes:
Analytics::Span, ForcedTracing::Span
Defined in:
lib/ddtrace/span.rb

Overview

Represents a logical unit of work in the system. Each trace consists of one or more spans. Each span consists of a start time and a duration. For example, a span can describe the time spent on a distributed call on a separate machine, or the time spent in a small component within a larger operation. Spans can be nested within each other, and in those instances will have a parent-child relationship.

rubocop:disable Metrics/ClassLength

Defined Under Namespace

Classes: ResourceContainer

Constant Summary collapse

RUBY_MAX_ID =

The max value for a Span identifier. Span and trace identifiers should be strictly positive and strictly inferior to this limit.

Limited to 2<<62-1 positive integers, as Ruby is able to represent such numbers “inline”, inside a VALUE scalar, thus not requiring memory allocation.

The range of IDs also has to consider portability across different languages and platforms.

(1 << 62) - 1
EXTERNAL_MAX_ID =

While we only generate 63-bit integers due to limitations in other languages, we support parsing 64-bit integers for distributed tracing since an upstream system may generate one

1 << 64
NUMERIC_TAG_SIZE_RANGE =

This limit is for numeric tags because uint64 could end up rounded.

(-1 << 53..1 << 53).freeze
ENSURE_AGENT_TAGS =

Some associated values should always be sent as Tags, never as Metrics, regardless if their value is numeric or not. The Datadog agent will look for these values only as Tags, not Metrics.

{
  Ext::DistributedTracing::ORIGIN_KEY => true,
  Ext::Environment::TAG_VERSION => true,
  Ext::HTTP::STATUS_CODE => true,
  Ext::NET::TAG_HOSTNAME => true
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tracer, name, options = {}) ⇒ Span

Create a new span linked to the given tracer. Call the Tracer method start_span() and then finish() once the tracer operation is over.

  • service: the service name for this span

  • resource: the resource this span refers, or name if it’s missing

  • span_type: the type of the span (such as http, db and so on)

  • parent_id: the identifier of the parent span

  • trace_id: the identifier of the root span for this trace

  • context: the context of the span



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
# File 'lib/ddtrace/span.rb', line 79

def initialize(tracer, name, options = {})
  @tracer = tracer

  @name = name
  @service = options.fetch(:service, nil)
  @resource_container = ResourceContainer.new(options.fetch(:resource, name))
  @span_type = options.fetch(:span_type, nil)

  @span_id = Datadog::Utils.next_id
  @parent_id = options.fetch(:parent_id, 0)
  @trace_id = options.fetch(:trace_id, Datadog::Utils.next_id)

  @context = options.fetch(:context, nil)

  @meta = {}
  @metrics = {}
  @status = 0

  @parent = nil
  @sampled = true

  @allocation_count_start = now_allocations
  @allocation_count_finish = @allocation_count_start

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

Instance Attribute Details

#contextObject

Returns the value of attribute context.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def context
  @context
end

#durationObject



409
410
411
412
413
414
415
# File 'lib/ddtrace/span.rb', line 409

def duration
  if @duration_end.nil? || @duration_start.nil?
    @end_time - @start_time
  else
    @duration_end - @duration_start
  end
end

#end_timeObject

Returns the value of attribute end_time.



66
67
68
# File 'lib/ddtrace/span.rb', line 66

def end_time
  @end_time
end

#nameObject

Returns the value of attribute name.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def name
  @name
end

#parentObject

Returns the value of attribute parent.



66
67
68
# File 'lib/ddtrace/span.rb', line 66

def parent
  @parent
end

#parent_idObject

Returns the value of attribute parent_id.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def parent_id
  @parent_id
end

#resource_containerObject (readonly)

Returns the value of attribute resource_container.



66
67
68
# File 'lib/ddtrace/span.rb', line 66

def resource_container
  @resource_container
end

#sampledObject

Returns the value of attribute sampled.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def sampled
  @sampled
end

#serviceObject

Returns the value of attribute service.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def service
  @service
end

#span_idObject

Returns the value of attribute span_id.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def span_id
  @span_id
end

#span_typeObject

Returns the value of attribute span_type.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def span_type
  @span_type
end

#start_timeObject

Returns the value of attribute start_time.



66
67
68
# File 'lib/ddtrace/span.rb', line 66

def start_time
  @start_time
end

#statusObject

Returns the value of attribute status.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def status
  @status
end

#trace_idObject

Returns the value of attribute trace_id.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def trace_id
  @trace_id
end

#tracerObject

Returns the value of attribute tracer.



61
62
63
# File 'lib/ddtrace/span.rb', line 61

def tracer
  @tracer
end

Instance Method Details

#allocationsObject



280
281
282
# File 'lib/ddtrace/span.rb', line 280

def allocations
  @allocation_count_finish - @allocation_count_start
end

#clear_metric(key) ⇒ Object

This method removes a metric for the given key. It acts like #remove_tag.



175
176
177
# File 'lib/ddtrace/span.rb', line 175

def clear_metric(key)
  @metrics.delete(key)
end

#clear_tag(key) ⇒ Object

This method removes a tag for the given key.



152
153
154
# File 'lib/ddtrace/span.rb', line 152

def clear_tag(key)
  @meta.delete(key)
end

#finish(finish_time = nil) ⇒ Object

Mark the span finished at the current time and submit it.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/ddtrace/span.rb', line 217

def finish(finish_time = nil)
  # A span should not be finished twice. Note that this is not thread-safe,
  # finish is called from multiple threads, a given span might be finished
  # 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 finished?

  @allocation_count_finish = now_allocations

  now = 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(finish_time || now) unless started?

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

  # Finish does not really do anything if the span is not bound to a tracer and a context.
  return self if @tracer.nil? || @context.nil?

  # spans without a service would be dropped, so here we provide a default.
  # This should really never happen with integrations in contrib, as a default
  # service is always set. It's only for custom instrumentation.
  @service ||= (@tracer && @tracer.default_service)

  begin
    @context.close_span(self)
    @tracer.record(self)
  rescue StandardError => e
    Datadog.logger.debug("error recording finished trace: #{e}")
    Datadog.health_metrics.error_span_finish(1, tags: ["error:#{e.class.name}"])
  end
  self
end

#finished?Boolean

Return whether the duration is finished or not.

Returns:

  • (Boolean)


405
406
407
# File 'lib/ddtrace/span.rb', line 405

def finished?
  !@end_time.nil?
end

#get_metric(key) ⇒ Object

Return the metric with the given key, nil if it doesn’t exist.



180
181
182
# File 'lib/ddtrace/span.rb', line 180

def get_metric(key)
  @metrics[key] || @meta[key]
end

#get_tag(key) ⇒ Object

Return the tag with the given key, nil if it doesn’t exist.



157
158
159
# File 'lib/ddtrace/span.rb', line 157

def get_tag(key)
  @meta[key] || @metrics[key]
end

#pretty_print(q) ⇒ Object

Return a human readable version of the span



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
394
395
396
397
# File 'lib/ddtrace/span.rb', line 367

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: #{@span_id}\n"
    q.text "Parent ID: #{@parent_id}\n"
    q.text "Trace ID: #{@trace_id}\n"
    q.text "Type: #{@span_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 finished?}\n"
    q.text "Allocations: #{allocations}\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

#resourceObject



417
418
419
# File 'lib/ddtrace/span.rb', line 417

def resource
  @resource_container.latest
end

#resource=(resource) ⇒ Object



421
422
423
# File 'lib/ddtrace/span.rb', line 421

def resource=(resource)
  @resource_container.latest = resource
end

#set_error(e) ⇒ Object

Mark the span with the given error.



185
186
187
188
189
190
191
192
# File 'lib/ddtrace/span.rb', line 185

def set_error(e)
  e = Error.build_from(e)

  @status = Ext::Errors::STATUS
  set_tag(Ext::Errors::TYPE, e.type) unless e.type.empty?
  set_tag(Ext::Errors::MSG, e.message) unless e.message.empty?
  set_tag(Ext::Errors::STACK, e.backtrace) unless e.backtrace.empty?
end

#set_metric(key, value) ⇒ Object

This method sets a tag with a floating point value for the given key. It acts like ‘set_tag()` and it simply add a tag without further processing.



163
164
165
166
167
168
169
170
171
172
# File 'lib/ddtrace/span.rb', line 163

def set_metric(key, value)
  # Keys must be unique between tags and metrics
  @meta.delete(key)

  # enforce that the value is a floating point number
  value = Float(value)
  @metrics[key] = value
rescue StandardError => e
  Datadog.logger.debug("Unable to set the metric #{key}, ignoring it. Caused by: #{e}")
end

#set_parent(parent) ⇒ Object

DEPRECATED: remove this function in the next release, replaced by “parent=“



260
261
262
# File 'lib/ddtrace/span.rb', line 260

def set_parent(parent)
  self.parent = parent
end

#set_tag(key, value = nil) ⇒ Object

Set the given key / value tag pair on the span. Keys and values must be strings. A valid example is:

span.set_tag('http.method', request.method)


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/ddtrace/span.rb', line 120

def set_tag(key, value = nil)
  # Keys must be unique between tags and metrics
  @metrics.delete(key)

  # DEV: This is necessary because the agent looks at `meta[key]`, not `metrics[key]`.
  value = value.to_s if ENSURE_AGENT_TAGS[key]

  # NOTE: Adding numeric tags as metrics is stop-gap support
  #       for numeric typed tags. Eventually they will become
  #       tags again.
  # Any numeric that is not an integer greater than max size is logged as a metric.
  # Everything else gets logged as a tag.
  if value.is_a?(Numeric) && !(value.is_a?(Integer) && !NUMERIC_TAG_SIZE_RANGE.cover?(value))
    set_metric(key, value)
  else
    @meta[key] = value.to_s
  end
rescue StandardError => e
  Datadog.logger.debug("Unable to set the tag #{key}, ignoring it. Caused by: #{e}")
end

#set_tags(tags) ⇒ Object

Sets tags from given hash, for each key in hash it sets the tag with that key and associated value from the hash. It is shortcut for set_tag. Keys and values of the hash must be strings. Note that nested hashes are not supported. A valid example is:

span.set_tags({ "http.method" => "GET", "user.id" => "234" })


147
148
149
# File 'lib/ddtrace/span.rb', line 147

def set_tags(tags)
  tags.each { |k, v| set_tag(k, v) }
end

#start(start_time = nil) ⇒ Object

Mark the span started at the current time.



195
196
197
198
199
200
201
202
203
204
# File 'lib/ddtrace/span.rb', line 195

def start(start_time = nil)
  # A span should not be started twice. However, this is existing
  # behavior and so we maintain it for backward compatibility for those
  # who are using async manual instrumentation that may rely on this

  @start_time = start_time || 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)


400
401
402
# File 'lib/ddtrace/span.rb', line 400

def started?
  !@start_time.nil?
end

#to_hashObject

Return the hash representation of the current span.



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/ddtrace/span.rb', line 285

def to_hash
  h = {
    span_id: @span_id,
    parent_id: @parent_id,
    trace_id: @trace_id,
    name: @name,
    service: @service,
    resource: resource,
    type: @span_type,
    meta: @meta,
    metrics: @metrics,
    allocations: allocations,
    error: @status
  }

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

  h
end

#to_json(*args) ⇒ Object

JSON serializer interface. Used by older version of the transport.



362
363
364
# File 'lib/ddtrace/span.rb', line 362

def to_json(*args)
  to_hash.to_json(*args)
end

#to_msgpack(packer = nil) ⇒ Object

MessagePack serializer interface. Making this object respond to #to_msgpack allows it to be automatically serialized by MessagePack.

This is more efficient than doing MessagePack.pack(span.to_hash) as we don’t have to create an intermediate Hash.

Parameters:

  • packer (MessagePack::Packer) (defaults to: nil)

    serialization buffer, can be nil with JRuby



316
317
318
319
320
321
322
323
324
325
326
327
328
329
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
# File 'lib/ddtrace/span.rb', line 316

def to_msgpack(packer = nil)
  # As of 1.3.3, JRuby implementation doesn't pass an existing packer
  packer ||= MessagePack::Packer.new

  if finished?
    packer.write_map_header(13) # Set header with how many elements in the map

    packer.write('start')
    packer.write(start_time_nano)

    packer.write('duration')
    packer.write(duration_nano)
  else
    packer.write_map_header(11) # Set header with how many elements in the map
  end

  # DEV: We use strings as keys here, instead of symbols, as
  # DEV: MessagePack will ultimately convert them to strings.
  # DEV: By providing strings directly, we skip this indirection operation.
  packer.write('span_id')
  packer.write(@span_id)
  packer.write('parent_id')
  packer.write(@parent_id)
  packer.write('trace_id')
  packer.write(@trace_id)
  packer.write('name')
  packer.write(@name)
  packer.write('service')
  packer.write(@service)
  packer.write('resource')
  packer.write(resource)
  packer.write('type')
  packer.write(@span_type)
  packer.write('meta')
  packer.write(@meta)
  packer.write('metrics')
  packer.write(@metrics)
  packer.write('allocations')
  packer.write(allocations)
  packer.write('error')
  packer.write(@status)
  packer
end

#to_sObject

Return a string representation of the span.



255
256
257
# File 'lib/ddtrace/span.rb', line 255

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