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] Span and SpanContext implementation #472

Merged
merged 8 commits into from
Jun 22, 2018
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(
Copy link
Member

Choose a reason for hiding this comment

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

👍

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)
Copy link
Member

Choose a reason for hiding this comment

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

What about the other error fields (stack, event, etc)? Or does error.object basically encapsulate all of these?

All standard log fields:
https://github.com/opentracing/specification/blob/master/semantic_conventions.md#log-fields-table

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Datadog::Span#set_error only accepts an StandardError object, which contains the message, stack, and type. That function then pulls those parts out and tags the span with them appropriately.

# 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
Copy link
Member

Choose a reason for hiding this comment

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

👍

@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