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

Constant Summary collapse

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 63-bit positive integers, as some other languages might be limited to this, and IDs need to be easy to port across various languages and platforms.

2**63
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

2**64
NUMERIC_TAG_SIZE_RANGE =

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

(-2**53..2**53)

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



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

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

  @name = name
  @service = options.fetch(:service, nil)
  @resource = 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

  @start_time = nil # set by Tracer.start_span
  @end_time = nil # set by Span.finish

  @allocation_count_start = now_allocations
  @allocation_count_finish = @allocation_count_start
end

Instance Attribute Details

#contextObject

Returns the value of attribute context.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def context
  @context
end

#end_timeObject

Returns the value of attribute end_time.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def end_time
  @end_time
end

#nameObject

Returns the value of attribute name.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def name
  @name
end

#parentObject

Returns the value of attribute parent.



44
45
46
# File 'lib/ddtrace/span.rb', line 44

def parent
  @parent
end

#parent_idObject

Returns the value of attribute parent_id.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def parent_id
  @parent_id
end

#resourceObject

Returns the value of attribute resource.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def resource
  @resource
end

#sampledObject

Returns the value of attribute sampled.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def sampled
  @sampled
end

#serviceObject

Returns the value of attribute service.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def service
  @service
end

#span_idObject

Returns the value of attribute span_id.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def span_id
  @span_id
end

#span_typeObject

Returns the value of attribute span_type.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def span_type
  @span_type
end

#start_timeObject

Returns the value of attribute start_time.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def start_time
  @start_time
end

#statusObject

Returns the value of attribute status.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def status
  @status
end

#trace_idObject

Returns the value of attribute trace_id.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def trace_id
  @trace_id
end

#tracerObject

Returns the value of attribute tracer.



38
39
40
# File 'lib/ddtrace/span.rb', line 38

def tracer
  @tracer
end

Instance Method Details

#allocationsObject



229
230
231
# File 'lib/ddtrace/span.rb', line 229

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.



144
145
146
# File 'lib/ddtrace/span.rb', line 144

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

#clear_tag(key) ⇒ Object

This method removes a tag for the given key.



121
122
123
# File 'lib/ddtrace/span.rb', line 121

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

#finish(finish_time = nil) ⇒ Object

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



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

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

  # Provide a default start_time if unset, but this should have been set by start_span.
  # Using now here causes 0-duration spans, still, this is expected, as we never
  # explicitely say when it started.
  @start_time ||= Time.now.utc

  @end_time = finish_time.nil? ? Time.now.utc : finish_time # finish this

  # 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 span is finished or not.

Returns:

  • (Boolean)


199
200
201
# File 'lib/ddtrace/span.rb', line 199

def finished?
  !@end_time.nil?
end

#get_metric(key) ⇒ Object

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



149
150
151
# File 'lib/ddtrace/span.rb', line 149

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.



126
127
128
# File 'lib/ddtrace/span.rb', line 126

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

#pretty_print(q) ⇒ Object

Return a human readable version of the span



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/ddtrace/span.rb', line 258

def pretty_print(q)
  start_time = (@start_time.to_f * 1e9).to_i rescue '-'
  end_time = (@end_time.to_f * 1e9).to_i rescue '-'
  duration = ((@end_time - @start_time) * 1e9).to_i rescue 0
  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}\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

#set_error(e) ⇒ Object

Mark the span with the given error.



154
155
156
157
158
159
160
161
# File 'lib/ddtrace/span.rb', line 154

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.



132
133
134
135
136
137
138
139
140
141
# File 'lib/ddtrace/span.rb', line 132

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=“



209
210
211
# File 'lib/ddtrace/span.rb', line 209

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)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/ddtrace/span.rb', line 86

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

  # Ensure `http.status_code` is always a string so it is added to
  #   @meta instead of @metrics
  # DEV: This is necessary because the agent looks to `meta['http.status_code']` for
  #   tagging necessary metrics
  value = value.to_s if key == Ext::HTTP::STATUS_CODE

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


116
117
118
# File 'lib/ddtrace/span.rb', line 116

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

#to_hashObject

Return the hash representation of the current span.



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/ddtrace/span.rb', line 234

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 !@start_time.nil? && !@end_time.nil?
    h[:start] = (@start_time.to_f * 1e9).to_i
    h[:duration] = ((@end_time - @start_time) * 1e9).to_i
  end

  h
end

#to_sObject

Return a string representation of the span.



204
205
206
# File 'lib/ddtrace/span.rb', line 204

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