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.

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.



63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/ddtrace/tracer.rb', line 63

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

  @buffer = Datadog::SpanBuffer.new()

  @mutex = Mutex.new
  @spans = []
  @services = {}
  @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.



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

def default_service
  return @default_service if @default_service
  begin
    @default_service = File.basename($PROGRAM_NAME, '.*')
  rescue => 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.



20
21
22
# File 'lib/ddtrace/tracer.rb', line 20

def enabled
  @enabled
end

#samplerObject (readonly)

Returns the value of attribute sampler.



19
20
21
# File 'lib/ddtrace/tracer.rb', line 19

def sampler
  @sampler
end

#servicesObject (readonly)

Returns the value of attribute services.



19
20
21
# File 'lib/ddtrace/tracer.rb', line 19

def services
  @services
end

#tagsObject (readonly)

Returns the value of attribute tags.



19
20
21
# File 'lib/ddtrace/tracer.rb', line 19

def tags
  @tags
end

#writerObject (readonly)

Returns the value of attribute writer.



19
20
21
# File 'lib/ddtrace/tracer.rb', line 19

def writer
  @writer
end

Class Method Details

.debug_loggingObject

Return if the debug mode is activated or not



54
55
56
# File 'lib/ddtrace/tracer.rb', line 54

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

.debug_logging=(value) ⇒ Object

Activate the debug mode providing more information related to tracer usage



49
50
51
# File 'lib/ddtrace/tracer.rb', line 49

def self.debug_logging=(value)
  log.level = value ? Logger::DEBUG : Logger::WARN
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.



25
26
27
28
29
30
31
# File 'lib/ddtrace/tracer.rb', line 25

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.



34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ddtrace/tracer.rb', line 34

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_spanObject

Return the current active span or nil.



237
238
239
# File 'lib/ddtrace/tracer.rb', line 237

def active_span
  @buffer.get()
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')


87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ddtrace/tracer.rb', line 87

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

  @enabled = enabled unless enabled.nil?
  @writer.transport.hostname = hostname unless hostname.nil?
  @writer.transport.port = port unless port.nil?
  @sampler = sampler unless sampler.nil?
end

#record(span) ⇒ Object

Record the given finished span in the spans list. When a span is recorded, it will be sent to the Datadog trace agent as soon as the trace is finished.



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
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/ddtrace/tracer.rb', line 199

def record(span)
  span.service ||= default_service

  spans = []
  @mutex.synchronize do
    @spans << span
    parent = span.parent
    # Bubble up until we find a non-finished parent. This is necessary for
    # the case when the parent finished after its parent.
    parent = parent.parent while !parent.nil? && parent.finished?
    @buffer.set(parent)

    return unless parent.nil?

    # In general, all spans within the buffer belong to the same trace.
    # But in heavily multithreaded contexts and/or when using lots of callbacks
    # hooks and other non-linear programming style, one can technically
    # end up in different situations. So we only extract the spans which
    # are associated to the root span that just finished, and save the
    # others for later.
    trace_spans = []
    alien_spans = []
    @spans.each do |s|
      if s.trace_id == span.trace_id
        trace_spans << s
      else
        alien_spans << s
      end
    end
    spans = trace_spans
    @spans = alien_spans
  end

  return if spans.empty? || !span.sampled
  write(spans)
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')


102
103
104
105
106
107
108
109
110
# File 'lib/ddtrace/tracer.rb', line 102

def set_service_info(service, app, app_type)
  @services[service] = {
    'app' => app,
    'app_type' => app_type
  }

  return unless Datadog::Tracer.debug_logging
  Datadog::Tracer.log.debug("set_service_info: service: #{service} app: #{app} type: #{app_type}")
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')


131
132
133
# File 'lib/ddtrace/tracer.rb', line 131

def set_tags(tags)
  @tags.update(tags)
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()


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

def trace(name, options = {})
  span = Span.new(self, name, options)

  # set up inheritance
  parent = @buffer.get()
  span.set_parent(parent)
  @buffer.set(span)

  @tags.each { |k, v| span.set_tag(k, v) } unless @tags.empty?

  # sampling
  if parent.nil?
    @sampler.sample(span)
  else
    span.sampled = span.parent.sampled
  end

  # 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?
    begin
      yield(span)
    rescue StandardError => e
      span.set_error(e)
      raise
    ensure
      span.finish()
    end
  else
    span
  end
end