Skip to content

Commit cad2098

Browse files
committed
Use common encoder
1 parent 87cae52 commit cad2098

5 files changed

Lines changed: 162 additions & 269 deletions

File tree

exporter/otlp-common/.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ inherit_from: ../../contrib/rubocop.yml
33
Metrics/CyclomaticComplexity:
44
Max: 16
55
Metrics/MethodLength:
6-
Max: 35
6+
Max: 22
77
Metrics/PerceivedComplexity:
88
Max: 16

exporter/otlp-common/lib/opentelemetry/exporter/otlp/common.rb

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def as_etsr(span_data)
7070

7171
private
7272

73-
def as_otlp_span(span_data)
73+
def as_otlp_span(span_data) # rubocop:disable Metrics/MethodLength
7474
Opentelemetry::Proto::Trace::V1::Span.new(
7575
trace_id: span_data.trace_id,
7676
span_id: span_data.span_id,
@@ -96,8 +96,9 @@ def as_otlp_span(span_data)
9696
trace_id: link.span_context.trace_id,
9797
span_id: link.span_context.span_id,
9898
trace_state: link.span_context.tracestate.to_s,
99-
attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) }
99+
attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) },
100100
# TODO: track dropped_attributes_count in Span#trim_links
101+
flags: build_span_flags(link.span_context.remote?, link.span_context.trace_flags)
101102
)
102103
end,
103104
dropped_links_count: span_data.total_recorded_links - span_data.links&.size.to_i,
@@ -106,10 +107,31 @@ def as_otlp_span(span_data)
106107
code: as_otlp_status_code(status.code),
107108
message: status.description
108109
)
109-
end
110+
end,
111+
flags: build_span_flags(span_data.parent_span_is_remote, span_data.trace_flags)
110112
)
111113
end
112114

115+
# Builds span flags based on whether the parent span context is remote.
116+
# This follows the OTLP specification for span flags.
117+
def build_span_flags(parent_span_is_remote, base_flags)
118+
# Extract integer value from TraceFlags object if needed
119+
# Derive the low 8-bit W3C trace flags using the public API.
120+
base_flags_int =
121+
if base_flags.sampled?
122+
1
123+
else
124+
0
125+
end
126+
127+
has_remote_mask = Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
128+
is_remote_mask = Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
129+
130+
flags = base_flags_int | has_remote_mask
131+
flags |= is_remote_mask if parent_span_is_remote
132+
flags
133+
end
134+
113135
def as_otlp_status_code(code)
114136
case code
115137
when OpenTelemetry::Trace::Status::OK then Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_OK

