lib/ddtrace/tracer.rb in ddtrace-0.7.2 vs lib/ddtrace/tracer.rb in ddtrace-0.8.0
- old
+ new
@@ -2,21 +2,23 @@
require 'thread'
require 'logger'
require 'pathname'
require 'ddtrace/span'
-require 'ddtrace/buffer'
+require 'ddtrace/context'
+require 'ddtrace/provider'
require 'ddtrace/logger'
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.
+ # rubocop:disable Metrics/ClassLength
class Tracer
attr_reader :writer, :sampler, :services, :tags
attr_accessor :enabled
attr_writer :default_service
@@ -53,24 +55,34 @@
# Return if the debug mode is activated or not
def self.debug_logging
log.level == Logger::DEBUG
end
+ # 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.
+ def call_context
+ @provider.context
+ 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()
+ @provider = options.fetch(:context_provider, Datadog::DefaultContextProvider.new)
+ @provider ||= Datadog::DefaultContextProvider.new # @provider should never be nil
@mutex = Mutex.new
- @spans = []
@services = {}
@tags = {}
end
# Updates the current \Tracer instance, so that the tracer can be configured after the
@@ -111,14 +123,14 @@
# 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.
def default_service
- return @default_service if @default_service
+ return @default_service if instance_variable_defined?(:@default_service) && @default_service
begin
@default_service = File.basename($PROGRAM_NAME, '.*')
- rescue => e
+ rescue StandardError => e
Datadog::Tracer.log.error("unable to guess default service: #{e}")
@default_service = 'ruby'.freeze
end
@default_service
end
@@ -130,10 +142,73 @@
# tracer.set_tags('env' => 'prod', 'component' => 'core')
def set_tags(tags)
@tags.update(tags)
end
+ # Guess context and parent from child_of entry.
+ def guess_context_and_parent(options = {})
+ child_of = options.fetch(:child_of, nil) # can be context or span
+
+ ctx = nil
+ parent = nil
+ unless child_of.nil?
+ if child_of.respond_to?(:current_span)
+ ctx = child_of
+ parent = child_of.current_span
+ elsif child_of.is_a?(Datadog::Span)
+ parent = child_of
+ ctx = child_of.context
+ end
+ end
+
+ ctx ||= call_context
+
+ [ctx, parent]
+ end
+
+ # 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.
+ def start_span(name, options = {})
+ start_time = options.fetch(:start_time, Time.now.utc)
+ tags = options.fetch(:tags, {})
+
+ opts = options.select do |k, _v|
+ # Filter options, we want no side effects with unexpected args.
+ # Plus, this documents the code (Ruby 2 named args would be better but we're Ruby 1.9 compatible)
+ [:service, :resource, :span_type].include?(k)
+ end
+
+ ctx, parent = guess_context_and_parent(options)
+ opts[:context] = ctx unless ctx.nil?
+
+ span = Span.new(self, name, opts)
+ if parent.nil?
+ # root span
+ @sampler.sample(span)
+ span.set_tag('system.pid', Process.pid)
+ 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
+
# Return a +span+ that will trace an operation called +name+. You could trace your code
# using a <tt>do-block</tt> like:
#
# tracer.trace('web.request') do |span|
# span.service = 'my-web-site'
@@ -158,97 +233,78 @@
# 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.
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
+ opts = options.select do |k, _v|
+ # Filter options, we want no side effects with unexpected args.
+ # Plus, this documents the code (Ruby 2 named args would be better but we're Ruby 1.9 compatible)
+ [:service, :resource, :span_type, :tags].include?(k)
end
+ opts[:child_of] = call_context
+ span = start_span(name, opts)
# 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
+ # 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
span.set_error(e)
- raise
+ raise e
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)
- 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)
+ # 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+.
+ def record(context)
+ context = context.context if context.is_a?(Datadog::Span)
+ return if context.nil?
+ trace, sampled = context.get
+ ready = !trace.nil? && !trace.empty? && sampled
+ write(trace) if ready
end
# Return the current active span or +nil+.
def active_span
- @buffer.get()
+ call_context.current_span
end
- def write(spans)
+ # Send the trace to the writer to enqueue the spans list in the agent
+ # sending queue.
+ def write(trace)
return if @writer.nil? || !@enabled
if Datadog::Tracer.debug_logging
- Datadog::Tracer.log.debug("Writing #{spans.length} spans (enabled: #{@enabled})")
- PP.pp(spans)
+ Datadog::Tracer.log.debug("Writing #{trace.length} spans (enabled: #{@enabled})")
+ str = String.new('')
+ PP.pp(trace, str)
+ Datadog::Tracer.log.debug(str)
end
- @writer.write(spans, @services)
+ @writer.write(trace, @services)
end
- private :write
+ private :write, :guess_context_and_parent
end
end