Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OpenTracing] Context propagation implementation #495

Merged
merged 4 commits into from
Aug 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a possible issue with round-trip propagation here. I'm anticipating there's a case where you could have baggage items with names containing either -s or uppercase characters. These should serialize to the carrier without issue, however, they'd probably fail to be extracted back to their original form, because how Rack forces headers to uppercase and underscores.

e.g. My-Baggage-Item --> #inject --> HTTP_OT_BAGGAGE_MY_BAGGAGE_ITEM --> #extract --> my_baggage_item.

I'm not sure this is possible to correct for, because we lose resolution on whether the key should have these dashes or uppercase characters when it goes through this conversion. Just something to watch out for.

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