Class: Datadog::Tracer

Inherits:
Object
  • Object
show all
Defined in:
lib/ddtrace/tracer.rb

Overview

A Tracer keeps track of the time spent by an application processing a single operation. For example, a trace can be used to track the entire time spent processing a complicated web request. Even though the request may require multiple resources and machines to handle the request, all of these function calls and sub-requests would be encapsulated within a single trace. rubocop:disable Metrics/ClassLength

Constant Summary collapse

ALLOWED_SPAN_OPTIONS =
[:service, :resource, :span_type].freeze
DEFAULT_ON_ERROR =
proc { |span, error| span.set_error(error) unless span.nil? }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Tracer

Initialize a new Tracer used to create, sample and submit spans that measure the time of sections of code. Available options are:

  • enabled: set if the tracer submits or not spans to the local agent. It’s enabled by default.



111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ddtrace/tracer.rb', line 111

def initialize(options = {})
  @enabled = options.fetch(:enabled, true)
  @writer = options.fetch(:writer, Datadog::Writer.new)
  @sampler = options.fetch(:sampler, Datadog::AllSampler.new)

  @provider = options.fetch(:context_provider, Datadog::DefaultContextProvider.new)
  @provider ||= Datadog::DefaultContextProvider.new # @provider should never be nil

  @context_flush = options[:partial_flush] ? Datadog::ContextFlush.new(options) : nil

  @mutex = Mutex.new
  @tags = {}
end

Instance Attribute Details

#default_serviceObject

A default value for service. One should really override this one for non-root spans which have a parent. However, root spans without a service would be invalid and rejected.



173
174
175
176
177
178
179
180
181
182
# File 'lib/ddtrace/tracer.rb', line 173

def default_service
  return @default_service if instance_variable_defined?(:@default_service) && @default_service
  begin
    @default_service = File.basename($PROGRAM_NAME, '.*')
  rescue StandardError => e
    Datadog::Tracer.log.error("unable to guess default service: #{e}")
    @default_service = 'ruby'.freeze
  end
  @default_service
end

#enabledObject

Returns the value of attribute enabled.



24
25
26
# File 'lib/ddtrace/tracer.rb', line 24

def enabled
  @enabled
end

#providerObject (readonly)

Returns the value of attribute provider.



23
24
25
# File 'lib/ddtrace/tracer.rb', line 23

def provider
  @provider
end

#samplerObject (readonly)

Returns the value of attribute sampler.



23
24
25
# File 'lib/ddtrace/tracer.rb', line 23

def sampler
  @sampler
end

#tagsObject (readonly)

Returns the value of attribute tags.



23
24
25
# File 'lib/ddtrace/tracer.rb', line 23

def tags
  @tags
end

#writerObject

Returns the value of attribute writer.



24
25
26
# File 'lib/ddtrace/tracer.rb', line 24

def writer
  @writer
end

Class Method Details

.debug_loggingObject

Return if the debug mode is activated or not



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

def self.debug_logging
  log.level == Logger::DEBUG
end

.debug_logging=(value) ⇒ Object

Activate the debug mode providing more information related to tracer usage Default to Warn level unless using custom logger



57
58
59
60
61
62
63
# File 'lib/ddtrace/tracer.rb', line 57

def self.debug_logging=(value)
  if value
    log.level = Logger::DEBUG
  elsif log.is_a?(Datadog::Logger)
    log.level = Logger::WARN
  end
end

.logObject

Global, memoized, lazy initialized instance of a logger that is used within the the Datadog namespace. This logger outputs to STDOUT by default, and is considered thread-safe.



32
33
34
35
36
37
38
# File 'lib/ddtrace/tracer.rb', line 32

def self.log
  unless defined? @logger
    @logger = Datadog::Logger.new(STDOUT)
    @logger.level = Logger::WARN
  end
  @logger
end

.log=(logger) ⇒ Object

Override the default logger with a custom one.



41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/ddtrace/tracer.rb', line 41

def self.log=(logger)
  return unless logger
  return unless logger.respond_to? :methods
  return unless logger.respond_to? :error
  if logger.respond_to? :methods
    unimplemented = Logger.new(STDOUT).methods - logger.methods
    unless unimplemented.empty?
      logger.error("logger #{logger} does not implement #{unimplemented}")
      return
    end
  end
  @logger = logger
end

Instance Method Details

#active_correlationObject

Return a CorrelationIdentifier for active span



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

def active_correlation
  Datadog::Correlation.identifier_from_context(call_context)
end

#active_root_spanObject

Return the current active root span or nil.



357
358
359
# File 'lib/ddtrace/tracer.rb', line 357

def active_root_span
  call_context.current_root_span
end

#active_spanObject

Return the current active span or nil.



352
353
354
# File 'lib/ddtrace/tracer.rb', line 352

def active_span
  call_context.current_span
end

#call_contextObject

Return the current active Context for this traced execution. This method is automatically called when calling Tracer.trace or Tracer.start_span, but it can be used in the application code during manual instrumentation.

This method makes use of a ContextProvider that is automatically set during the tracer initialization, or while using a library instrumentation.



102
103
104
# File 'lib/ddtrace/tracer.rb', line 102

def call_context
  @provider.context
end

#configure(options = {}) ⇒ Object

Updates the current Tracer instance, so that the tracer can be configured after the initialization. Available options are:

  • enabled: set if the tracer submits or not spans to the trace agent

  • hostname: change the location of the trace agent

  • port: change the port of the trace agent

For instance, if the trace agent runs in a different location, just:

tracer.configure(hostname: 'agent.service.consul', port: '8777')


136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/ddtrace/tracer.rb', line 136

def configure(options = {})
  enabled = options.fetch(:enabled, nil)

  # Those are rare "power-user" options.
  sampler = options.fetch(:sampler, nil)
  max_spans_before_partial_flush = options.fetch(:max_spans_before_partial_flush, nil)
  min_spans_before_partial_flush = options.fetch(:min_spans_before_partial_flush, nil)
  partial_flush_timeout = options.fetch(:partial_flush_timeout, nil)

  @enabled = enabled unless enabled.nil?
  @sampler = sampler unless sampler.nil?

  configure_writer(options)

  @context_flush = Datadog::ContextFlush.new(options) unless min_spans_before_partial_flush.nil? &&
                                                             max_spans_before_partial_flush.nil? &&
                                                             partial_flush_timeout.nil?
end

#record(context) ⇒ Object

Record the given context. For compatibility with previous versions, context can also be a span. It is similar to the child_of argument, method will figure out what to do, submitting a span for recording is like trying to record its context.



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/ddtrace/tracer.rb', line 328

def record(context)
  context = context.context if context.is_a?(Datadog::Span)
  return if context.nil?
  trace, sampled = context.get

  # If context flushing is configured...
  if @context_flush
    if sampled
      if trace.nil? || trace.empty?
        @context_flush.each_partial_trace(context) do |t|
          write(t)
        end
      else
        write(trace)
      end
    end
  # Default behavior
  else
    ready = !trace.nil? && !trace.empty? && sampled
    write(trace) if ready
  end
end

#servicesObject



70
71
72
73
74
75
76
77
# File 'lib/ddtrace/tracer.rb', line 70

def services
  # Only log each deprecation warning once (safeguard against log spam)
  Datadog::Patcher.do_once('Tracer#set_service_info') do
    Datadog::Tracer.log.warn('services: Usage of Tracer.services has been deprecated')
  end

  {}
end

#set_service_info(service, app, app_type) ⇒ Object

Set the information about the given service. A valid example is:

tracer.set_service_info('web-application', 'rails', 'web')

set_service_info is deprecated, no service information needs to be tracked



160
161
162
163
164
165
166
167
168
# File 'lib/ddtrace/tracer.rb', line 160

def set_service_info(service, app, app_type)
  # Only log each deprecation warning once (safeguard against log spam)
  Datadog::Patcher.do_once('Tracer#set_service_info') do
    Datadog::Tracer.log.warn(%(
      set_service_info: Usage of set_service_info has been deprecated,
      service information no longer needs to be reported to the trace agent.
    ))
  end
end

#set_tags(tags) ⇒ Object

Set the given key / value tag pair at the tracer level. These tags will be appended to each span created by the tracer. Keys and values must be strings. A valid example is:

tracer.set_tags('env' => 'prod', 'component' => 'core')


189
190
191
# File 'lib/ddtrace/tracer.rb', line 189

def set_tags(tags)
  @tags.update(tags)
end

#shutdown!Object

Shorthand that calls the ‘shutdown!` method of a registered worker. It’s useful to ensure that the Trace Buffer is properly flushed before shutting down the application.

For instance:

tracer.trace('operation_name', service='rake_tasks') do |span|
  span.set_tag('task.name', 'script')
end

tracer.shutdown!


91
92
93
94
# File 'lib/ddtrace/tracer.rb', line 91

def shutdown!
  return if !@enabled || @writer.worker.nil?
  @writer.worker.stop
end

#start_span(name, options = {}) ⇒ Object

Return a span that will trace an operation called name. This method allows parenting passing child_of as an option. If it’s missing, the newly created span is a root span. Available options are:

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

  • child_of: a Span or a Context instance representing the parent for this span.

  • start_time: when the span actually starts (defaults to now)

  • tags: extra tags which should be added to the span.



215
216
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
# File 'lib/ddtrace/tracer.rb', line 215

def start_span(name, options = {})
  start_time = options.fetch(:start_time, Time.now.utc)

  tags = options.fetch(:tags, {})

  span_options = options.select do |k, _v|
    # Filter options, we want no side effects with unexpected args.
    ALLOWED_SPAN_OPTIONS.include?(k)
  end

  ctx, parent = guess_context_and_parent(options[:child_of])
  span_options[:context] = ctx unless ctx.nil?

  span = Span.new(self, name, span_options)
  if parent.nil?
    # root span
    @sampler.sample!(span)
    span.set_tag('system.pid', Process.pid)

    if ctx && ctx.trace_id
      span.trace_id = ctx.trace_id
      span.parent_id = ctx.span_id unless ctx.span_id.nil?
    end
  else
    # child span
    span.parent = parent # sets service, trace_id, parent_id, sampled
  end
  tags.each { |k, v| span.set_tag(k, v) } unless tags.empty?
  @tags.each { |k, v| span.set_tag(k, v) } unless @tags.empty?
  span.start_time = start_time

  # this could at some point be optional (start_active_span vs start_manual_span)
  ctx.add_span(span) unless ctx.nil?

  span
end

#trace(name, options = {}) ⇒ Object

Return a span that will trace an operation called name. You could trace your code using a do-block like:

tracer.trace('web.request') do |span|
  span.service = 'my-web-site'
  span.resource = '/'
  span.set_tag('http.method', request.request_method)
  do_something()
end

The tracer.trace() method can also be used without a block in this way:

span = tracer.trace('web.request', service: 'my-web-site')
do_something()
span.finish()

Remember that in this case, calling span.finish() is mandatory.

When a Trace is started, trace() will store the created span; subsequent spans will become it’s children and will inherit some properties:

parent = tracer.trace('parent')     # has no parent span
child  = tracer.trace('child')      # is a child of 'parent'
child.finish()
parent.finish()
parent2 = tracer.trace('parent2')   # has no parent span
parent2.finish()

Available options are:

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

  • tags: extra tags which should be added to the span.



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/ddtrace/tracer.rb', line 286

def trace(name, options = {})
  options[:child_of] = call_context

  # call the finish only if a block is given; this ensures
  # that a call to tracer.trace() without a block, returns
  # a span that should be manually finished.
  if block_given?
    span = nil
    return_value = nil

    begin
      begin
        span = start_span(name, options)
      # rubocop:disable Lint/UselessAssignment
      rescue StandardError => e
        Datadog::Tracer.log.debug('Failed to start span: #{e}')
      ensure
        return_value = yield(span)
      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
      (options[:on_error] || DEFAULT_ON_ERROR).call(span, e)
      raise e
    ensure
      span.finish unless span.nil?
    end

    return_value
  else
    start_span(name, options)
  end
end