Skip to content

Commit

Permalink
[OpenTracing] Span and SpanContext implementation (#472)
Browse files Browse the repository at this point in the history
* Added: Datadog::OpenTracer::SpanContext implementation.

* Added: Datadog::OpenTracer::Span implementation.

* Changed: Bump required Ruby version for OpenTracing to 2.1.

* Added: Datadog::OpenTracer::SpanContextFactory.

* Changed: OpenTracer::Span#set_baggage_item to create new SpanContexts.

* Changed: Datadog::OpenTracer::SpanContext#baggage to be immutable.

* Added: span_id, trace_id, parent_id to Datadog::OpenTracer::SpanContext.

* Changed: Replaced span_id, trace_id, parent_id with reference to Datadog::Context.
  • Loading branch information
delner committed Jul 16, 2018
1 parent d33dea0 commit 1d68de4
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 19 deletions.
3 changes: 2 additions & 1 deletion lib/ddtrace/opentracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module OpenTracer
module_function

def supported?
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.0')
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.1')
end

def load_opentracer
Expand All @@ -15,6 +15,7 @@ def load_opentracer
require 'ddtrace/opentracer/tracer'
require 'ddtrace/opentracer/span'
require 'ddtrace/opentracer/span_context'
require 'ddtrace/opentracer/span_context_factory'
require 'ddtrace/opentracer/scope'
require 'ddtrace/opentracer/scope_manager'
require 'ddtrace/opentracer/global_tracer'
Expand Down
84 changes: 84 additions & 0 deletions lib/ddtrace/opentracer/span.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,90 @@
module Datadog
module OpenTracer
# OpenTracing adapter for Datadog::Span
class Span < ::OpenTracing::Span
attr_reader \
:datadog_span

def initialize(datadog_span:, span_context:)
@datadog_span = datadog_span
@span_context = span_context
end

# Set the name of the operation
#
# @param [String] name
def operation_name=(name)
datadog_span.name = name
end

# Span Context
#
# @return [SpanContext]
def context
@span_context
end

# Set a tag value on this span
# @param key [String] the key of the tag
# @param value [String, Numeric, Boolean] the value of the tag. If it's not
# a String, Numeric, or Boolean it will be encoded with to_s
def set_tag(key, value)
tap { datadog_span.set_tag(key, value) }
end

# Set a baggage item on the span
# @param key [String] the key of the baggage item
# @param value [String] the value of the baggage item
def set_baggage_item(key, value)
tap do
# SpanContext is immutable, so to make changes
# build a new span context.
@span_context = SpanContextFactory.clone(
span_context: context,
baggage: { key => value }
)
end
end

# Get a baggage item
# @param key [String] the key of the baggage item
# @return [String] value of the baggage item
def get_baggage_item(key)
context.baggage[key]
end

# @deprecated Use {#log_kv} instead.
# Reason: event is an optional standard log field defined in spec and not required. Also,
# method name {#log_kv} is more consistent with other language implementations such as Python and Go.
#
# Add a log entry to this span
# @param event [String] event name for the log
# @param timestamp [Time] time of the log
# @param fields [Hash] Additional information to log
def log(event: nil, timestamp: Time.now, **fields)
super # Log deprecation warning

# If the fields specify an error
if fields.key?(:'error.object')
datadog_span.set_error(fields[:'error.object'])
end
end

# Add a log entry to this span
# @param timestamp [Time] time of the log
# @param fields [Hash] Additional information to log
def log_kv(timestamp: Time.now, **fields)
# If the fields specify an error
if fields.key?(:'error.object')
datadog_span.set_error(fields[:'error.object'])
end
end

# Finish the {Span}
# @param end_time [Time] custom end time, if not now
def finish(end_time: Time.now)
datadog_span.finish(end_time)
end
end
end
end
8 changes: 8 additions & 0 deletions lib/ddtrace/opentracer/span_context.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
module Datadog
module OpenTracer
# OpenTracing adapter for SpanContext
class SpanContext < ::OpenTracing::SpanContext
attr_reader \
:datadog_context

def initialize(datadog_context:, baggage: {})
@datadog_context = datadog_context
@baggage = baggage.freeze
end
end
end
end
23 changes: 23 additions & 0 deletions lib/ddtrace/opentracer/span_context_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Datadog
module OpenTracer
# Creates new Datadog::OpenTracer::SpanContext
module SpanContextFactory
module_function

def build(datadog_context:, baggage: {})
SpanContext.new(
datadog_context: datadog_context,
baggage: baggage.dup
)
end

def clone(span_context:, baggage: {})
SpanContext.new(
datadog_context: span_context.datadog_context,
# Merge baggage from previous SpanContext
baggage: span_context.baggage.merge(baggage)
)
end
end
end
end
137 changes: 137 additions & 0 deletions spec/ddtrace/opentracer/span_context_factory_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
require 'spec_helper'

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

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

describe 'class methods' do
describe '#build' do
context 'given Datadog::Context' do
subject(:span_context) do
described_class.build(
datadog_context: datadog_context
)
end
let(:datadog_context) { instance_double(Datadog::Context) }

it { is_expected.to be_a_kind_of(Datadog::OpenTracer::SpanContext) }

describe 'builds a SpanContext where' do
it { expect(span_context.datadog_context).to be(datadog_context) }

describe '#baggage' do
subject(:baggage) { span_context.baggage }
it { is_expected.to be_a_kind_of(Hash) }
it { is_expected.to be_empty }
end
end

context 'and baggage' do
subject(:span_context) do
described_class.build(
datadog_context: datadog_context,
baggage: original_baggage
)
end
let(:original_baggage) { { 'account_id' => '1234' } }

it { is_expected.to be_a_kind_of(Datadog::OpenTracer::SpanContext) }

describe 'builds a SpanContext where' do
it { expect(span_context.datadog_context).to be(datadog_context) }

describe '#baggage' do
subject(:baggage) { span_context.baggage }
it { is_expected.to be_a_kind_of(Hash) }

context 'when the original baggage contains data' do
it { is_expected.to include('account_id' => '1234') }
it { is_expected.to_not be(original_baggage) }
end
end
end
end
end
end

describe '#clone' do
context 'given a SpanContext' do
subject(:span_context) { described_class.clone(span_context: original_span_context) }
let(:original_span_context) do
instance_double(
Datadog::OpenTracer::SpanContext,
datadog_context: original_datadog_context,
baggage: original_baggage
)
end
let(:original_datadog_context) { instance_double(Datadog::Context) }
let(:original_baggage) { {} }

it { is_expected.to be_a_kind_of(Datadog::OpenTracer::SpanContext) }

describe 'builds a SpanContext where' do
it { expect(span_context.datadog_context).to be(original_datadog_context) }

describe '#baggage' do
subject(:baggage) { span_context.baggage }
it { is_expected.to be_a_kind_of(Hash) }

context 'when the original SpanContext contains baggage' do
let(:original_baggage) { { 'org_id' => '4321' } }
it { is_expected.to include('org_id' => '4321') }
it { is_expected.to_not be(original_baggage) }
end
end
end

context 'and baggage' do
subject(:span_context) { described_class.clone(span_context: original_span_context, baggage: param_baggage) }
let(:param_baggage) { {} }

it { is_expected.to be_a_kind_of(Datadog::OpenTracer::SpanContext) }

describe 'builds a SpanContext where' do
describe '#baggage' do
subject(:baggage) { span_context.baggage }
it { is_expected.to be_a_kind_of(Hash) }

context 'when the original SpanContext contains baggage' do
let(:original_baggage) { { 'org_id' => '4321' } }
it { is_expected.to include('org_id' => '4321') }
it { is_expected.to_not be(original_baggage) }
end

context 'when the original baggage contains data' do
let(:param_baggage) { { 'account_id' => '1234' } }
it { is_expected.to include('account_id' => '1234') }
it { is_expected.to_not be(param_baggage) }
end

context 'when the original SpanContext baggage and param baggage contains data' do
context 'that doesn\'t overlap' do
let(:original_baggage) { { 'org_id' => '4321' } }
let(:param_baggage) { { 'account_id' => '1234' } }
it { is_expected.to include('org_id' => '4321', 'account_id' => '1234') }
it { is_expected.to_not be(original_baggage) }
it { is_expected.to_not be(param_baggage) }
end

context 'that overlaps' do
let(:original_baggage) { { 'org_id' => '4321' } }
let(:param_baggage) { { 'org_id' => '1234' } }
it { is_expected.to include('org_id' => '1234') }
it { is_expected.to_not be(original_baggage) }
it { is_expected.to_not be(param_baggage) }
end
end
end
end
end
end
end
end
end
end
38 changes: 31 additions & 7 deletions spec/ddtrace/opentracer/span_context_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,39 @@
RSpec.describe Datadog::OpenTracer::SpanContext do
include_context 'OpenTracing helpers'

subject(:span_context) { described_class.new }
describe '#initialize' do
context 'given a Datadog::Context' do
subject(:span_context) { described_class.new(datadog_context: datadog_context) }
let(:datadog_context) { instance_double(Datadog::Context) }

it { is_expected.to have_attributes(baggage: nil) }
it do
is_expected.to have_attributes(
datadog_context: datadog_context,
baggage: {}
)
end

describe '#initialize' do
context 'given baggage' do
subject(:span_context) { described_class.new(baggage: baggage) }
let(:baggage) { { account_id: '1234' } }
it { is_expected.to be_a_kind_of(described_class) }
context 'and baggage' do
subject(:span_context) do
described_class.new(
datadog_context: datadog_context,
baggage: original_baggage
)
end
let(:original_baggage) { { account_id: '1234' } }

it { is_expected.to be_a_kind_of(described_class) }

describe 'builds a SpanContext where' do
describe '#baggage' do
subject(:baggage) { span_context.baggage }
it { is_expected.to be(original_baggage) }
it 'is immutable' do
expect { baggage[1] = 2 }.to raise_error(RuntimeError)
end
end
end
end
end
end
end
Expand Down
Loading

0 comments on commit 1d68de4

Please sign in to comment.