require 'pp'
require 'thread'
require 'logger'
require 'ddtrace/span'
require 'ddtrace/buffer'
require 'ddtrace/writer'
require 'ddtrace/sampler'
# \Datadog global namespace that includes all tracing functionality for Tracer and Span classes.
module Datadog
# 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.
class Tracer
attr_reader :writer, :sampler, :services
attr_accessor :enabled
# 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.
def self.log
unless defined? @logger
@logger = Logger.new(STDOUT)
@logger.level = Logger::WARN
end
@logger
end
# Activate the debug mode providing more information related to tracer usage
def self.debug_logging=(value)
log.level = value ? Logger::DEBUG : Logger::WARN
end
# Return if the debug mode is activated or not
def self.debug_logging
log.level == Logger::DEBUG
end
# 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.
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 = {}
end
# 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')
#
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
# Set the information about the given service. A valid example is:
#
# tracer.set_service_info('web-application', 'rails', 'web')
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
# 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()
#
def trace(name, options = {})
span = Span.new(self, name, options)
# set up inheritance
parent = @buffer.get()
span.set_parent(parent)
@buffer.set(span)
# 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
# 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.
def record(span)
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?
spans = @spans
@spans = []
end
return if spans.empty? || !span.sampled
write(spans)
end
# Return the current active span or +nil+.
def active_span
@buffer.get()
end
def write(spans)
return if @writer.nil? || !@enabled
if Datadog::Tracer.debug_logging
Datadog::Tracer.log.debug("Writing #{spans.length} spans (enabled: #{@enabled})")
PP.pp(spans)
end
@writer.write(spans, @services)
end
private :write
end
end