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(propagation): baggage support and automatic propagation #4371

Draft
wants to merge 20 commits into
base: master
Choose a base branch
from
Draft
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

* Tracing: Added support for Opentelemetry Baggage extraction and injection by default.

## [2.10.0] - 2025-02-04

### Added
Expand Down Expand Up @@ -4731,4 +4735,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
[@y-yagi]: https://github.com/y-yagi
[@yujideveloper]: https://github.com/yujideveloper
[@yukimurasawa]: https://github.com/yukimurasawa
[@zachmccormick]: https://github.com/zachmccormick
[@zachmccormick]: https://github.com/zachmccormick
7 changes: 7 additions & 0 deletions lib/datadog/tracing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ def log_correlation
correlation.to_log_format
end

def baggage
# Baggage should not be dependent on there being an active trace.
# So we create a new TraceOperation if there isn't one.
active_trace = self.active_trace || tracer.continue_trace!(nil)
active_trace.baggage
end

# Gracefully shuts down the tracer.
#
# The public tracing API will still respond to method calls as usual
Expand Down
7 changes: 6 additions & 1 deletion lib/datadog/tracing/configuration/ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ module Distributed
# W3C Trace Context
PROPAGATION_STYLE_TRACE_CONTEXT = 'tracecontext'

# W3C Baggage
# @see https://www.w3.org/TR/baggage/
PROPAGATION_STYLE_BAGGAGE = 'baggage'

PROPAGATION_STYLE_SUPPORTED = [PROPAGATION_STYLE_DATADOG, PROPAGATION_STYLE_B3_MULTI_HEADER,
PROPAGATION_STYLE_B3_SINGLE_HEADER, PROPAGATION_STYLE_TRACE_CONTEXT].freeze
PROPAGATION_STYLE_B3_SINGLE_HEADER, PROPAGATION_STYLE_TRACE_CONTEXT,
PROPAGATION_STYLE_BAGGAGE].freeze

