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

feat(otel): add support for span links #3572

Merged
merged 13 commits into from
May 3, 2024
Merged

Conversation

mabdinur
Copy link
Contributor

@mabdinur mabdinur commented Apr 4, 2024

What does this PR do?

This PR ensures that when span links are generative via the OpenTelemetry API the values stored on the link are set on the underlying datadog span.

Motivation:

Support OTEL Span Links and provide better support for otel instrumentations

@mabdinur mabdinur force-pushed the munir/add-links-to-otel-api branch 3 times, most recently from 3140808 to 2eb8dc0 Compare April 9, 2024 18:11
@mabdinur mabdinur force-pushed the munir/add-links-to-otel-api branch from 2eb8dc0 to 1abf37f Compare April 15, 2024 21:20
@mabdinur mabdinur marked this pull request as ready for review April 16, 2024 13:56
@mabdinur mabdinur requested a review from a team as a code owner April 16, 2024 13:56
Base automatically changed from 2.0 to master April 18, 2024 14:32
@mabdinur mabdinur requested a review from TonyCTHsu April 19, 2024 14:11
datadog_span.links = span.links.map do |link|
Datadog::Tracing::SpanLink.new(
Datadog::Tracing::TraceDigest.new(
trace_id: link.span_context.trace_id.unpack1('Q'),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
trace_id: link.span_context.trace_id.unpack1('Q'),
trace_id: to_datadog_id(link.span_context.trace_id),

Can you wrap unpack1('Q') into a method, so that we know what it does in the future?

For example, this is how we do the inverse operation:

# Converts the {Numeric} Datadog id object to OpenTelemetry's byte array format.
# This method currently converts an unsigned 64-bit Integer to a binary String.
def to_otel_id(dd_id)
Array(dd_id).pack('Q')
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated this code to use hex_trace_id and hex_span_id field on the otel SpanContext. This way we ensure the otel library encodes/decodes bytes in a consistent manner (I believe they use Array.pack('H*') for trace ids and Array.pack('Q') for span ids).

@@ -83,7 +83,8 @@
start_time: nil,
status: 0,
trace_id: kind_of(Integer),
type: nil
Copy link
Member

Choose a reason for hiding this comment

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

You'll want to add a test to the given an option group for links:

context 'given an option' do
shared_examples 'a string property' do |nillable: true|
let(:options) { { property => value } }
context 'set to a String' do
let(:value) { 'test string' }
it { is_expected.to have_attributes(property => value) }
end
context 'set to a non-UTF-8 String' do
let(:value) { 'ascii'.encode(Encoding::ASCII) }
it { is_expected.to have_attributes(property => value) }
it { expect(span_op.public_send(property).encoding).to eq(Encoding::UTF_8) }
end
context 'invoking the public setter' do
subject! { span_op.public_send("#{property}=", value) }
context 'with a string' do
let(:value) { 'test string' }
it { expect(span_op).to have_attributes(property => value) }
end
context 'with a string that is not in UTF-8' do
let(:value) { 'ascii'.encode(Encoding::ASCII) }
it { expect(span_op).to have_attributes(property => value) }
it { expect(span_op.public_send(property).encoding).to eq(Encoding::UTF_8) }
end
end
if nillable
context 'set to nil' do
let(:value) { nil }
# Allow property to be explicitly set to nil
it { is_expected.to have_attributes(property => nil) }
end
else
context 'set to nil' do
let(:value) { nil }
it { expect { subject }.to raise_error(ArgumentError) }
end
end
end
context ':on_error' do
let(:options) { { on_error: block } }
let(:block) { proc { raise error } }
let(:error) { error_class.new('error message') }
let(:error_class) { stub_const('TestError', Class.new(StandardError)) }
context 'that is nil' do
let(:on_error) { nil }
context 'and #measure raises an error' do
subject(:measure) { span_op.measure { raise error } }
before { allow(span_op).to receive(:set_error) }
it 'propagates the error' do
expect { measure }.to raise_error(error)
expect(span_op).to have_received(:set_error).with(error)
end
end
end
context 'that is a block' do
let(:on_error) { block }
it 'yields to the error block and raises the error' do
expect do
expect do |b|
options[:on_error] = b.to_proc
span_op.measure(&block)
end.to yield_with_args(
a_kind_of(described_class),
error
)
end.to raise_error(error)
# It should not set an error, as this overrides behavior.
expect(span_op).to_not have_error
end
end
context 'that is not a Proc' do
let(:on_error) { 'not a proc' }
it 'fallbacks to default error handler and log a debug message' do
expect(Datadog.logger).to receive(:debug).at_least(:once)
expect do
span_op.measure(&block)
end.to raise_error(error)
end
end
end
describe ':name' do
it_behaves_like 'a string property', nillable: false do
let(:property) { :name }
# :name is not a keyword argument, but positional.
# We swap those two here.
let(:options) { {} }
let(:name) { value }
end
end
describe ':parent_id' do
let(:options) { { parent_id: parent_id } }
context 'that is nil' do
let(:parent_id) { nil }
it { is_expected.to have_attributes(parent_id: 0) }
end
context 'that is an Integer' do
let(:parent_id) { instance_double(Integer) }
it { is_expected.to have_attributes(parent_id: parent_id) }
end
end
describe ':resource' do
it_behaves_like 'a string property' do
let(:property) { :resource }
end
end
describe ':service' do
it_behaves_like 'a string property' do
let(:property) { :service }
end
end
describe ':start_time' do
let(:options) { { start_time: start_time } }
let(:start_time) { instance_double(Time) }
context 'that is nil' do
let(:start_time) { nil }
it { is_expected.to have_attributes(start_time: nil) }
end
context 'that is a Time' do
let(:start_time) { instance_double(Time) }
it { is_expected.to have_attributes(start_time: start_time) }
end
end
describe ':tags' do
let(:options) { { tags: tags } }
context 'that is nil' do
let(:tags) { nil }
it_behaves_like 'a root span operation'
end
context 'that is a Hash' do
let(:tags) { { 'custom_tag' => 'custom_value' } }
it_behaves_like 'a root span operation'
it { expect(span_op.get_tag('custom_tag')).to eq(tags['custom_tag']) }
end
end
describe ':trace_id' do
let(:options) { { trace_id: trace_id } }
context 'that is nil' do
let(:trace_id) { nil }
it { is_expected.to have_attributes(trace_id: kind_of(Integer)) }
end
context 'that is an Integer' do
let(:trace_id) { Datadog::Tracing::Utils::TraceId.next_id }
it { is_expected.to have_attributes(trace_id: trace_id) }
end
end
describe ':type' do
it_behaves_like 'a string property' do
let(:property) { :type }
end
end
end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added test coverage for the links field in span_operation

@mabdinur mabdinur force-pushed the munir/add-links-to-otel-api branch from 5e51f52 to 59e9c5c Compare April 23, 2024 01:58
Co-authored-by: Marco Costa <[email protected]>
@mabdinur mabdinur requested a review from marcotc April 25, 2024 11:05
@marcotc
Copy link
Member

marcotc commented May 1, 2024

@mabdinur I think we only have one test failure left here:

  1) Datadog::OpenTelemetry with Datadog TraceProvider #start_span with span_links sets span links
     Failure/Error: expect(span.links[1].span_id).to eq(67890)
     
       expected: 67890
            got: 10
     
       (compared using ==)
     # ./spec/datadog/opentelemetry_spec.rb:355:in `block (5 levels) in <top (required)>'
     # ./spec/spec_helper.rb:230:in `block (2 levels) in <top (required)>'
     # ./spec/spec_helper.rb:115:in `block (2 levels) in <top (required)>'
     # /usr/local/bundle/gems/webmock-3.13.0/lib/webmock/rspec.rb:37:in `block (2 levels) in <top (required)>'
     # /usr/local/bundle/gems/rspec-wait-0.0.9/lib/rspec/wait.rb:46:in `block (2 levels) in <top (required)>'

Finished in 0.59189 seconds (files took 0.88941 seconds to load)
140 examples, 1 failure

Failed examples:

rspec ./spec/datadog/opentelemetry_spec.rb:344 # Datadog::OpenTelemetry with Datadog TraceProvider #start_span with span_links sets span links

@marcotc
Copy link
Member

marcotc commented May 1, 2024

@mabdinur, do we have span links system tests in place already?

@mabdinur mabdinur requested a review from a team as a code owner May 1, 2024 18:36
@marcotc
Copy link
Member

marcotc commented May 1, 2024

Different failure now, possibly because of the 2.0 branch being merged into master:

  1) Datadog::Tracing::SpanOperation::new given an option :links that is an Array 
     Failure/Error:
       def initialize(
         digest,
         attributes: nil
       )
         @span_id = digest.span_id
         @trace_id = digest.trace_id
         @trace_flags = if digest.trace_sampling_priority.nil?
                          nil
                        elsif digest.trace_sampling_priority > 0
                          1
     
     ArgumentError:
       wrong number of arguments (given 0, expected 1)
     # ./lib/datadog/tracing/span_link.rb:43:in `initialize'
     # ./spec/datadog/tracing/span_operation_spec.rb:244:in `new'
     # ./spec/datadog/tracing/span_operation_spec.rb:244:in `block (6 levels) in <top (required)>'
     # ./spec/datadog/tracing/span_operation_spec.rb:240:in `block (5 levels) in <top (required)>'
     # ./spec/datadog/tracing/span_operation_spec.rb:17:in `block (2 levels) in <top (required)>'
     # ./spec/datadog/tracing/span_operation_spec.rb:249:in `block (6 levels) in <top (required)>'
     # ./spec/spec_helper.rb:230:in `block (2 levels) in <top (required)>'
     # ./spec/spec_helper.rb:115:in `block (2 levels) in <top (required)>'
     # /usr/local/bundle/gems/webmock-3.23.0/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'
     # /usr/local/bundle/gems/rspec-wait-0.0.10/lib/rspec/wait.rb:47:in `block (2 levels) in <top (required)>'

