Skip to content
Open
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
2 changes: 1 addition & 1 deletion exporter/otlp-common/.rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ AllCops:
Metrics/CyclomaticComplexity:
Max: 16
Metrics/MethodLength:
Max: 35
Max: 22
Metrics/PerceivedComplexity:
Max: 16
28 changes: 25 additions & 3 deletions exporter/otlp-common/lib/opentelemetry/exporter/otlp/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def as_etsr(span_data)

private

def as_otlp_span(span_data)
def as_otlp_span(span_data) # rubocop:disable Metrics/MethodLength
Opentelemetry::Proto::Trace::V1::Span.new(
trace_id: span_data.trace_id,
span_id: span_data.span_id,
Expand All @@ -96,8 +96,9 @@ def as_otlp_span(span_data)
trace_id: link.span_context.trace_id,
span_id: link.span_context.span_id,
trace_state: link.span_context.tracestate.to_s,
attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) }
attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) },
# TODO: track dropped_attributes_count in Span#trim_links
flags: build_span_flags(link.span_context.remote?, link.span_context.trace_flags)
)
end,
dropped_links_count: span_data.total_recorded_links - span_data.links&.size.to_i,
Expand All @@ -106,10 +107,31 @@ def as_otlp_span(span_data)
code: as_otlp_status_code(status.code),
message: status.description
)
end
end,
flags: build_span_flags(span_data.parent_span_is_remote, span_data.trace_flags)
)
end

# Builds span flags based on whether the parent span context is remote.
# This follows the OTLP specification for span flags.
def build_span_flags(parent_span_is_remote, base_flags)
# Extract integer value from TraceFlags object if needed
# Derive the low 8-bit W3C trace flags using the public API.
base_flags_int =
if base_flags.sampled?
1
else
0
end

has_remote_mask = Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
is_remote_mask = Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK

flags = base_flags_int | has_remote_mask
flags |= is_remote_mask if parent_span_is_remote
flags
end

def as_otlp_status_code(code)
case code
when OpenTelemetry::Trace::Status::OK then Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_OK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@
end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i,
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_OK
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
),
Opentelemetry::Proto::Trace::V1::Span.new(
Expand All @@ -175,6 +179,10 @@
end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i,
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_UNSET
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
),
Opentelemetry::Proto::Trace::V1::Span.new(
Expand All @@ -187,6 +195,10 @@
end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i,
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_UNSET
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
),
Opentelemetry::Proto::Trace::V1::Span.new(
Expand Down Expand Up @@ -229,11 +241,19 @@
span_id: root_span_id,
attributes: [
Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'attr', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 4))
]
],
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
)
],
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_ERROR
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
)
]
Expand All @@ -253,6 +273,10 @@
end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i,
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_UNSET
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
)
]
Expand Down Expand Up @@ -344,4 +368,136 @@
_(etsr.resource_spans.first.scope_spans.first.scope.version).must_be_empty
end
end

describe 'span flags' do
let(:common) { OpenTelemetry::Exporter::OTLP::Common }
let(:trace_id) { OpenTelemetry::Trace.generate_trace_id }
let(:span_id) { OpenTelemetry::Trace.generate_span_id }
let(:parent_span_id) { OpenTelemetry::Trace.generate_span_id }
let(:resource) { OpenTelemetry::SDK::Resources::Resource.create('service.name' => 'test-service') }
let(:instrumentation_scope) { OpenTelemetry::SDK::InstrumentationScope.new('test-lib', '1.0.0') }

describe 'build_span_flags' do
it 'sets flags to HAS_IS_REMOTE for local parent span context' do
flags = common.send(:build_span_flags, false, OpenTelemetry::Trace::TraceFlags::DEFAULT)
_(flags).must_equal(
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
)
end

it 'sets flags to HAS_IS_REMOTE | IS_REMOTE for remote parent span context' do
flags = common.send(:build_span_flags, true, OpenTelemetry::Trace::TraceFlags::DEFAULT)
_(flags).must_equal(
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
)
end

it 'preserves base trace flags' do
flags = common.send(:build_span_flags, false, OpenTelemetry::Trace::TraceFlags::SAMPLED)
_(flags).must_equal(
0x01 |
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
)
end
end