# Sets both extract and inject propagation style tho the provided value.
# Has lower precedence than `DD_TRACE_PROPAGATION_STYLE_INJECT` or
Expand Down
4 changes: 3 additions & 1 deletion lib/datadog/tracing/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def self.extended(base)
#
# The tracer will try to find distributed headers in the order they are present in the list provided to this option.
# The first format to have valid data present will be used.
#
# Baggage style is a special case, as it will always be extracted in addition if present.
# @default `DD_TRACE_PROPAGATION_STYLE_EXTRACT` environment variable (comma-separated list),
# otherwise `['datadog','b3multi','b3']`.
# @return [Array<String>]
Expand All @@ -53,6 +53,7 @@ def self.extended(base)
[
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG,
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT,
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_BAGGAGE,
]
)
o.after_set do |styles|
Expand All @@ -74,6 +75,7 @@ def self.extended(base)
o.default [
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_DATADOG,
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT,
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_BAGGAGE,
]
o.after_set do |styles|
# Make values case-insensitive
Expand Down
2 changes: 2 additions & 0 deletions lib/datadog/tracing/contrib/grpc/distributed/propagation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def initialize(
Tracing::Distributed::Datadog.new(fetcher: Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT =>
Tracing::Distributed::TraceContext.new(fetcher: Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_BAGGAGE =>
Tracing::Distributed::Baggage.new(fetcher: Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new
},
propagation_style_inject: propagation_style_inject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def initialize(
Tracing::Distributed::Datadog.new(fetcher: Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT =>
Tracing::Distributed::TraceContext.new(fetcher: Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_BAGGAGE =>
Tracing::Distributed::Baggage.new(fetcher: Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new,
},
propagation_style_inject: propagation_style_inject,
propagation_style_extract: propagation_style_extract,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def initialize(
Tracing::Distributed::Datadog.new(fetcher: Tracing::Distributed::Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_TRACE_CONTEXT =>
Tracing::Distributed::TraceContext.new(fetcher: Tracing::Distributed::Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_BAGGAGE =>
Tracing::Distributed::Baggage.new(fetcher: Tracing::Distributed::Fetcher),
Tracing::Configuration::Ext::Distributed::PROPAGATION_STYLE_NONE => Tracing::Distributed::None.new
},
propagation_style_inject: propagation_style_inject,
Expand Down
116 changes: 116 additions & 0 deletions lib/datadog/tracing/distributed/baggage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# frozen_string_literal: true

require_relative '../metadata/ext'
require_relative '../trace_digest'
require_relative 'datadog_tags_codec'
require_relative '../utils'
require_relative 'helpers'
require 'uri'
require 'cgi'

module Datadog
module Tracing
module Distributed
# W3C Baggage propagator implementation.
# The baggage header is propagated through `baggage`.
# @see https://www.w3.org/TR/baggage/
class Baggage
BAGGAGE_KEY = 'baggage'
DD_TRACE_BAGGAGE_MAX_ITEMS = 64
DD_TRACE_BAGGAGE_MAX_BYTES = 8192
SAFE_CHARACTERS_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$!#&'*+-.^_`|~"
SAFE_CHARACTERS_VALUE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$!#&'()*+-./:<>?@[]^_`{|}~"

def initialize(
fetcher:,
baggage_key: BAGGAGE_KEY
)
@baggage_key = baggage_key
@fetcher = fetcher
end

def inject!(digest, data)
return if digest.nil? || digest.baggage.nil?

baggage_items = digest.baggage.to_a
return if baggage_items.empty?

begin
if baggage_items.size > DD_TRACE_BAGGAGE_MAX_ITEMS
::Datadog.logger.warn('Baggage item limit exceeded, dropping excess items')
baggage_items = baggage_items.first(DD_TRACE_BAGGAGE_MAX_ITEMS)
end

encoded_items = []
total_size = 0

baggage_items.each do |key, value|
item = "#{encode_key(key)}=#{encode_value(value)}"
item_size = item.bytesize + (encoded_items.empty? ? 0 : 1) # +1 for comma if not first item
if total_size + item_size > DD_TRACE_BAGGAGE_MAX_BYTES
::Datadog.logger.warn('Baggage header size exceeded, dropping excess items')
break # stop adding items when size limit is reached
end
encoded_items << item
total_size += item_size
end

# edge case where a single item is too large
return if encoded_items.empty?

header_value = encoded_items.join(',')
data[@baggage_key] = header_value
rescue => e
::Datadog.logger.warn("Failed to encode and inject baggage header: #{e.message}")
end
end

def extract(data)
fetcher = @fetcher.new(data)
data = fetcher[@baggage_key]
return unless data

baggage = parse_baggage_header(fetcher[@baggage_key])
return unless baggage

TraceDigest.new(
baggage: baggage,
)
end

private

# We can't use uri encode because it incorrectly encodes some characters
def encode_key(key)
CGI.escape(key.strip).gsub('+', '%20').gsub(/%[0-9A-F]{2}/) do |encoded|
char = [encoded[1..2].hex].pack('C')
SAFE_CHARACTERS_KEY.include?(char) ? char : encoded
end
end

def encode_value(value)
CGI.escape(value.strip).gsub('+', '%20').gsub(/%[0-9A-F]{2}/) do |encoded|
char = [encoded[1..2].hex].pack('C')
SAFE_CHARACTERS_VALUE.include?(char) ? char : encoded
end
end

def parse_baggage_header(baggage_header)
baggage = {}
baggages = baggage_header.split(',')
baggages.each do |key_value|
key, value = key_value.split('=', 2)
next unless key && value

key = URI.decode_www_form_component(key.strip)
value = URI.decode_www_form_component(value.strip)
next if key.empty? || value.empty?

baggage[key] = value
end
baggage
end
end
end
end
end
28 changes: 25 additions & 3 deletions lib/datadog/tracing/distributed/propagation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require_relative '../trace_digest'
require_relative '../trace_operation'
require_relative '../../core/telemetry/logger'
require_relative 'baggage'

module Datadog
module Tracing
Expand All @@ -26,9 +27,13 @@ def initialize(
)
@propagation_styles = propagation_styles
@propagation_extract_first = propagation_extract_first

@propagation_style_inject = propagation_style_inject.map { |style| propagation_styles[style] }
@propagation_style_extract = propagation_style_extract.map { |style| propagation_styles[style] }

# The baggage propagator is unique in that baggage should always be extracted, if present.
# Therefore we remove it from the `propagation_style_extract` list.
@baggage_propagator = @propagation_style_extract.find { |propagator| propagator.is_a?(Baggage) }
@propagation_style_extract.delete(@baggage_propagator) if @baggage_propagator
end

# inject! populates the env with span ID, trace ID and sampling priority
Expand Down Expand Up @@ -57,8 +62,8 @@ def inject!(digest, data)
end

digest = digest.to_digest if digest.respond_to?(:to_digest)

if digest.trace_id.nil?
# it's right here we drop out because there's no trace id in the digest, just a baggage object
if digest.trace_id.nil? && digest.baggage.nil?
::Datadog.logger.debug('Cannot inject distributed trace data: digest.trace_id is nil.')
return nil
end
Expand Down Expand Up @@ -138,12 +143,29 @@ def extract(data)
"Error extracting distributed trace data. Cause: #{e} Location: #{Array(e.backtrace).first}"
)
end
# Handle baggage after all other styles if present
extracted_trace_digest = propagate_baggage(data, extracted_trace_digest) if @baggage_propagator

extracted_trace_digest
end

private

def propagate_baggage(data, extracted_trace_digest)
if extracted_trace_digest
# Merge with baggage if present
digest = @baggage_propagator.extract(data)
if digest
extracted_trace_digest.merge(baggage: digest.baggage)
else
extracted_trace_digest
end
else
# Baggage is the only style
@baggage_propagator.extract(data)
end
end

def last_datadog_parent_id(headers, tracecontext_tags)
dd_propagator = @propagation_style_extract.find { |propagator| propagator.is_a?(Datadog) }
if tracecontext_tags&.fetch(
Expand Down
11 changes: 9 additions & 2 deletions lib/datadog/tracing/trace_digest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ class TraceDigest
# This allows later propagation to include those unknown fields, as they can represent future versions of the spec
# sending data through this service. This value ends in a trailing `;` to facilitate serialization.
# @return [String]
# @!attribute [r] baggage
# The W3C "baggage" extracted from a distributed context. This field is a hash of key/value pairs.
# @return [Hash<String,String>]
# TODO: The documentation for the last attribute above won't be rendered.
# TODO: This might be a YARD bug as adding an attribute, making it now second-last attribute, renders correctly.
attr_reader \
Expand All @@ -102,7 +105,8 @@ class TraceDigest
:trace_flags,
:trace_state,
:trace_state_unknown_fields,
:span_remote
:span_remote,
:baggage

def initialize(
span_id: nil,
Expand All @@ -124,7 +128,8 @@ def initialize(
trace_flags: nil,
trace_state: nil,
trace_state_unknown_fields: nil,
span_remote: true
span_remote: true,
baggage: nil
)
@span_id = span_id
@span_name = span_name && span_name.dup.freeze
Expand All @@ -146,6 +151,7 @@ def initialize(
@trace_state = trace_state && trace_state.dup.freeze
@trace_state_unknown_fields = trace_state_unknown_fields && trace_state_unknown_fields.dup.freeze
@span_remote = span_remote
@baggage = baggage && baggage.dup.freeze
freeze
end

Expand Down Expand Up @@ -177,6 +183,7 @@ def merge(field_value_pairs)
trace_state: trace_state,
trace_state_unknown_fields: trace_state_unknown_fields,
span_remote: span_remote,
baggage: baggage
}.merge!(field_value_pairs)
)
end
Expand Down
8 changes: 6 additions & 2 deletions lib/datadog/tracing/trace_operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class TraceOperation
:rule_sample_rate,
:sample_rate,
:sampling_priority,
:remote_parent
:remote_parent,
:baggage

attr_reader \
:active_span_count,
Expand Down Expand Up @@ -76,7 +77,8 @@ def initialize(
trace_state: nil,
trace_state_unknown_fields: nil,
remote_parent: false,
tracer: nil
tracer: nil,
baggage: nil

)
# Attributes
Expand All @@ -101,6 +103,7 @@ def initialize(
@trace_state = trace_state
@trace_state_unknown_fields = trace_state_unknown_fields
@tracer = tracer
@baggage = baggage || {}

# Generic tags
set_tags(tags) if tags
Expand Down Expand Up @@ -332,6 +335,7 @@ def to_digest
trace_state: @trace_state,
trace_state_unknown_fields: @trace_state_unknown_fields,
span_remote: (@remote_parent && @active_span.nil?),
baggage: @baggage.nil? || @baggage.empty? ? nil : @baggage
).freeze
end

Expand Down
3 changes: 2 additions & 1 deletion lib/datadog/tracing/tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,8 @@ def build_trace(digest = nil)
trace_state: digest.trace_state,
trace_state_unknown_fields: digest.trace_state_unknown_fields,
remote_parent: digest.span_remote,
tracer: self
tracer: self,
baggage: digest.baggage
)
else
TraceOperation.new(
Expand Down
1 change: 1 addition & 0 deletions sig/datadog/tracing/configuration/ext.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module Datadog
PROPAGATION_STYLE_B3_MULTI_HEADER: "b3multi"
PROPAGATION_STYLE_B3_SINGLE_HEADER: "b3"
PROPAGATION_STYLE_TRACE_CONTEXT: "tracecontext"
PROPAGATION_STYLE_BAGGAGE: "baggage"
ENV_PROPAGATION_STYLE: "DD_TRACE_PROPAGATION_STYLE"

ENV_PROPAGATION_STYLE_INJECT: "DD_TRACE_PROPAGATION_STYLE_INJECT"
Expand Down
Loading
Loading