Finished in 1 minute 5.14 seconds (files took 2.74 seconds to load)
4289 examples, 1 failure, 46 pending

Failed examples:

rspec ./spec/datadog/tracing/span_operation_spec.rb:249 # Datadog::Tracing::SpanOperation::new given an option :links that is an Array

@mabdinur
Copy link
Contributor Author

mabdinur commented May 3, 2024

@mabdinur, do we have span links system tests in place already?

Yes I plan to enable them in this PR: https://github.com/DataDog/system-tests/pull/2351/files

@mabdinur mabdinur force-pushed the munir/add-links-to-otel-api branch from d74946a to 2370fe7 Compare May 3, 2024 15:00
@codecov-commenter
Copy link

codecov-commenter commented May 3, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.13%. Comparing base (e581d87) to head (683dd7b).
Report is 3 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3572   +/-   ##
=======================================
  Coverage   98.13%   98.13%           
=======================================
  Files        1223     1223           
  Lines       72146    72185   +39     
  Branches     3425     3428    +3     
=======================================
+ Hits        70797    70836   +39     
  Misses       1349     1349           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@mabdinur mabdinur force-pushed the munir/add-links-to-otel-api branch from bc0a902 to 683dd7b Compare May 3, 2024 18:05
@marcotc marcotc merged commit 43292f5 into master May 3, 2024
166 checks passed
@marcotc marcotc deleted the munir/add-links-to-otel-api branch May 3, 2024 22:40
@github-actions github-actions bot added this to the 2.0.0 milestone May 3, 2024
@TonyCTHsu TonyCTHsu mentioned this pull request Jun 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants