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