exporter/otlp-common/test/opentelemetry/exporter/otlp/common/common_test.rb

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,4 +344,136 @@
344344
_(etsr.resource_spans.first.scope_spans.first.scope.version).must_be_empty
345345
end
346346
end
347+
348+
describe 'span flags' do
349+
let(:common) { OpenTelemetry::Exporter::OTLP::Common }
350+
let(:trace_id) { OpenTelemetry::Trace.generate_trace_id }
351+
let(:span_id) { OpenTelemetry::Trace.generate_span_id }
352+
let(:parent_span_id) { OpenTelemetry::Trace.generate_span_id }
353+
let(:resource) { OpenTelemetry::SDK::Resources::Resource.create('service.name' => 'test-service') }
354+
let(:instrumentation_scope) { OpenTelemetry::SDK::InstrumentationScope.new('test-lib', '1.0.0') }
355+
356+
describe 'build_span_flags' do
357+
it 'sets flags to HAS_IS_REMOTE for local parent span context' do
358+
flags = common.send(:build_span_flags, false, OpenTelemetry::Trace::TraceFlags::DEFAULT)
359+
_(flags).must_equal(
360+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
361+
)
362+
end
363+
364+
it 'sets flags to HAS_IS_REMOTE | IS_REMOTE for remote parent span context' do
365+
flags = common.send(:build_span_flags, true, OpenTelemetry::Trace::TraceFlags::DEFAULT)
366+
_(flags).must_equal(
367+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
368+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
369+
)
370+
end
371+
372+
it 'preserves base trace flags' do
373+
flags = common.send(:build_span_flags, false, OpenTelemetry::Trace::TraceFlags::SAMPLED)
374+
_(flags).must_equal(
375+
0x01 |
376+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
377+
)
378+
end
379+
end
380+
381+
describe 'as_otlp_span with flags' do
382+
it 'sets flags to HAS_IS_REMOTE for local parent span context' do
383+
span_data = create_span_data(parent_span_is_remote: false)
384+
span = common.send(:as_otlp_span, span_data)
385+
_(span.flags).must_equal(
386+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
387+
)
388+
end
389+
390+
it 'sets flags to HAS_IS_REMOTE | IS_REMOTE for remote parent span context' do
391+
span_data = create_span_data(parent_span_is_remote: true)
392+
span = common.send(:as_otlp_span, span_data)
393+
_(span.flags).must_equal(
394+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
395+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
396+
)
397+
end
398+
end
399+
400+
describe 'as_otlp_span with link flags' do
401+
it 'sets link flags to HAS_IS_REMOTE for local link context' do
402+
local_span_context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: parent_span_id, remote: false)
403+
local_link = create_link(local_span_context)
404+
span_data = create_span_data(links: [local_link])
405+
span = common.send(:as_otlp_span, span_data)
406+
_(span.links.first.flags).must_equal(
407+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
408+
)
409+
end
410+
411+
it 'sets link flags to HAS_IS_REMOTE | IS_REMOTE for remote link context' do
412+
remote_span_context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: parent_span_id, remote: true)
413+
remote_link = create_link(remote_span_context)
414+
span_data = create_span_data(links: [remote_link])
415+
span = common.send(:as_otlp_span, span_data)
416+
_(span.links.first.flags).must_equal(
417+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
418+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
419+
)
420+
end
421+
end
422+
423+
describe 'export with flags' do
424+
it 'includes flags in exported spans' do
425+
span_data = create_span_data(parent_span_is_remote: true)
426+
encoded_data = common.send(:as_encoded_etsr, [span_data])
427+
decoded = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode(encoded_data)
428+
exported_span = decoded.resource_spans.first.scope_spans.first.spans.first
429+
_(exported_span.flags).must_equal(
430+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
431+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
432+
)
433+
end
434+
435+
it 'includes flags in exported links' do
436+
remote_span_context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: parent_span_id, remote: true)
437+
remote_link = create_link(remote_span_context)
438+
span_data = create_span_data(links: [remote_link])
439+
encoded_data = common.send(:as_encoded_etsr, [span_data])
440+
decoded = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode(encoded_data)
441+
exported_link = decoded.resource_spans.first.scope_spans.first.spans.first.links.first
442+
_(exported_link.flags).must_equal(
443+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK |
444+
Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
445+
)
446+
end
447+
end
448+
449+
private
450+
451+
def create_span_data(parent_span_is_remote: false, links: [])
452+
OpenTelemetry::SDK::Trace::SpanData.new(
453+
'test-span', # name
454+
:internal, # kind
455+
OpenTelemetry::Trace::Status.ok, # status
456+
parent_span_id, # parent_span_id
457+
0, # total_recorded_attributes
458+
0, # total_recorded_events
459+
links.size, # total_recorded_links
460+
Time.now.to_i * 1_000_000_000, # start_timestamp
461+
Time.now.to_i * 1_000_000_000, # end_timestamp
462+
{}, # attributes
463+
links, # links
464+
[], # events
465+
resource, # resource
466+
instrumentation_scope, # instrumentation_scope
467+
span_id, # span_id
468+
trace_id, # trace_id
469+
OpenTelemetry::Trace::TraceFlags::DEFAULT, # trace_flags
470+
OpenTelemetry::Trace::Tracestate::DEFAULT, # tracestate
471+
parent_span_is_remote # parent_span_is_remote
472+
)
473+
end
474+
475+
def create_link(span_context)
476+
OpenTelemetry::Trace::Link.new(span_context, { 'link-attribute' => 'link-value' })
477+
end
478+
end
347479
end

exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb

Lines changed: 3 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# SPDX-License-Identifier: Apache-2.0
66

77
require 'opentelemetry/common'
8+
require 'opentelemetry/exporter/otlp/common'
89
require 'opentelemetry/sdk'
910
require 'net/http'
1011
require 'zlib'
@@ -105,26 +106,6 @@ def shutdown(timeout: nil)
105106

106107
private
107108

108-
# Builds span flags based on whether the parent span context is remote.
109-
# This follows the OTLP specification for span flags.
110-
def build_span_flags(parent_span_is_remote, base_flags)
111-
# Extract integer value from TraceFlags object if needed
112-
# Derive the low 8-bit W3C trace flags using the public API.
113-
base_flags_int =
114-
if base_flags.sampled?
115-
1
116-
else
117-
0
118-
end
119-
120-
has_remote_mask = Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
121-
is_remote_mask = Opentelemetry::Proto::Trace::V1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
122-
123-
flags = base_flags_int | has_remote_mask
124-
flags |= is_remote_mask if parent_span_is_remote
125-
flags
126-
end
127-
128109
def http_connection(uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file)
129110
http = Net::HTTP.new(uri.hostname, uri.port)
130111
http.use_ssl = uri.scheme == 'https'
@@ -293,32 +274,9 @@ def backoff?(retry_count:, reason:, retry_after: nil) # rubocop:disable Metrics/
293274
true
294275
end
295276

296-
def encode(span_data) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
277+
def encode(span_data)
297278
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
298-
Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode(
299-
Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.new(
300-
resource_spans: span_data
301-
.group_by(&:resource)
302-
.map do |resource, span_datas|
303-
Opentelemetry::Proto::Trace::V1::ResourceSpans.new(
304-
resource: Opentelemetry::Proto::Resource::V1::Resource.new(
305-
attributes: resource.attribute_enumerator.map { |key, value| as_otlp_key_value(key, value) }
306-
),
307-
scope_spans: span_datas
308-
.group_by(&:instrumentation_scope)
309-
.map do |il, sds|
310-
Opentelemetry::Proto::Trace::V1::ScopeSpans.new(
311-
scope: Opentelemetry::Proto::Common::V1::InstrumentationScope.new(
312-
name: il.name,
313-
version: il.version
314-
),
315-
spans: sds.map { |sd| as_otlp_span(sd) }
316-
)
317-
end
318-
)
319-
end
320-
)
321-
)
279+
OpenTelemetry::Exporter::OTLP::Common.as_encoded_etsr(span_data)
322280
rescue StandardError => e
323281
OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::Exporter#encode')
324282
nil
@@ -329,93 +287,6 @@ def encode(span_data) # rubocop:disable Metrics/MethodLength, Metrics/Cyclomatic
329287
value: duration_ms)
330288
end
331289

