From 7f8e005c34d9f053ab250569d91f39947681ead8 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 20 Mar 2024 14:47:06 -0400 Subject: [PATCH 1/6] feat: add span links to Datadog API --- lib/datadog/tracing/span.rb | 9 +- lib/datadog/tracing/span_link.rb | 80 +++++++++++ lib/datadog/tracing/trace_digest.rb | 2 +- .../tracing/transport/serializable_trace.rb | 4 +- sig/datadog/tracing/span_link.rbs | 133 ++++++++++++++++++ spec/datadog/tracing/span_link_spec.rb | 106 ++++++++++++++ spec/datadog/tracing/span_spec.rb | 1 + .../transport/serializable_trace_spec.rb | 38 +++++ 8 files changed, 369 insertions(+), 4 deletions(-) create mode 100644 lib/datadog/tracing/span_link.rb create mode 100644 sig/datadog/tracing/span_link.rbs create mode 100644 spec/datadog/tracing/span_link_spec.rb diff --git a/lib/datadog/tracing/span.rb b/lib/datadog/tracing/span.rb index 018b50e6690..187d25c31e1 100644 --- a/lib/datadog/tracing/span.rb +++ b/lib/datadog/tracing/span.rb @@ -26,6 +26,7 @@ class Span :parent_id, :resource, :service, + :links, :type, :start_time, :status, @@ -58,7 +59,8 @@ def initialize( status: 0, type: nil, trace_id: nil, - service_entry: nil + service_entry: nil, + links: nil ) @name = Core::Utils::SafeDup.frozen_or_dup(name) @service = Core::Utils::SafeDup.frozen_or_dup(service) @@ -86,6 +88,8 @@ def initialize( @service_entry = service_entry + @links = links || [] + # Mark with the service entry span metric, if applicable set_metric(Metadata::Ext::TAG_TOP_LEVEL, 1.0) if service_entry end @@ -136,7 +140,8 @@ def to_hash service: @service, span_id: @id, trace_id: @trace_id, - type: @type + type: @type, + span_links: @links.map!(&:to_hash) } if stopped? diff --git a/lib/datadog/tracing/span_link.rb b/lib/datadog/tracing/span_link.rb new file mode 100644 index 00000000000..e88c52e480f --- /dev/null +++ b/lib/datadog/tracing/span_link.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module Datadog + module Tracing + # SpanLink represents a causal link between two spans. + # @public_api + class SpanLink + # @!attribute [r] span_id + # Datadog id for the currently active span. + # @return [Integer] + # @!attribute [r] trace_id + # Datadog id for the currently active trace. + # @return [Integer] + # @!attribute [r] attributes + # Datadog-specific tags that support richer distributed tracing association. + # @return [Hash] + # @!attribute [r] trace_flags + # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. + # @return [Integer] + # @see https://www.w3.org/TR/trace-context/#trace-flags + # @!attribute [r] trace_state + # The W3C "tracestate" extracted from a distributed context. + # This field is a string representing vendor-specific distribution data. + # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated + # on every propagation injection. + # @return [String] + # @see https://www.w3.org/TR/trace-context/#tracestate-header + attr_reader \ + :span_id, + :trace_id, + :attributes, + :trace_flags, + :trace_state + + def initialize( + span_id: nil, + trace_id: nil, + attributes: nil, + trace_flags: nil, + trace_state: nil + ) + @span_id = span_id + @trace_id = trace_id + @attributes = attributes && attributes.dup.freeze + @trace_flags = trace_flags + @trace_state = trace_state && trace_state.dup.freeze + @dropped_attributes = 0 + freeze + end + + def to_hash + h = { + span_id: @span_id, + trace_id: Tracing::Utils::TraceId.to_low_order(@trace_id), + + } + if @trace_id.to_i > Tracing::Utils::EXTERNAL_MAX_ID + h[:trace_id_high] = + Tracing::Utils::TraceId.to_high_order(@trace_id) + end + if @attributes + h[:attributes] = {} + @attributes.each do |k1, v1| + h[:attributes][k1.to_s] = v1.to_s + end + end + h[:dropped_attributes_count] = @dropped_attributes if @dropped_attributes > 0 + h[:tracestate] = @trace_state if @trace_state + # If traceflags set, the high bit (bit 31) should be set to 1 (uint32). + # This helps us distinguish between when the sample decision is zero or not set + h[:flags] = if @trace_flags + @trace_flags | (1 << 31) + else + 0 + end + h + end + end + end +end diff --git a/lib/datadog/tracing/trace_digest.rb b/lib/datadog/tracing/trace_digest.rb index 751bfa61d9e..790013e56e6 100644 --- a/lib/datadog/tracing/trace_digest.rb +++ b/lib/datadog/tracing/trace_digest.rb @@ -4,7 +4,7 @@ module Datadog module Tracing # Trace digest that represents the important parts of an active trace. # Used to propagate context and continue traces across execution boundaries. - # TODO: Update all references from span to parent (ex: span_id -> parent_id) + # TODO: Update all references from span to parent (ex: span_id -> parent_id) # @public_api class TraceDigest # @!attribute [r] span_id diff --git a/lib/datadog/tracing/transport/serializable_trace.rb b/lib/datadog/tracing/transport/serializable_trace.rb index e14ce2c7b27..44cfb696280 100644 --- a/lib/datadog/tracing/transport/serializable_trace.rb +++ b/lib/datadog/tracing/transport/serializable_trace.rb @@ -58,7 +58,7 @@ def initialize(span) def to_msgpack(packer = nil) packer ||= MessagePack::Packer.new - number_of_elements_to_write = 10 + number_of_elements_to_write = 11 if span.stopped? packer.write_map_header(number_of_elements_to_write + 2) # Set header with how many elements in the map @@ -93,6 +93,8 @@ def to_msgpack(packer = nil) packer.write(span.meta) packer.write('metrics') packer.write(span.metrics) + packer.write('span_links') + packer.write(span.links.map!(&:to_hash)) packer.write('error') packer.write(span.status) packer diff --git a/sig/datadog/tracing/span_link.rbs b/sig/datadog/tracing/span_link.rbs new file mode 100644 index 00000000000..558a561acfb --- /dev/null +++ b/sig/datadog/tracing/span_link.rbs @@ -0,0 +1,133 @@ +module Datadog + module Tracing + # SpanLink represents a causal link between two spans. + # @public_api + class SpanLink + @span_id: untyped + + @trace_id: untyped + + @attributes: untyped + + @trace_flags: untyped + + @trace_state: untyped + + @dropped_attributes: untyped + + # @!attribute [r] span_id + # Datadog id for the currently active span. + # @return [Integer] + # @!attribute [r] trace_id + # Datadog id for the currently active trace. + # @return [Integer] + # @!attribute [r] attributes + # Datadog-specific tags that support richer distributed tracing association. + # @return [Hash] + # @!attribute [r] trace_flags + # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. + # @return [Integer] + # @see https://www.w3.org/TR/trace-context/#trace-flags + # @!attribute [r] trace_state + # The W3C "tracestate" extracted from a distributed context. + # This field is a string representing vendor-specific distribution data. + # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated + # on every propagation injection. + # @return [String] + # @see https://www.w3.org/TR/trace-context/#tracestate-header + attr_reader span_id: untyped + + # @!attribute [r] span_id + # Datadog id for the currently active span. + # @return [Integer] + # @!attribute [r] trace_id + # Datadog id for the currently active trace. + # @return [Integer] + # @!attribute [r] attributes + # Datadog-specific tags that support richer distributed tracing association. + # @return [Hash] + # @!attribute [r] trace_flags + # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. + # @return [Integer] + # @see https://www.w3.org/TR/trace-context/#trace-flags + # @!attribute [r] trace_state + # The W3C "tracestate" extracted from a distributed context. + # This field is a string representing vendor-specific distribution data. + # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated + # on every propagation injection. + # @return [String] + # @see https://www.w3.org/TR/trace-context/#tracestate-header + attr_reader trace_id: untyped + + # @!attribute [r] span_id + # Datadog id for the currently active span. + # @return [Integer] + # @!attribute [r] trace_id + # Datadog id for the currently active trace. + # @return [Integer] + # @!attribute [r] attributes + # Datadog-specific tags that support richer distributed tracing association. + # @return [Hash] + # @!attribute [r] trace_flags + # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. + # @return [Integer] + # @see https://www.w3.org/TR/trace-context/#trace-flags + # @!attribute [r] trace_state + # The W3C "tracestate" extracted from a distributed context. + # This field is a string representing vendor-specific distribution data. + # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated + # on every propagation injection. + # @return [String] + # @see https://www.w3.org/TR/trace-context/#tracestate-header + attr_reader attributes: untyped + + # @!attribute [r] span_id + # Datadog id for the currently active span. + # @return [Integer] + # @!attribute [r] trace_id + # Datadog id for the currently active trace. + # @return [Integer] + # @!attribute [r] attributes + # Datadog-specific tags that support richer distributed tracing association. + # @return [Hash] + # @!attribute [r] trace_flags + # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. + # @return [Integer] + # @see https://www.w3.org/TR/trace-context/#trace-flags + # @!attribute [r] trace_state + # The W3C "tracestate" extracted from a distributed context. + # This field is a string representing vendor-specific distribution data. + # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated + # on every propagation injection. + # @return [String] + # @see https://www.w3.org/TR/trace-context/#tracestate-header + attr_reader trace_flags: untyped + + # @!attribute [r] span_id + # Datadog id for the currently active span. + # @return [Integer] + # @!attribute [r] trace_id + # Datadog id for the currently active trace. + # @return [Integer] + # @!attribute [r] attributes + # Datadog-specific tags that support richer distributed tracing association. + # @return [Hash] + # @!attribute [r] trace_flags + # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. + # @return [Integer] + # @see https://www.w3.org/TR/trace-context/#trace-flags + # @!attribute [r] trace_state + # The W3C "tracestate" extracted from a distributed context. + # This field is a string representing vendor-specific distribution data. + # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated + # on every propagation injection. + # @return [String] + # @see https://www.w3.org/TR/trace-context/#tracestate-header + attr_reader trace_state: untyped + + def initialize: (?span_id: untyped?, ?trace_id: untyped?, ?attributes: untyped?, ?trace_flags: untyped?, ?trace_state: untyped?) -> void + + def to_hash: () -> untyped + end + end +end \ No newline at end of file diff --git a/spec/datadog/tracing/span_link_spec.rb b/spec/datadog/tracing/span_link_spec.rb new file mode 100644 index 00000000000..f840ef52819 --- /dev/null +++ b/spec/datadog/tracing/span_link_spec.rb @@ -0,0 +1,106 @@ +require 'spec_helper' +require 'support/object_helpers' + +require 'datadog/tracing/span_link' + +RSpec.describe Datadog::Tracing::SpanLink do + subject(:span_link) { described_class.new(**options) } + let(:options) { {} } + + describe '::new' do + context 'by default' do + it do + is_expected.to have_attributes( + span_id: nil, + attributes: nil, + trace_id: nil, + trace_flags: nil, + trace_state: nil, + ) + end + + it { is_expected.to be_frozen } + end + + context 'given' do + context ':span_id' do + let(:options) { { span_id: span_id } } + let(:span_id) { Datadog::Tracing::Utils.next_id } + + it { is_expected.to have_attributes(span_id: span_id) } + end + + context ':attributes' do + let(:options) { { attributes: attributes } } + let(:attributes) { { tag: 'value' } } + + it { is_expected.to have_attributes(attributes: be_a_frozen_copy_of(attributes)) } + end + + context ':trace_id' do + let(:options) { { trace_id: trace_id } } + let(:trace_id) { Datadog::Tracing::Utils::TraceId.next_id } + + it { is_expected.to have_attributes(trace_id: trace_id) } + end + + context ':trace_flags' do + let(:options) { { trace_flags: trace_flags } } + let(:trace_flags) { 0x01 } + + it { is_expected.to have_attributes(trace_flags: 0x01) } + end + + context ':trace_state' do + let(:options) { { trace_state: trace_state } } + let(:trace_state) { 'vendor1=value,v2=v' } + + it { is_expected.to have_attributes(trace_state: be_a_frozen_copy_of('vendor1=value,v2=v')) } + end + end + end + + describe '#to_hash' do + subject(:to_hash) { span_link.to_hash } + + context 'with required fields' do + let(:options) { { span_id: 34, trace_id: 12 } } + + context 'when trace_id < 2^64' do + it { is_expected.to eq(trace_id: 12, span_id: 34, flags: 0) } + end + + context 'when trace_id >= 2^64' do + let(:options) { { span_id: 34, trace_id: 2**64 + 12 } } + it { is_expected.to eq(trace_id: 12, trace_id_high: 1, span_id: 34, flags: 0) } + end + end + + context 'with trace_state' do + let(:options) { { span_id: 34, trace_id: 12, trace_state: 'dd=s:1' } } + it { is_expected.to include(tracestate: 'dd=s:1') } + end + + context 'with trace_flag' do + context 'when trace_flag is unset' do + let(:options) { { span_id: 34, trace_id: 12 } } + it { is_expected.to include(flags: 0) } + end + + context 'when trace_flags is 0' do + let(:options) { { span_id: 34, trace_id: 12, trace_flags: 0 } } + it { is_expected.to include(flags: 2147483648) } + end + + context 'when trace_flag is 1' do + let(:options) { { span_id: 34, trace_id: 12, trace_flags: 1 } } + it { is_expected.to include(flags: 2147483649) } + end + end + + context 'with attributes' do + let(:options) { { span_id: 34, trace_id: 12, attributes: { 'link.name' => :test_link, 'link.id' => 1 } } } + it { is_expected.to include(attributes: { 'link.name' => 'test_link', 'link.id' => '1' }) } + end + end +end diff --git a/spec/datadog/tracing/span_spec.rb b/spec/datadog/tracing/span_spec.rb index 22b33812dfb..0c6bca7f3f0 100644 --- a/spec/datadog/tracing/span_spec.rb +++ b/spec/datadog/tracing/span_spec.rb @@ -244,6 +244,7 @@ type: nil, meta: {}, metrics: {}, + span_links: [], error: 0 ) end diff --git a/spec/datadog/tracing/transport/serializable_trace_spec.rb b/spec/datadog/tracing/transport/serializable_trace_spec.rb index 52136cfd932..4c961117e24 100644 --- a/spec/datadog/tracing/transport/serializable_trace_spec.rb +++ b/spec/datadog/tracing/transport/serializable_trace_spec.rb @@ -70,6 +70,44 @@ end end end + + context 'when given span links' do + subject(:unpacked_trace) { MessagePack.unpack(to_msgpack) } + + let(:spans) do + Array.new(3) do |_i| + Datadog::Tracing::Span.new( + 'dummy', + links: [Datadog::Tracing::SpanLink.new( + trace_id: 0xaaaaaaaaaaaaaaaaffffffffffffffff, + span_id: 0x1, + trace_state: 'vendor1=value,v2=v,dd=s:1', + trace_flags: 0x1, + attributes: { 'link.name' => 'test_link' } + )] + ) + end + end + + it 'serializes span links' do + expect( + unpacked_trace.map do |s| + s['span_links'] + end + ).to all( + eq( + [{ + 'span_id' => 1, + 'trace_id' => 0xffffffffffffffff, + 'trace_id_high' => 0xaaaaaaaaaaaaaaaa, + 'attributes' => { 'link.name' => 'test_link' }, + 'flags' => 2147483649, + 'tracestate' => 'vendor1=value,v2=v,dd=s:1', + }] + ) + ) + end + end end describe '#to_json' do From 6c26230ab8f7ccb212876e46deb4ae757dd3c852 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 20 Mar 2024 16:05:14 -0400 Subject: [PATCH 2/6] flatten span link attributes --- .../opentelemetry/sdk/span_processor.rb | 2 +- lib/datadog/opentelemetry/sdk/trace/span.rb | 20 +++---------------- lib/datadog/tracing/span_link.rb | 4 +++- lib/datadog/tracing/utils.rb | 16 +++++++++++++++ spec/datadog/tracing/span_link_spec.rb | 12 +++++++++-- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/lib/datadog/opentelemetry/sdk/span_processor.rb b/lib/datadog/opentelemetry/sdk/span_processor.rb index 1860c3a193f..655facab164 100644 --- a/lib/datadog/opentelemetry/sdk/span_processor.rb +++ b/lib/datadog/opentelemetry/sdk/span_processor.rb @@ -115,7 +115,7 @@ def span_arguments(span, attributes) # DEV: There's no `flat_map!`; we have to split it into two operations attributes = attributes.map do |key, value| - Datadog::OpenTelemetry::Trace::Span.serialize_attribute(key, value) + Datadog::Tracing::Utils.serialize_attribute(key, value) end attributes.flatten!(1) diff --git a/lib/datadog/opentelemetry/sdk/trace/span.rb b/lib/datadog/opentelemetry/sdk/trace/span.rb index e6ccd7f964c..797809c36d2 100644 --- a/lib/datadog/opentelemetry/sdk/trace/span.rb +++ b/lib/datadog/opentelemetry/sdk/trace/span.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require '../../../tracing/utils' + module Datadog module OpenTelemetry module Trace @@ -36,22 +38,6 @@ def status=(s) span.set_error(status.description) if status && status.code == ::OpenTelemetry::Trace::Status::ERROR end - # Serialize values into Datadog span tags and metrics. - # Notably, arrays are exploded into many keys, each with - # a numeric suffix representing the array index, for example: - # `'foo' => ['a','b']` becomes `'foo.0' => 'a', 'foo.1' => 'b'` - def self.serialize_attribute(key, value) - if value.is_a?(Array) - value.flat_map.with_index do |v, idx| - serialize_attribute("#{key}.#{idx}", v) - end - elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) - [[key, value.to_s]] - else - [[key, value]] - end - end - # Create a meaningful Datadog operation name from the OpenTelemetry # semantic convention for span kind and span attributes. # @see https://opentelemetry.io/docs/specs/semconv/general/trace/ @@ -123,7 +109,7 @@ def datadog_set_attribute(key) span.name = rich_name.downcase end - Span.serialize_attribute(key, @attributes[key]).each do |new_key, value| + Tracing::Utils.serialize_attribute(key, @attributes[key]).each do |new_key, value| override_datadog_values(span, new_key, value) # When an attribute is used to override a Datadog Span property, diff --git a/lib/datadog/tracing/span_link.rb b/lib/datadog/tracing/span_link.rb index e88c52e480f..1c85a00f533 100644 --- a/lib/datadog/tracing/span_link.rb +++ b/lib/datadog/tracing/span_link.rb @@ -61,7 +61,9 @@ def to_hash if @attributes h[:attributes] = {} @attributes.each do |k1, v1| - h[:attributes][k1.to_s] = v1.to_s + Tracing::Utils.serialize_attribute(k1, v1).each do |new_k1, value| + h[:attributes][new_k1.to_s] = value.to_s + end end end h[:dropped_attributes_count] = @dropped_attributes if @dropped_attributes > 0 diff --git a/lib/datadog/tracing/utils.rb b/lib/datadog/tracing/utils.rb index 543b2986654..a0001f5679d 100644 --- a/lib/datadog/tracing/utils.rb +++ b/lib/datadog/tracing/utils.rb @@ -45,6 +45,22 @@ def self.reset! @id_rng = Random.new end + # Serialize values into Datadog span tags and metrics. + # Notably, arrays are exploded into many keys, each with + # a numeric suffix representing the array index, for example: + # `'foo' => ['a','b']` becomes `'foo.0' => 'a', 'foo.1' => 'b'` + def self.serialize_attribute(key, value) + if value.is_a?(Array) + value.flat_map.with_index do |v, idx| + serialize_attribute("#{key}.#{idx}", v) + end + elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) + [[key, value.to_s]] + else + [[key, value]] + end + end + private_class_method :id_rng, :reset! # The module handles bitwise operation for trace id diff --git a/spec/datadog/tracing/span_link_spec.rb b/spec/datadog/tracing/span_link_spec.rb index f840ef52819..6a0281038db 100644 --- a/spec/datadog/tracing/span_link_spec.rb +++ b/spec/datadog/tracing/span_link_spec.rb @@ -99,8 +99,16 @@ end context 'with attributes' do - let(:options) { { span_id: 34, trace_id: 12, attributes: { 'link.name' => :test_link, 'link.id' => 1 } } } - it { is_expected.to include(attributes: { 'link.name' => 'test_link', 'link.id' => '1' }) } + let(:options) do + { span_id: 34, trace_id: 12, + attributes: { 'link.name' => :test_link, 'link.id' => 1, 'nested' => [true, [2, 3], 'val'] } } + end + it { + is_expected.to include( + attributes: { 'link.name' => 'test_link', 'link.id' => '1', 'nested.0' => 'true', + 'nested.1.0' => '2', 'nested.1.1' => '3', 'nested.2' => 'val', } + ) + } end end end From 78c39246bf93b61aeb5eeb14f8102ebb5f5784b1 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 21 Mar 2024 11:54:02 -0400 Subject: [PATCH 3/6] update rbs and clean up to hash --- lib/datadog/tracing/span_link.rb | 24 +++++---- sig/datadog/tracing/span_link.rbs | 82 +------------------------------ 2 files changed, 15 insertions(+), 91 deletions(-) diff --git a/lib/datadog/tracing/span_link.rb b/lib/datadog/tracing/span_link.rb index 1c85a00f533..6874f871060 100644 --- a/lib/datadog/tracing/span_link.rb +++ b/lib/datadog/tracing/span_link.rb @@ -8,16 +8,24 @@ class SpanLink # @!attribute [r] span_id # Datadog id for the currently active span. # @return [Integer] + attr_reader :span_id + # @!attribute [r] trace_id # Datadog id for the currently active trace. # @return [Integer] + attr_reader :trace_id + # @!attribute [r] attributes # Datadog-specific tags that support richer distributed tracing association. # @return [Hash] + attr_reader :attributes + # @!attribute [r] trace_flags # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. # @return [Integer] # @see https://www.w3.org/TR/trace-context/#trace-flags + attr_reader :trace_flags + # @!attribute [r] trace_state # The W3C "tracestate" extracted from a distributed context. # This field is a string representing vendor-specific distribution data. @@ -25,12 +33,7 @@ class SpanLink # on every propagation injection. # @return [String] # @see https://www.w3.org/TR/trace-context/#tracestate-header - attr_reader \ - :span_id, - :trace_id, - :attributes, - :trace_flags, - :trace_state + attr_reader :trace_state def initialize( span_id: nil, @@ -54,6 +57,7 @@ def to_hash trace_id: Tracing::Utils::TraceId.to_low_order(@trace_id), } + # Optimization: Hash non empty attributes if @trace_id.to_i > Tracing::Utils::EXTERNAL_MAX_ID h[:trace_id_high] = Tracing::Utils::TraceId.to_high_order(@trace_id) @@ -62,7 +66,7 @@ def to_hash h[:attributes] = {} @attributes.each do |k1, v1| Tracing::Utils.serialize_attribute(k1, v1).each do |new_k1, value| - h[:attributes][new_k1.to_s] = value.to_s + h[:attributes][new_k1] = value.to_s end end end @@ -70,10 +74,10 @@ def to_hash h[:tracestate] = @trace_state if @trace_state # If traceflags set, the high bit (bit 31) should be set to 1 (uint32). # This helps us distinguish between when the sample decision is zero or not set - h[:flags] = if @trace_flags - @trace_flags | (1 << 31) - else + h[:flags] = if @trace_flags.nil? 0 + else + @trace_flags | (1 << 31) end h end diff --git a/sig/datadog/tracing/span_link.rbs b/sig/datadog/tracing/span_link.rbs index 558a561acfb..45815625410 100644 --- a/sig/datadog/tracing/span_link.rbs +++ b/sig/datadog/tracing/span_link.rbs @@ -18,104 +18,24 @@ module Datadog # @!attribute [r] span_id # Datadog id for the currently active span. # @return [Integer] - # @!attribute [r] trace_id - # Datadog id for the currently active trace. - # @return [Integer] - # @!attribute [r] attributes - # Datadog-specific tags that support richer distributed tracing association. - # @return [Hash] - # @!attribute [r] trace_flags - # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. - # @return [Integer] - # @see https://www.w3.org/TR/trace-context/#trace-flags - # @!attribute [r] trace_state - # The W3C "tracestate" extracted from a distributed context. - # This field is a string representing vendor-specific distribution data. - # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated - # on every propagation injection. - # @return [String] - # @see https://www.w3.org/TR/trace-context/#tracestate-header attr_reader span_id: untyped - # @!attribute [r] span_id - # Datadog id for the currently active span. - # @return [Integer] # @!attribute [r] trace_id # Datadog id for the currently active trace. # @return [Integer] - # @!attribute [r] attributes - # Datadog-specific tags that support richer distributed tracing association. - # @return [Hash] - # @!attribute [r] trace_flags - # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. - # @return [Integer] - # @see https://www.w3.org/TR/trace-context/#trace-flags - # @!attribute [r] trace_state - # The W3C "tracestate" extracted from a distributed context. - # This field is a string representing vendor-specific distribution data. - # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated - # on every propagation injection. - # @return [String] - # @see https://www.w3.org/TR/trace-context/#tracestate-header attr_reader trace_id: untyped - # @!attribute [r] span_id - # Datadog id for the currently active span. - # @return [Integer] - # @!attribute [r] trace_id - # Datadog id for the currently active trace. - # @return [Integer] # @!attribute [r] attributes # Datadog-specific tags that support richer distributed tracing association. # @return [Hash] - # @!attribute [r] trace_flags - # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. - # @return [Integer] - # @see https://www.w3.org/TR/trace-context/#trace-flags - # @!attribute [r] trace_state - # The W3C "tracestate" extracted from a distributed context. - # This field is a string representing vendor-specific distribution data. - # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated - # on every propagation injection. - # @return [String] - # @see https://www.w3.org/TR/trace-context/#tracestate-header attr_reader attributes: untyped - # @!attribute [r] span_id - # Datadog id for the currently active span. - # @return [Integer] - # @!attribute [r] trace_id - # Datadog id for the currently active trace. - # @return [Integer] - # @!attribute [r] attributes - # Datadog-specific tags that support richer distributed tracing association. - # @return [Hash] # @!attribute [r] trace_flags # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. # @return [Integer] # @see https://www.w3.org/TR/trace-context/#trace-flags - # @!attribute [r] trace_state - # The W3C "tracestate" extracted from a distributed context. - # This field is a string representing vendor-specific distribution data. - # The `dd=` entry is removed from `trace_state` as its value is dynamically calculated - # on every propagation injection. - # @return [String] - # @see https://www.w3.org/TR/trace-context/#tracestate-header attr_reader trace_flags: untyped - # @!attribute [r] span_id - # Datadog id for the currently active span. - # @return [Integer] - # @!attribute [r] trace_id - # Datadog id for the currently active trace. - # @return [Integer] - # @!attribute [r] attributes - # Datadog-specific tags that support richer distributed tracing association. - # @return [Hash] - # @!attribute [r] trace_flags - # The W3C "trace-flags" extracted from a distributed context. This field is an 8-bit unsigned integer. - # @return [Integer] - # @see https://www.w3.org/TR/trace-context/#trace-flags # @!attribute [r] trace_state # The W3C "tracestate" extracted from a distributed context. # This field is a string representing vendor-specific distribution data. @@ -130,4 +50,4 @@ module Datadog def to_hash: () -> untyped end end -end \ No newline at end of file +end From dd0a2318db29ec7f5367725a722db76aa69b188e Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 21 Mar 2024 14:09:18 -0400 Subject: [PATCH 4/6] use digest as argument --- lib/datadog/opentelemetry/sdk/trace/span.rb | 2 +- lib/datadog/tracing/span_link.rb | 23 ++-- sig/datadog/tracing/span_link.rbs | 6 +- sig/datadog/tracing/utils.rbs | 5 +- spec/datadog/tracing/span_link_spec.rb | 105 ++++++++++-------- .../transport/serializable_trace_spec.rb | 36 ++++-- 6 files changed, 102 insertions(+), 75 deletions(-) diff --git a/lib/datadog/opentelemetry/sdk/trace/span.rb b/lib/datadog/opentelemetry/sdk/trace/span.rb index 797809c36d2..6bf30657193 100644 --- a/lib/datadog/opentelemetry/sdk/trace/span.rb +++ b/lib/datadog/opentelemetry/sdk/trace/span.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require '../../../tracing/utils' +require 'datadog/tracing/utils' module Datadog module OpenTelemetry diff --git a/lib/datadog/tracing/span_link.rb b/lib/datadog/tracing/span_link.rb index 6874f871060..f911d982dcb 100644 --- a/lib/datadog/tracing/span_link.rb +++ b/lib/datadog/tracing/span_link.rb @@ -36,33 +36,28 @@ class SpanLink attr_reader :trace_state def initialize( - span_id: nil, - trace_id: nil, attributes: nil, - trace_flags: nil, - trace_state: nil + digest: nil ) - @span_id = span_id - @trace_id = trace_id - @attributes = attributes && attributes.dup.freeze - @trace_flags = trace_flags - @trace_state = trace_state && trace_state.dup.freeze + @span_id = digest&.span_id + @trace_id = digest&.trace_id + @trace_flags = digest&.trace_flags + @trace_state = digest&.trace_state && digest&.trace_state.dup @dropped_attributes = 0 - freeze + @attributes = (attributes && attributes.dup) || {} end def to_hash h = { - span_id: @span_id, - trace_id: Tracing::Utils::TraceId.to_low_order(@trace_id), - + span_id: @span_id || 0, + trace_id: Tracing::Utils::TraceId.to_low_order(@trace_id) || 0, } # Optimization: Hash non empty attributes if @trace_id.to_i > Tracing::Utils::EXTERNAL_MAX_ID h[:trace_id_high] = Tracing::Utils::TraceId.to_high_order(@trace_id) end - if @attributes + unless @attributes&.empty? h[:attributes] = {} @attributes.each do |k1, v1| Tracing::Utils.serialize_attribute(k1, v1).each do |new_k1, value| diff --git a/sig/datadog/tracing/span_link.rbs b/sig/datadog/tracing/span_link.rbs index 45815625410..2889beaed02 100644 --- a/sig/datadog/tracing/span_link.rbs +++ b/sig/datadog/tracing/span_link.rbs @@ -7,14 +7,14 @@ module Datadog @trace_id: untyped - @attributes: untyped - @trace_flags: untyped @trace_state: untyped @dropped_attributes: untyped + @attributes: untyped + # @!attribute [r] span_id # Datadog id for the currently active span. # @return [Integer] @@ -45,7 +45,7 @@ module Datadog # @see https://www.w3.org/TR/trace-context/#tracestate-header attr_reader trace_state: untyped - def initialize: (?span_id: untyped?, ?trace_id: untyped?, ?attributes: untyped?, ?trace_flags: untyped?, ?trace_state: untyped?) -> void + def initialize: (?attributes: untyped?, ?digest: untyped?) -> void def to_hash: () -> untyped end diff --git a/sig/datadog/tracing/utils.rbs b/sig/datadog/tracing/utils.rbs index b20ac2dbc97..589493e4180 100644 --- a/sig/datadog/tracing/utils.rbs +++ b/sig/datadog/tracing/utils.rbs @@ -10,6 +10,9 @@ module Datadog def self.id_rng: () -> untyped def self.reset!: () -> untyped + + def self.serialize_attribute: (untyped key, untyped value) -> (untyped | ::Array[::Array[untyped]]) + module TraceId MAX: untyped def self?.next_id: () -> untyped @@ -22,4 +25,4 @@ module Datadog end end end -end +end \ No newline at end of file diff --git a/spec/datadog/tracing/span_link_spec.rb b/spec/datadog/tracing/span_link_spec.rb index 6a0281038db..6094a1de9c9 100644 --- a/spec/datadog/tracing/span_link_spec.rb +++ b/spec/datadog/tracing/span_link_spec.rb @@ -2,107 +2,116 @@ require 'support/object_helpers' require 'datadog/tracing/span_link' +require 'datadog/tracing/trace_digest' RSpec.describe Datadog::Tracing::SpanLink do - subject(:span_link) { described_class.new(**options) } - let(:options) { {} } + subject(:span_link) { described_class.new(attributes: attributes, digest: digest) } + + let(:attributes) { nil } + let(:digest) do + Datadog::Tracing::TraceDigest.new( + span_id: span_id, + trace_id: trace_id, + trace_flags: trace_flags, + trace_state: trace_state, + ) + end + let(:span_id) { nil } + let(:trace_id) { nil } + let(:trace_state) { nil } + let(:trace_flags) { nil } describe '::new' do context 'by default' do + let(:digest) { nil } + let(:attributes) { nil } + it do is_expected.to have_attributes( span_id: nil, - attributes: nil, + attributes: {}, trace_id: nil, trace_flags: nil, trace_state: nil, ) end - - it { is_expected.to be_frozen } end context 'given' do - context ':span_id' do - let(:options) { { span_id: span_id } } - let(:span_id) { Datadog::Tracing::Utils.next_id } - - it { is_expected.to have_attributes(span_id: span_id) } - end - context ':attributes' do - let(:options) { { attributes: attributes } } let(:attributes) { { tag: 'value' } } - - it { is_expected.to have_attributes(attributes: be_a_frozen_copy_of(attributes)) } - end - - context ':trace_id' do - let(:options) { { trace_id: trace_id } } - let(:trace_id) { Datadog::Tracing::Utils::TraceId.next_id } - - it { is_expected.to have_attributes(trace_id: trace_id) } + it { is_expected.to have_attributes(attributes: attributes) } end - context ':trace_flags' do - let(:options) { { trace_flags: trace_flags } } - let(:trace_flags) { 0x01 } - - it { is_expected.to have_attributes(trace_flags: 0x01) } - end - - context ':trace_state' do - let(:options) { { trace_state: trace_state } } - let(:trace_state) { 'vendor1=value,v2=v' } - - it { is_expected.to have_attributes(trace_state: be_a_frozen_copy_of('vendor1=value,v2=v')) } + context ':digest with' do + context ':span_id' do + let(:span_id) { Datadog::Tracing::Utils.next_id } + it { is_expected.to have_attributes(span_id: span_id) } + end + + context ':trace_id' do + let(:trace_id) { Datadog::Tracing::Utils::TraceId.next_id } + it { is_expected.to have_attributes(trace_id: trace_id) } + end + + context ':trace_flags' do + let(:trace_flags) { 0x01 } + it { is_expected.to have_attributes(trace_flags: 0x01) } + end + + context ':trace_state' do + let(:trace_state) { 'vendor1=value,v2=v' } + it { is_expected.to have_attributes(trace_state: 'vendor1=value,v2=v') } + end end end end describe '#to_hash' do subject(:to_hash) { span_link.to_hash } + let(:span_id) { 34 } + let(:trace_id) { 12 } context 'with required fields' do - let(:options) { { span_id: 34, trace_id: 12 } } - context 'when trace_id < 2^64' do it { is_expected.to eq(trace_id: 12, span_id: 34, flags: 0) } end context 'when trace_id >= 2^64' do - let(:options) { { span_id: 34, trace_id: 2**64 + 12 } } + let(:trace_id) { 2**64 + 12 } it { is_expected.to eq(trace_id: 12, trace_id_high: 1, span_id: 34, flags: 0) } end end - context 'with trace_state' do - let(:options) { { span_id: 34, trace_id: 12, trace_state: 'dd=s:1' } } + context 'required fields are set to nil' do + let(:span_id) { nil } + let(:trace_id) { nil } + it { is_expected.to eq(trace_id: 0, span_id: 0, flags: 0) } + end + + context 'when trace_state is set' do + let(:trace_state) { 'dd=s:1' } it { is_expected.to include(tracestate: 'dd=s:1') } end - context 'with trace_flag' do + context 'when trace_flag is set' do context 'when trace_flag is unset' do - let(:options) { { span_id: 34, trace_id: 12 } } it { is_expected.to include(flags: 0) } end context 'when trace_flags is 0' do - let(:options) { { span_id: 34, trace_id: 12, trace_flags: 0 } } + let(:trace_flags) { 0 } it { is_expected.to include(flags: 2147483648) } end context 'when trace_flag is 1' do - let(:options) { { span_id: 34, trace_id: 12, trace_flags: 1 } } + let(:trace_flags) { 1 } it { is_expected.to include(flags: 2147483649) } end end - context 'with attributes' do - let(:options) do - { span_id: 34, trace_id: 12, - attributes: { 'link.name' => :test_link, 'link.id' => 1, 'nested' => [true, [2, 3], 'val'] } } - end + context 'when attributes is set' do + let(:attributes) { { 'link.name' => :test_link, 'link.id' => 1, 'nested' => [true, [2, 3], 'val'] } } it { is_expected.to include( attributes: { 'link.name' => 'test_link', 'link.id' => '1', 'nested.0' => 'true', diff --git a/spec/datadog/tracing/transport/serializable_trace_spec.rb b/spec/datadog/tracing/transport/serializable_trace_spec.rb index 4c961117e24..e558f24770f 100644 --- a/spec/datadog/tracing/transport/serializable_trace_spec.rb +++ b/spec/datadog/tracing/transport/serializable_trace_spec.rb @@ -78,13 +78,26 @@ Array.new(3) do |_i| Datadog::Tracing::Span.new( 'dummy', - links: [Datadog::Tracing::SpanLink.new( - trace_id: 0xaaaaaaaaaaaaaaaaffffffffffffffff, - span_id: 0x1, - trace_state: 'vendor1=value,v2=v,dd=s:1', - trace_flags: 0x1, - attributes: { 'link.name' => 'test_link' } - )] + links: [ + Datadog::Tracing::SpanLink.new( + digest: Datadog::Tracing::TraceDigest.new( + trace_id: 0xaaaaaaaaaaaaaaaaffffffffffffffff, + span_id: 0x1, + trace_state: 'vendor1=value,v2=v,dd=s:1', + trace_flags: 0x1, + ), + attributes: { 'link.name' => 'test_link' } + ), + Datadog::Tracing::SpanLink.new( + digest: Datadog::Tracing::TraceDigest.new( + trace_id: 0xa0123456789abcdef, + span_id: 0x2, + ), + ), + Datadog::Tracing::SpanLink.new( + digest: Datadog::Tracing::TraceDigest.new, + ) + ], ) end end @@ -103,7 +116,14 @@ 'attributes' => { 'link.name' => 'test_link' }, 'flags' => 2147483649, 'tracestate' => 'vendor1=value,v2=v,dd=s:1', - }] + }, + { + 'span_id' => 2, + 'trace_id' => 0x0123456789abcdef, + 'trace_id_high' => 10, + 'flags' => 0 + }, + { 'span_id' => 0, 'trace_id' => 0, 'flags' => 0 }] ) ) end From c594f9458ad7f1df74a0ed690f4cd362990d35ba Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Thu, 21 Mar 2024 18:48:02 -0400 Subject: [PATCH 5/6] Apply suggestions from code review --- spec/datadog/tracing/span_link_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/datadog/tracing/span_link_spec.rb b/spec/datadog/tracing/span_link_spec.rb index 6094a1de9c9..d9e1dcdce8d 100644 --- a/spec/datadog/tracing/span_link_spec.rb +++ b/spec/datadog/tracing/span_link_spec.rb @@ -83,7 +83,7 @@ end end - context 'required fields are set to nil' do + context 'when required fields are not set' do let(:span_id) { nil } let(:trace_id) { nil } it { is_expected.to eq(trace_id: 0, span_id: 0, flags: 0) } From c6431dd1a2f4c5c1109927271e585d4d37d22831 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Sat, 30 Mar 2024 04:16:34 +0300 Subject: [PATCH 6/6] review comments --- lib/datadog/tracing/span.rb | 2 +- lib/datadog/tracing/span_link.rb | 5 +++++ lib/datadog/tracing/transport/serializable_trace.rb | 2 +- sig/datadog/tracing/span_link.rbs | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/datadog/tracing/span.rb b/lib/datadog/tracing/span.rb index 187d25c31e1..57f156dcef1 100644 --- a/lib/datadog/tracing/span.rb +++ b/lib/datadog/tracing/span.rb @@ -141,7 +141,7 @@ def to_hash span_id: @id, trace_id: @trace_id, type: @type, - span_links: @links.map!(&:to_hash) + span_links: @links.map(&:to_hash) } if stopped? diff --git a/lib/datadog/tracing/span_link.rb b/lib/datadog/tracing/span_link.rb index f911d982dcb..8194b2e1c46 100644 --- a/lib/datadog/tracing/span_link.rb +++ b/lib/datadog/tracing/span_link.rb @@ -35,6 +35,11 @@ class SpanLink # @see https://www.w3.org/TR/trace-context/#tracestate-header attr_reader :trace_state + # @!attribute [r] dropped_attributes + # The number of attributes that were discarded due to serialization limits. + # @return [Integer] + attr_reader :dropped_attributes + def initialize( attributes: nil, digest: nil diff --git a/lib/datadog/tracing/transport/serializable_trace.rb b/lib/datadog/tracing/transport/serializable_trace.rb index 44cfb696280..b056af71c2a 100644 --- a/lib/datadog/tracing/transport/serializable_trace.rb +++ b/lib/datadog/tracing/transport/serializable_trace.rb @@ -94,7 +94,7 @@ def to_msgpack(packer = nil) packer.write('metrics') packer.write(span.metrics) packer.write('span_links') - packer.write(span.links.map!(&:to_hash)) + packer.write(span.links.map(&:to_hash)) packer.write('error') packer.write(span.status) packer diff --git a/sig/datadog/tracing/span_link.rbs b/sig/datadog/tracing/span_link.rbs index 2889beaed02..e2dcc49aef1 100644 --- a/sig/datadog/tracing/span_link.rbs +++ b/sig/datadog/tracing/span_link.rbs @@ -45,6 +45,11 @@ module Datadog # @see https://www.w3.org/TR/trace-context/#tracestate-header attr_reader trace_state: untyped + # @!attribute [r] dropped_attributes + # The number of attributes that were discarded due to serialization limits. + # @return [Integer] + attr_reader dropped_attributes: untyped + def initialize: (?attributes: untyped?, ?digest: untyped?) -> void def to_hash: () -> untyped