-
Notifications
You must be signed in to change notification settings - Fork 373
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
Changes from 3 commits
04abcab
f8910b6
c6d3007
cdb7f4b
a8ca2e1
65915bf
8e369d5
6f16be3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,83 @@ | ||
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 { context.baggage[key] = value } | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about the other error fields ( All standard log fields: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
# 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
module Datadog | ||
module OpenTracer | ||
# OpenTracing adapter for SpanContext | ||
class SpanContext < ::OpenTracing::SpanContext | ||
def initialize(baggage: {}) | ||
super | ||
@baggage = baggage | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,38 +7,52 @@ | |
RSpec.describe Datadog::OpenTracer::Span do | ||
include_context 'OpenTracing helpers' | ||
|
||
subject(:span) { described_class.new } | ||
subject(:span) { described_class.new(datadog_span: datadog_span, span_context: span_context) } | ||
let(:datadog_span) { instance_double(Datadog::Span) } | ||
let(:span_context) { instance_double(Datadog::OpenTracer::SpanContext) } | ||
|
||
describe '#operation_name=' do | ||
subject(:result) { span.operation_name = name } | ||
let(:name) { 'execute_job' } | ||
|
||
before(:each) { expect(datadog_span).to receive(:name=).with(name).and_return(name) } | ||
it { expect(result).to eq(name) } | ||
end | ||
|
||
describe '#context' do | ||
subject(:context) { span.context } | ||
it { is_expected.to be(OpenTracing::SpanContext::NOOP_INSTANCE) } | ||
it { is_expected.to be(span_context) } | ||
end | ||
|
||
describe '#set_tag' do | ||
subject(:result) { span.set_tag(key, value) } | ||
let(:key) { 'account_id' } | ||
let(:value) { '1234' } | ||
before(:each) { expect(datadog_span).to receive(:set_tag).with(key, value) } | ||
it { is_expected.to be(span) } | ||
end | ||
|
||
describe '#set_baggage_item' do | ||
subject(:result) { span.set_baggage_item(key, value) } | ||
let(:key) { 'account_id' } | ||
let(:value) { '1234' } | ||
let(:baggage) { instance_double(Hash) } | ||
|
||
before(:each) do | ||
allow(span_context).to receive(:baggage).and_return(baggage) | ||
expect(baggage).to receive(:[]=).with(key, value) | ||
end | ||
|
||
it { is_expected.to be(span) } | ||
end | ||
|
||
describe '#get_baggage_item' do | ||
subject(:result) { span.get_baggage_item(key) } | ||
let(:key) { 'account_id' } | ||
it { is_expected.to be nil } | ||
let(:value) { '1234' } | ||
let(:baggage) { { key => value } } | ||
before(:each) { allow(span_context).to receive(:baggage).and_return(baggage) } | ||
it { is_expected.to be(value) } | ||
end | ||
|
||
describe '#log' do | ||
|
@@ -47,23 +61,30 @@ | |
let(:timestamp) { Time.now } | ||
let(:fields) { { time_started: Time.now, account_id: '1234' } } | ||
|
||
before(:each) do | ||
# Expect a deprecation warning to be output. | ||
it do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could do that, and that would be okay. What matters is that the test is easy to read and understand. The language as its written reads as "it expects log to output 'Span#log is deprecated.' to stderr." In my mind, I think this is an abundantly clear statement, that does not require clarification by naming the example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not to bikesched too much. I have mixed feelings about using nameless examples. Having explicit name to a example most often than not makes it more clearer than the code itself. And while 1 line and relatively short nameless examples are fine and easy to read most of the time.
look like a exception from this rule. However since beauty lies in the eye of beholder I'm fine with both :). Lets come back ot this discussion a bit later when working on Rubocop rules There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For what its worth, the style choice here isn't arbitrary, it's an implementation of the http://www.betterspecs.org/ standard, which I think does a better job of explaining the reasoning than I can here. This all comes down to style ultimately I think. Sometimes it's worth discussing the possible merits, and it's totally reasonable to suggest alternatives, but given that both suggested styles are clear and understandable, it doesn't seem like there's much to gain by dwelling too much on things like this. The same would apply if the circumstances were reversed, and it was the named example in the PR instead of the nameless one. |
||
expect { log }.to output("Span#log is deprecated. Please use Span#log_kv instead.\n").to_stderr | ||
end | ||
|
||
it { is_expected.to be nil } | ||
end | ||
|
||
describe '#log_kv' do | ||
subject(:log_kv) { span.log_kv(timestamp: timestamp, **fields) } | ||
let(:timestamp) { Time.now } | ||
let(:fields) { { time_started: Time.now, account_id: '1234' } } | ||
|
||
before(:each) do | ||
expect { log_kv }.to_not output.to_stderr | ||
context 'when given arbitrary key/value pairs' do | ||
let(:fields) { { time_started: Time.now, account_id: '1234' } } | ||
# We don't expect this to do anything right now. | ||
it { is_expected.to be nil } | ||
end | ||
|
||
it { is_expected.to be nil } | ||
context 'when given an \'error.object\'' do | ||
let(:fields) { { :'error.object' => error_object } } | ||
let(:error_object) { instance_double(StandardError) } | ||
|
||
before(:each) { expect(datadog_span).to receive(:set_error).with(error_object) } | ||
|
||
it { is_expected.to be nil } | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want the span to be in charge of manipulating the data within the baggage?
By having the span context take care of manipulating the baggage we can abstract away the implementation details of the baggage.
Although it is unlikely that the implementation of the baggage will change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I don't think the span should be responsible for knowing details about baggage on the span context. That's why I'm a bit confused about this: if the span context manages the baggage, why is there a baggage function on the span at all?
We could implement baggage functions on the span context object that mirror this, and have this span delegate to those. I guess I'm hesitant to do that only because it seems like a modification of the OT spec. But since its all internal anyways, maybe it's okay.
@Kyle-Verhoog What are your thoughts: move this to SpanContext and delegate from Span, or keep as is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the delegate makes sense. Also, I forgot to mention this earlier, but we gotta keep in mind the span context instance should be immutable[0]. So setting a key in the baggage for a span context should produce a new span context instance with the key which the span will use.
[0] https://github.com/opentracing/specification/blob/master/specification.md#spancontext
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what this could look like is spancontext.set_baggage_item returns a new span context instance with the new item added and span.set_baggage_item will update its span context reference with this new instance.
(This is a change I have to make in the python tracer still).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kyle-Verhoog Okay, I think you're right about the immutable part. I think the implementation might need to be a little different: how about we don't implement
SpanContext#set_baggage_item
and force the use ofSpanContext#new
constructor which accepts baggage? Then do as you said, and have theSpan#set_baggage_item
build a newSpanContext
from an old one?The more complicated this
SpanContext
management gets, the more it screams "factory pattern" to me. Especially if the tracer has to build these too.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me! The
SpanContext#new
should duplicate the baggage that it receives. 👍There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I'll go ahead and make this change then.