diff --git a/lib/ddtrace/context.rb b/lib/ddtrace/context.rb index f5e458279d1..1fba86a440f 100644 --- a/lib/ddtrace/context.rb +++ b/lib/ddtrace/context.rb @@ -14,6 +14,8 @@ module Datadog # # This data structure is thread-safe. class Context + attr_accessor :sampling_priority + # Initialize a new thread-safe \Context. def initialize @mutex = Mutex.new diff --git a/lib/ddtrace/contrib/faraday/middleware.rb b/lib/ddtrace/contrib/faraday/middleware.rb index a5bc3545b56..9d7ff64eeef 100644 --- a/lib/ddtrace/contrib/faraday/middleware.rb +++ b/lib/ddtrace/contrib/faraday/middleware.rb @@ -8,6 +8,8 @@ module Contrib module Faraday # Middleware implements a faraday-middleware for ddtrace instrumentation class Middleware < ::Faraday::Middleware + include Ext::DistributedTracing + DEFAULT_ERROR_HANDLER = lambda do |env| Ext::HTTP::ERROR_RANGE.cover?(env[:status]) end @@ -54,10 +56,10 @@ def handle_response(span, env) end def propagate!(span, env) - env[:request_headers].merge!( - Ext::DistributedTracing::HTTP_HEADER_TRACE_ID => span.trace_id.to_s, - Ext::DistributedTracing::HTTP_HEADER_PARENT_ID => span.span_id.to_s - ) + env[:request_headers][HTTP_HEADER_TRACE_ID] = span.trace_id.to_s + env[:request_headers][HTTP_HEADER_PARENT_ID] = span.span_id.to_s + return unless span.sampling_priority + env[:request_headers][HTTP_HEADER_SAMPLING_PRIORITY] = span.sampling_priority.to_s end def dd_pin diff --git a/lib/ddtrace/contrib/http/patcher.rb b/lib/ddtrace/contrib/http/patcher.rb index 09d6dc3ed71..6dfed3aba2f 100644 --- a/lib/ddtrace/contrib/http/patcher.rb +++ b/lib/ddtrace/contrib/http/patcher.rb @@ -132,6 +132,9 @@ def request(req, body = nil, &block) # :yield: +response+ unless Datadog::Contrib::HTTP.should_skip_distributed_tracing?(pin) req.add_field(Datadog::Ext::DistributedTracing::HTTP_HEADER_TRACE_ID, span.trace_id) req.add_field(Datadog::Ext::DistributedTracing::HTTP_HEADER_PARENT_ID, span.span_id) + if span.sampling_priority + req.add_field(Datadog::Ext::DistributedTracing::HTTP_HEADER_SAMPLING_PRIORITY, span.sampling_priority) + end end rescue StandardError => e Datadog::Tracer.log.error("error preparing span for http request: #{e}") diff --git a/lib/ddtrace/contrib/rack/middlewares.rb b/lib/ddtrace/contrib/rack/middlewares.rb index 254c3ee6f13..f7021ae9495 100644 --- a/lib/ddtrace/contrib/rack/middlewares.rb +++ b/lib/ddtrace/contrib/rack/middlewares.rb @@ -1,21 +1,12 @@ require 'ddtrace/ext/app_types' require 'ddtrace/ext/http' -require 'ddtrace/distributed' +require 'ddtrace/distributed_headers' module Datadog module Contrib # Rack module includes middlewares that are required to trace any framework # and application built on top of Rack. module Rack - # RACK headers to test when doing distributed tracing. - # They are slightly different from real headers as Rack uppercases everything - - # Header used to transmit the trace ID. - HTTP_HEADER_TRACE_ID = 'HTTP_X_DATADOG_TRACE_ID'.freeze - - # Header used to transmit the parent ID. - HTTP_HEADER_PARENT_ID = 'HTTP_X_DATADOG_PARENT_ID'.freeze - # TraceMiddleware ensures that the Rack Request is properly traced # from the beginning to the end. The middleware adds the request span # in the Rack environment so that it can be retrieved by the underlying @@ -71,16 +62,15 @@ def call(env) request_span = @tracer.trace('rack.request', trace_options) if @distributed_tracing_enabled - # Merge distributed trace ids if present - # - # Use integer values for tests, as it will catch both - # a non-existing header or a badly formed one. - trace_id, parent_id = Datadog::Distributed.parse_trace_headers( - env[Datadog::Contrib::Rack::HTTP_HEADER_TRACE_ID], - env[Datadog::Contrib::Rack::HTTP_HEADER_PARENT_ID] - ) - request_span.trace_id = trace_id unless trace_id.nil? - request_span.parent_id = parent_id unless parent_id.nil? + headers = DistributedHeaders.new(env) + + if headers.valid? + @tracer.provider.context.sampling_priority = headers.sampling_priority + + request_span.trace_id = headers.trace_id + request_span.parent_id = headers.parent_id + request_span.sampling_priority = headers.sampling_priority + end end env[:datadog_rack_request_span] = request_span diff --git a/lib/ddtrace/distributed.rb b/lib/ddtrace/distributed.rb deleted file mode 100644 index 9e268962c67..00000000000 --- a/lib/ddtrace/distributed.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'ddtrace/span' - -module Datadog - # Common code related to distributed tracing. - module Distributed - module_function - - # Parses a trace_id and a parent_id, typically sent as headers in - # a distributed tracing context, and returns a couple of trace_id,parent_id - # which are garanteed to be both non-zero. This does not 100% ensure they - # are valid (after all, the caller could mess up data) but at least it - # sorts out most common errors, such as syntax, nil values, etc. - # Both headers must be set, else nil values are returned, for both. - # Reports problem on debug log. - def parse_trace_headers(trace_id_header, parent_id_header) - return nil, nil if trace_id_header.nil? || parent_id_header.nil? - trace_id = trace_id_header.to_i - parent_id = parent_id_header.to_i - if trace_id.zero? - Datadog::Tracer.log.debug("invalid trace_id header: #{trace_id_header}") - return nil, nil - end - if parent_id.zero? - Datadog::Tracer.log.debug("invalid parent_id header: #{parent_id_header}") - return nil, nil - end - if trace_id < 0 || trace_id >= Datadog::Span::MAX_ID - Datadog::Tracer.log.debug("trace_id out of range: #{trace_id_header}") - return nil, nil - end - if parent_id < 0 || parent_id >= Datadog::Span::MAX_ID - Datadog::Tracer.log.debug("parent_id out of range: #{parent_id_header}") - return nil, nil - end - [trace_id, parent_id] - end - end -end diff --git a/lib/ddtrace/distributed_headers.rb b/lib/ddtrace/distributed_headers.rb new file mode 100644 index 00000000000..09c53fd9958 --- /dev/null +++ b/lib/ddtrace/distributed_headers.rb @@ -0,0 +1,43 @@ +require 'ddtrace/span' +require 'ddtrace/ext/distributed' + +module Datadog + # DistributedHeaders provides easy access and validation to headers + class DistributedHeaders + include Ext::DistributedTracing + + def initialize(env) + @env = env + end + + def valid? + trace_id && parent_id && sampling_priority + end + + def trace_id + value = header(HTTP_HEADER_TRACE_ID).to_i + return if value <= 0 || value >= Span::MAX_ID + value + end + + def parent_id + value = header(HTTP_HEADER_PARENT_ID).to_i + return if value <= 0 || value >= Span::MAX_ID + value + end + + def sampling_priority + value = header(HTTP_HEADER_SAMPLING_PRIORITY).to_f + return unless SAMPLING_PRIORITY_RANGE.include?(value) + value + end + + private + + def header(name) + rack_header = "http-#{name}".upcase!.tr('-', '_') + + @env[rack_header] + end + end +end diff --git a/lib/ddtrace/ext/distributed.rb b/lib/ddtrace/ext/distributed.rb index 9fcfc9b99ce..675c0ffaff3 100644 --- a/lib/ddtrace/ext/distributed.rb +++ b/lib/ddtrace/ext/distributed.rb @@ -5,6 +5,8 @@ module DistributedTracing # These are cross-language (eg: Python, Go and other implementations should honor these) HTTP_HEADER_TRACE_ID = 'x-datadog-trace-id'.freeze HTTP_HEADER_PARENT_ID = 'x-datadog-parent-id'.freeze + HTTP_HEADER_SAMPLING_PRIORITY = 'x-datadog-sampling-priority'.freeze + SAMPLING_PRIORITY_RANGE = 0...2 end end end diff --git a/lib/ddtrace/span.rb b/lib/ddtrace/span.rb index 834e2a56015..fecd6010d9f 100644 --- a/lib/ddtrace/span.rb +++ b/lib/ddtrace/span.rb @@ -22,7 +22,7 @@ class Span :start_time, :end_time, :span_id, :trace_id, :parent_id, :status, :sampled, - :tracer, :context + :tracer, :context, :sampling_priority attr_reader :parent @@ -46,6 +46,7 @@ def initialize(tracer, name, options = {}) @span_id = Datadog::Utils.next_id @parent_id = options.fetch(:parent_id, 0) @trace_id = options.fetch(:trace_id, Datadog::Utils.next_id) + @sampling_priority = options[:sampling_priority] @context = options.fetch(:context, nil) @@ -160,6 +161,7 @@ def parent=(parent) @parent_id = parent.span_id @service ||= parent.service @sampled = parent.sampled + @sampling_priority = parent.sampling_priority end end diff --git a/lib/ddtrace/tracer.rb b/lib/ddtrace/tracer.rb index fb61914a110..9a9a248689b 100644 --- a/lib/ddtrace/tracer.rb +++ b/lib/ddtrace/tracer.rb @@ -162,24 +162,12 @@ def set_tags(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 + def guess_context_and_parent(child_of) + return [call_context, nil] unless child_of - ctx ||= call_context + return [child_of, child_of.current_span] if child_of.is_a?(Context) - [ctx, parent] + [child_of.context, child_of] end # Return a span that will trace an operation called \name. This method allows @@ -202,7 +190,7 @@ def start_span(name, options = {}) [:service, :resource, :span_type].include?(k) end - ctx, parent = guess_context_and_parent(options) + ctx, parent = guess_context_and_parent(options[:child_of]) opts[:context] = ctx unless ctx.nil? span = Span.new(self, name, opts) diff --git a/test/distributed_test.rb b/test/distributed_test.rb deleted file mode 100644 index 518cac84437..00000000000 --- a/test/distributed_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'helper' -require 'ddtrace/distributed' - -class DistributedTest < Minitest::Test - def test_parse_trace_headers - test_cases = { - %w[1 2] => [1, 2], - [3, 5] => [3, 5], - %w[9223372036854775807 9223372036854775807] => [9223372036854775807, 9223372036854775807], # 2**63 - 1 - %w[9223372036854775808 9223372036854775808] => [nil, nil], # 2**63 - %w[18446744073709551615 18446744073709551615] => [nil, nil], # 2**64 - 1 - %w[18446744073709551616 18446744073709551616] => [nil, nil], # 2**64 - %w[1000000000000000000000 1000000000000000000000] => [nil, nil], - %w[abc def] => [nil, nil], - [-1 - 2] => [nil, nil], - %w[-1 -2] => [nil, nil], - [3, 'ooops'] => [nil, nil], - ['ooops', 5] => [nil, nil], - [3, nil] => [nil, nil], - [nil, 5] => [nil, nil], - [nil, nil] => [nil, nil] - } - test_cases.each do |k, v| - trace_id, parent_id = Datadog::Distributed.parse_trace_headers(k[0], k[1]) - w = [trace_id, parent_id] - if v[0].nil? - assert_nil(trace_id, "unexpected trace_id (#{k} should return #{v} but got #{w})") - else - assert_equal(v[0], trace_id, "trace_id should match (#{k} should return #{v} but got #{w})") - end - if v[1].nil? - assert_nil(parent_id, "unexpected parent_id (#{k} should return #{v} but got #{w})") - else - assert_equal(v[1], parent_id, "parent_id should match (#{k} should return #{v} but got #{w})") - end - end - end -end