Skip to content

Commit

Permalink
[OpenTracing] Context propagation implementation (#495)
Browse files Browse the repository at this point in the history
* Added: Propagators to OpenTracer.

* Added: Specs for OpenTracer::Propagators.

* Fixed: OpenTracer::Tracer#start_span not using provided SpanContext.

* Added: OpenTracer context propagation integration specs.
  • Loading branch information
delner committed Aug 9, 2018
1 parent 55f786c commit 363f4ef
Show file tree
Hide file tree
Showing 14 changed files with 973 additions and 22 deletions.
5 changes: 5 additions & 0 deletions lib/ddtrace/opentracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ def load_opentracer
require 'ddtrace/opentracer/scope_manager'
require 'ddtrace/opentracer/thread_local_scope'
require 'ddtrace/opentracer/thread_local_scope_manager'
require 'ddtrace/opentracer/distributed_headers'
require 'ddtrace/opentracer/propagator'
require 'ddtrace/opentracer/text_map_propagator'
require 'ddtrace/opentracer/binary_propagator'
require 'ddtrace/opentracer/rack_propagator'
require 'ddtrace/opentracer/global_tracer'

# Modify the OpenTracing module functions
Expand Down
24 changes: 24 additions & 0 deletions lib/ddtrace/opentracer/binary_propagator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Datadog
module OpenTracer
# OpenTracing propagator for Datadog::OpenTracer::Tracer
module BinaryPropagator
extend Propagator

# Inject a SpanContext into the given carrier
#
# @param span_context [SpanContext]
# @param carrier [Carrier] A carrier object of Binary type
def self.inject(span_context, carrier)
nil
end

# Extract a SpanContext in Binary format from the given carrier.
#
# @param carrier [Carrier] A carrier object of Binary type
# @return [SpanContext, nil] the extracted SpanContext or nil if none could be found
def self.extract(carrier)
SpanContext::NOOP_INSTANCE
end
end
end
end
42 changes: 42 additions & 0 deletions lib/ddtrace/opentracer/distributed_headers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require 'ddtrace/span'
require 'ddtrace/ext/distributed'

module Datadog
module OpenTracer
# DistributedHeaders provides easy access and validation to headers
class DistributedHeaders
include Datadog::Ext::DistributedTracing

def initialize(carrier)
@carrier = carrier
end

def valid?
# Sampling priority is optional.
!trace_id.nil? && !parent_id.nil?
end

def trace_id
value = @carrier[HTTP_HEADER_TRACE_ID].to_i
return if value <= 0 || value >= Datadog::Span::MAX_ID
value
end

def parent_id
value = @carrier[HTTP_HEADER_PARENT_ID].to_i
return if value <= 0 || value >= Datadog::Span::MAX_ID
value
end

def sampling_priority
hdr = @carrier[HTTP_HEADER_SAMPLING_PRIORITY]
# It's important to make a difference between no header,
# and a header defined to zero.
return unless hdr
value = hdr.to_i
return if value < 0
value
end
end
end
end
22 changes: 22 additions & 0 deletions lib/ddtrace/opentracer/propagator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Datadog
module OpenTracer
# OpenTracing propagator for Datadog::OpenTracer::Tracer
module Propagator
# Inject a SpanContext into the given carrier
#
# @param span_context [SpanContext]
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
def inject(span_context, carrier)
raise NotImplementedError
end

# Extract a SpanContext in the given format from the given carrier.
#
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
# @return [SpanContext, nil] the extracted SpanContext or nil if none could be found
def extract(carrier)
raise NotImplementedError
end
end
end
end
60 changes: 60 additions & 0 deletions lib/ddtrace/opentracer/rack_propagator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'ddtrace/propagation/http_propagator'

module Datadog
module OpenTracer
# OpenTracing propagator for Datadog::OpenTracer::Tracer
module RackPropagator
extend Propagator
extend Datadog::Ext::DistributedTracing
include Datadog::Ext::DistributedTracing

BAGGAGE_PREFIX = 'ot-baggage-'.freeze
BAGGAGE_PREFIX_FORMATTED = 'HTTP_OT_BAGGAGE_'.freeze

class << self
# Inject a SpanContext into the given carrier
#
# @param span_context [SpanContext]
# @param carrier [Carrier] A carrier object of Rack type
def inject(span_context, carrier)
# Inject Datadog trace properties
Datadog::HTTPPropagator.inject!(span_context.datadog_context, carrier)

# Inject baggage
span_context.baggage.each do |key, value|
carrier[BAGGAGE_PREFIX + key] = value
end

nil
end

# Extract a SpanContext in Rack format from the given carrier.
#
# @param carrier [Carrier] A carrier object of Rack type
# @return [SpanContext, nil] the extracted SpanContext or nil if none could be found
def extract(carrier)
# First extract & build a Datadog context
datadog_context = Datadog::HTTPPropagator.extract(carrier)

# Then extract any other baggage
baggage = {}
carrier.each do |key, value|
baggage[header_to_baggage(key)] = value if baggage_header?(key)
end

SpanContextFactory.build(datadog_context: datadog_context, baggage: baggage)
end

private

def baggage_header?(header)
header.start_with?(BAGGAGE_PREFIX_FORMATTED)
end

def header_to_baggage(key)
key[BAGGAGE_PREFIX_FORMATTED.length, key.length].downcase
end
end
end
end
end
73 changes: 73 additions & 0 deletions lib/ddtrace/opentracer/text_map_propagator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require 'ddtrace/ext/distributed'

module Datadog
module OpenTracer
# OpenTracing propagator for Datadog::OpenTracer::Tracer
module TextMapPropagator
extend Propagator
extend Datadog::Ext::DistributedTracing
include Datadog::Ext::DistributedTracing

BAGGAGE_PREFIX = 'ot-baggage-'.freeze

class << self
# Inject a SpanContext into the given carrier
#
# @param span_context [SpanContext]
# @param carrier [Carrier] A carrier object of Rack type
def inject(span_context, carrier)
# Inject Datadog trace properties
span_context.datadog_context.tap do |datadog_context|
carrier[HTTP_HEADER_TRACE_ID] = datadog_context.trace_id
carrier[HTTP_HEADER_PARENT_ID] = datadog_context.span_id
carrier[HTTP_HEADER_SAMPLING_PRIORITY] = datadog_context.sampling_priority
end

# Inject baggage
span_context.baggage.each do |key, value|
carrier[BAGGAGE_PREFIX + key] = value
end

nil
end

# Extract a SpanContext in TextMap format from the given carrier.
#
# @param carrier [Carrier] A carrier object of TextMap type
# @return [SpanContext, nil] the extracted SpanContext or nil if none could be found
def extract(carrier)
# First extract & build a Datadog context
headers = DistributedHeaders.new(carrier)

datadog_context = if headers.valid?
Datadog::Context.new(
trace_id: headers.trace_id,
span_id: headers.parent_id,
sampling_priority: headers.sampling_priority
)
else
Datadog::Context.new
end

# Then extract any other baggage
baggage = {}
carrier.each do |key, value|
baggage[item_to_baggage(key)] = value if baggage_item?(key)
end

SpanContextFactory.build(datadog_context: datadog_context, baggage: baggage)
end

private

def baggage_item?(item)
item.start_with?(BAGGAGE_PREFIX)
end

def item_to_baggage(key)
key[BAGGAGE_PREFIX.length, key.length]
end
end
end
end
end
39 changes: 20 additions & 19 deletions lib/ddtrace/opentracer/tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,6 @@ def start_span(operation_name,
start_time: Time.now,
tags: nil,
ignore_active_scope: false)
# Get the parent Datadog span
parent_datadog_span = case child_of
when Span
child_of.datadog_span
else
ignore_active_scope ? nil : scope_manager.active && scope_manager.active.span.datadog_span
end

# Build the new Datadog span
datadog_span = datadog_tracer.start_span(
operation_name,
child_of: parent_datadog_span,
start_time: start_time,
tags: tags || {}
)

# Derive the OpenTracer::SpanContext to inherit from
parent_span_context = case child_of
Expand All @@ -126,6 +111,14 @@ def start_span(operation_name,
ignore_active_scope ? nil : scope_manager.active && scope_manager.active.span.context
end

# Build the new Datadog span
datadog_span = datadog_tracer.start_span(
operation_name,
child_of: parent_span_context && parent_span_context.datadog_context,
start_time: start_time,
tags: tags || {}
)

# Build or extend the OpenTracer::SpanContext
span_context = if parent_span_context
SpanContextFactory.clone(span_context: parent_span_context)
Expand All @@ -144,8 +137,12 @@ def start_span(operation_name,
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
def inject(span_context, format, carrier)
case format
when OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK
return nil
when OpenTracing::FORMAT_TEXT_MAP
TextMapPropagator.inject(span_context, carrier)
when OpenTracing::FORMAT_BINARY
BinaryPropagator.inject(span_context, carrier)
when OpenTracing::FORMAT_RACK
RackPropagator.inject(span_context, carrier)
else
warn 'Unknown inject format'
end
Expand All @@ -158,8 +155,12 @@ def inject(span_context, format, carrier)
# @return [SpanContext, nil] the extracted SpanContext or nil if none could be found
def extract(format, carrier)
case format
when OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK
return SpanContext::NOOP_INSTANCE
when OpenTracing::FORMAT_TEXT_MAP
TextMapPropagator.extract(carrier)
when OpenTracing::FORMAT_BINARY
BinaryPropagator.extract(carrier)
when OpenTracing::FORMAT_RACK
RackPropagator.extract(carrier)
else
warn 'Unknown extract format'
nil
Expand Down
23 changes: 23 additions & 0 deletions spec/ddtrace/opentracer/binary_propagator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'spec_helper'

require 'ddtrace/opentracer'
require 'ddtrace/opentracer/helper'

if Datadog::OpenTracer.supported?
RSpec.describe Datadog::OpenTracer::BinaryPropagator do
include_context 'OpenTracing helpers'

describe '#inject' do
subject { described_class.inject(span_context, carrier) }
let(:span_context) { instance_double(Datadog::OpenTracer::SpanContext) }
let(:carrier) { instance_double(Datadog::OpenTracer::Carrier) }
it { is_expected.to be nil }
end

describe '#extract' do
subject(:span_context) { described_class.extract(carrier) }
let(:carrier) { instance_double(Datadog::OpenTracer::Carrier) }
it { is_expected.to be(Datadog::OpenTracer::SpanContext::NOOP_INSTANCE) }
end
end
end
Loading

0 comments on commit 363f4ef

Please sign in to comment.