332-
def as_otlp_span(span_data) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
333-
Opentelemetry::Proto::Trace::V1::Span.new(
334-
trace_id: span_data.trace_id,
335-
span_id: span_data.span_id,
336-
trace_state: span_data.tracestate.to_s,
337-
parent_span_id: span_data.parent_span_id == OpenTelemetry::Trace::INVALID_SPAN_ID ? nil : span_data.parent_span_id,
338-
name: span_data.name,
339-
kind: as_otlp_span_kind(span_data.kind),
340-
start_time_unix_nano: span_data.start_timestamp,
341-
end_time_unix_nano: span_data.end_timestamp,
342-
attributes: span_data.attributes&.map { |k, v| as_otlp_key_value(k, v) },
343-
dropped_attributes_count: span_data.total_recorded_attributes - span_data.attributes&.size.to_i,
344-
events: span_data.events&.map do |event|
345-
Opentelemetry::Proto::Trace::V1::Span::Event.new(
346-
time_unix_nano: event.timestamp,
347-
name: event.name,
348-
attributes: event.attributes&.map { |k, v| as_otlp_key_value(k, v) }
349-
# TODO: track dropped_attributes_count in Span#append_event
350-
)
351-
end,
352-
dropped_events_count: span_data.total_recorded_events - span_data.events&.size.to_i,
353-
links: span_data.links&.map do |link|
354-
Opentelemetry::Proto::Trace::V1::Span::Link.new(
355-
trace_id: link.span_context.trace_id,
356-
span_id: link.span_context.span_id,
357-
trace_state: link.span_context.tracestate.to_s,
358-
attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) },
359-
# TODO: track dropped_attributes_count in Span#trim_links
360-
flags: build_span_flags(link.span_context.remote?, link.span_context.trace_flags)
361-
)
362-
end,
363-
dropped_links_count: span_data.total_recorded_links - span_data.links&.size.to_i,
364-
status: span_data.status&.then do |status|
365-
Opentelemetry::Proto::Trace::V1::Status.new(
366-
code: as_otlp_status_code(status.code),
367-
message: status.description
368-
)
369-
end,
370-
flags: build_span_flags(span_data.parent_span_is_remote, span_data.trace_flags)
371-
)
372-
end
373-
374-
def as_otlp_status_code(code)
375-
case code
376-
when OpenTelemetry::Trace::Status::OK then Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_OK
377-
when OpenTelemetry::Trace::Status::ERROR then Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_ERROR
378-
else Opentelemetry::Proto::Trace::V1::Status::StatusCode::STATUS_CODE_UNSET
379-
end
380-
end
381-
382-
def as_otlp_span_kind(kind)
383-
case kind
384-
when :internal then Opentelemetry::Proto::Trace::V1::Span::SpanKind::SPAN_KIND_INTERNAL
385-
when :server then Opentelemetry::Proto::Trace::V1::Span::SpanKind::SPAN_KIND_SERVER
386-
when :client then Opentelemetry::Proto::Trace::V1::Span::SpanKind::SPAN_KIND_CLIENT
387-
when :producer then Opentelemetry::Proto::Trace::V1::Span::SpanKind::SPAN_KIND_PRODUCER
388-
when :consumer then Opentelemetry::Proto::Trace::V1::Span::SpanKind::SPAN_KIND_CONSUMER
389-
else Opentelemetry::Proto::Trace::V1::Span::SpanKind::SPAN_KIND_UNSPECIFIED
390-
end
391-
end
392-
393-
def as_otlp_key_value(key, value)
394-
Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value(value))
395-
rescue Encoding::UndefinedConversionError => e
396-
encoded_value = value.encode('UTF-8', invalid: :replace, undef: :replace, replace: '�')
397-
OpenTelemetry.handle_error(exception: e, message: "encoding error for key #{key} and value #{encoded_value}")
398-
Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value('Encoding Error'))
399-
end
400-
401-
def as_otlp_any_value(value)
402-
result = Opentelemetry::Proto::Common::V1::AnyValue.new
403-
case value
404-
when String
405-
result.string_value = value
406-
when Integer
407-
result.int_value = value
408-
when Float
409-
result.double_value = value
410-
when true, false
411-
result.bool_value = value
412-
when Array
413-
values = value.map { |element| as_otlp_any_value(element) }
414-
result.array_value = Opentelemetry::Proto::Common::V1::ArrayValue.new(values: values)
415-
end
416-
result
417-
end
418-
419290
def prepare_endpoint(endpoint)
420291
endpoint ||= ENV.fetch('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', nil)
421292
if endpoint.nil?

0 commit comments

Comments
 (0)