Skip to content

Commit

Permalink
Merge pull request #3546 from DataDog/munir/add-dd-span-links
Browse files Browse the repository at this point in the history
  • Loading branch information
marcotc committed Apr 2, 2024
2 parents d872d68 + c6431dd commit 584ce50
Show file tree
Hide file tree
Showing 11 changed files with 360 additions and 22 deletions.
2 changes: 1 addition & 1 deletion lib/datadog/opentelemetry/sdk/span_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
20 changes: 3 additions & 17 deletions lib/datadog/opentelemetry/sdk/trace/span.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'datadog/tracing/utils'

module Datadog
module OpenTelemetry
module Trace
Expand Down Expand Up @@ -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/
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 7 additions & 2 deletions lib/datadog/tracing/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Span
:parent_id,
:resource,
:service,
:links,
:type,
:start_time,
:status,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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?
Expand Down
86 changes: 86 additions & 0 deletions lib/datadog/tracing/span_link.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 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]
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<String,String>]
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.
# 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

# @!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
)
@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
@attributes = (attributes && attributes.dup) || {}
end

def to_hash
h = {
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
unless @attributes&.empty?
h[:attributes] = {}
@attributes.each do |k1, v1|
Tracing::Utils.serialize_attribute(k1, v1).each do |new_k1, value|
h[:attributes][new_k1] = value.to_s
end
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.nil?
0
else
@trace_flags | (1 << 31)
end
h
end
end
end
end
4 changes: 3 additions & 1 deletion lib/datadog/tracing/transport/serializable_trace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions lib/datadog/tracing/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions sig/datadog/tracing/span_link.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module Datadog
module Tracing
# SpanLink represents a causal link between two spans.
# @public_api
class SpanLink
@span_id: untyped

@trace_id: 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]
attr_reader span_id: untyped

# @!attribute [r] trace_id
# Datadog id for the currently active trace.
# @return [Integer]
attr_reader trace_id: untyped

# @!attribute [r] attributes
# Datadog-specific tags that support richer distributed tracing association.
# @return [Hash<String,String>]
attr_reader attributes: untyped

# @!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: untyped

# @!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

# @!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
end
end
end
5 changes: 4 additions & 1 deletion sig/datadog/tracing/utils.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,4 +25,4 @@ module Datadog
end
end
end
end
end
Loading

0 comments on commit 584ce50

Please sign in to comment.