describe 'as_otlp_span with flags' do
it 'sets flags to HAS_IS_REMOTE for local parent span context' do
span_data = create_span_data(parent_span_is_remote: false)
span = common.send(:as_otlp_span, span_data)
_(span.flags).must_equal(
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
)
end

it 'sets flags to HAS_IS_REMOTE | IS_REMOTE for remote parent span context' do
span_data = create_span_data(parent_span_is_remote: true)
span = common.send(:as_otlp_span, span_data)
_(span.flags).must_equal(
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
)
end
end

describe 'as_otlp_span with link flags' do
it 'sets link flags to HAS_IS_REMOTE for local link context' do
local_span_context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: parent_span_id, remote: false)
local_link = create_link(local_span_context)
span_data = create_span_data(links: [local_link])
span = common.send(:as_otlp_span, span_data)
_(span.links.first.flags).must_equal(
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
)
end

it 'sets link flags to HAS_IS_REMOTE | IS_REMOTE for remote link context' do
remote_span_context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: parent_span_id, remote: true)
remote_link = create_link(remote_span_context)
span_data = create_span_data(links: [remote_link])
span = common.send(:as_otlp_span, span_data)
_(span.links.first.flags).must_equal(
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
)
end
end

describe 'export with flags' do
it 'includes flags in exported spans' do
span_data = create_span_data(parent_span_is_remote: true)
encoded_data = common.send(:as_encoded_etsr, [span_data])
decoded = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode(encoded_data)
exported_span = decoded.resource_spans.first.scope_spans.first.spans.first
_(exported_span.flags).must_equal(
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
)
end

it 'includes flags in exported links' do
remote_span_context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: parent_span_id, remote: true)
remote_link = create_link(remote_span_context)
span_data = create_span_data(links: [remote_link])
encoded_data = common.send(:as_encoded_etsr, [span_data])
decoded = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode(encoded_data)
exported_link = decoded.resource_spans.first.scope_spans.first.spans.first.links.first
_(exported_link.flags).must_equal(
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
)
end
end

private

def create_span_data(parent_span_is_remote: false, links: [])
OpenTelemetry::SDK::Trace::SpanData.new(
'test-span', # name
:internal, # kind
OpenTelemetry::Trace::Status.ok, # status
parent_span_id, # parent_span_id
0, # total_recorded_attributes
0, # total_recorded_events
links.size, # total_recorded_links
Time.now.to_i * 1_000_000_000, # start_timestamp
Time.now.to_i * 1_000_000_000, # end_timestamp
{}, # attributes
links, # links
[], # events
resource, # resource
instrumentation_scope, # instrumentation_scope
span_id, # span_id
trace_id, # trace_id
OpenTelemetry::Trace::TraceFlags::DEFAULT, # trace_flags
OpenTelemetry::Trace::Tracestate::DEFAULT, # tracestate
parent_span_is_remote # parent_span_is_remote
)
end

def create_link(span_context)
OpenTelemetry::Trace::Link.new(span_context, { 'link-attribute' => 'link-value' })
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,10 @@
end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i,
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_OK
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
),
Opentelemetry::Proto::Trace::V1::Span.new(
Expand All @@ -753,6 +757,10 @@
end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i,
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_UNSET
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
),
Opentelemetry::Proto::Trace::V1::Span.new(
Expand All @@ -765,6 +773,10 @@
end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i,
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_UNSET
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
),
Opentelemetry::Proto::Trace::V1::Span.new(
Expand Down Expand Up @@ -807,11 +819,19 @@
span_id: root_span_id,
attributes: [
Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'attr', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 4))
]
],
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
)
],
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_ERROR
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
)
]
Expand All @@ -831,6 +851,10 @@
end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i,
status: Opentelemetry::Proto::Trace::V1::Status.new(
code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_UNSET
),
flags: (
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
1
)
)
]
Expand Down
1 change: 1 addition & 0 deletions exporter/otlp/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ group :test, :development do
gem 'yard-doctest', '~> 0.1.6'
gem 'opentelemetry-api', path: '../../api', require: false
gem 'opentelemetry-common', path: '../../common', require: false
gem 'opentelemetry-exporter-otlp-common', path: '../otlp-common', require: false
gem 'opentelemetry-registry', path: '../../registry', require: false
gem 'opentelemetry-sdk', path: '../../sdk', require: false
gem 'opentelemetry-semantic_conventions', path: '../../semantic_conventions', require: false
Expand Down
Loading