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

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.


72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/ddtrace/tracer.rb', line 72

def initialize(options = {})
  # Configurable options
  @context_flush = if options[:partial_flush]
                     Datadog::ContextFlush::Partial.new(options)
                   else
                     Datadog::ContextFlush::Finished.new
                   end

  @default_service = options[:default_service]
  @enabled = options.fetch(:enabled, true)
  @provider = options.fetch(:context_provider, Datadog::DefaultContextProvider.new)
  @sampler = options.fetch(:sampler, Datadog::AllSampler.new)
  @tags = options.fetch(:tags, {})
  @writer = options.fetch(:writer, Datadog::Writer.new)

  # Instance variables
  @mutex = Mutex.new
  @provider ||= Datadog::DefaultContextProvider.new # @provider should never be nil

  # Enable priority sampling by default
  activate_priority_sampling!(@sampler)
end

Instance Attribute Details

#context_flushObject (readonly)

Returns the value of attribute context_flush


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

def context_flush
  @context_flush
end

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


145
146
147
148
149
150
151
152
153
154
# File 'lib/ddtrace/tracer.rb', line 145

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

Instance Method Details

#active_correlationObject

Return a CorrelationIdentifier for active span


338
339
340
# File 'lib/ddtrace/tracer.rb', line 338

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

#active_root_spanObject

Return the current active root span or nil.


333
334
335
# File 'lib/ddtrace/tracer.rb', line 333

def active_root_span
  call_context.current_root_span
end

#active_spanObject

Return the current active span or nil.


328
329
330
# File 'lib/ddtrace/tracer.rb', line 328

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.


63
64
65
# File 'lib/ddtrace/tracer.rb', line 63

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

  • partial_flush: enable partial trace flushing

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

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

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

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

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

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

  configure_writer(options)

  if options.key?(:partial_flush)
    @context_flush = if options[:partial_flush]
                       Datadog::ContextFlush::Partial.new(options)
                     else
                       Datadog::ContextFlush::Finished.new
                     end
  end
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.


308
309
310
311
312
313
# File 'lib/ddtrace/tracer.rb', line 308

def record(context)
  context = context.context if context.is_a?(Datadog::Span)
  return if context.nil?

  record_context(context)
end

#servicesObject


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

def services
  # Only log each deprecation warning once (safeguard against log spam)
  Datadog::Patcher.do_once('Tracer#set_service_info') do
    Datadog.logger.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


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

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.logger.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')

161
162
163
164
# File 'lib/ddtrace/tracer.rb', line 161

def set_tags(tags)
  string_tags = Hash[tags.collect { |k, v| [k.to_s, v] }]
  @tags = @tags.merge(string_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!

51
52
53
54
55
# File 'lib/ddtrace/tracer.rb', line 51

def shutdown!
  return unless @enabled

  @writer.stop unless @writer.nil?
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.


188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/ddtrace/tracer.rb', line 188

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
  span.set_tags(@tags) unless @tags.empty?
  span.set_tags(tags) 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)

  • child_of: a Span or a Context instance representing the parent for this span. If not set, defaults to Tracer.call_context

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


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
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/ddtrace/tracer.rb', line 261

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)
      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 call:
        # * `yield(span)` during normal execution.
        # * `yield(nil)` if `start_span` fails with a runtime error.
        # * 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(span) if span || 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
      (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