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

128 bits trace id #2543

Merged
merged 41 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
eee33a7
Define 128 bit trace id configuration options with environment variables
TonyCTHsu Dec 19, 2022
ca8009a
Logs a warning message when generation is enabled but not propagation
TonyCTHsu Dec 19, 2022
fc9e2a2
Documentation for configuration
TonyCTHsu Dec 19, 2022
613b181
Default propagation to be `true`
TonyCTHsu Dec 20, 2022
56f81d4
Improve warning message
TonyCTHsu Dec 20, 2022
92b47a6
Rubocop
TonyCTHsu Dec 21, 2022
6ee7180
128 bit trace_id generation
TonyCTHsu Dec 20, 2022
769a3e2
Refactor module and test
TonyCTHsu Dec 21, 2022
3181f43
Rubocop
TonyCTHsu Dec 21, 2022
40ab0fa
Implement 128 bit logging
TonyCTHsu Dec 21, 2022
fdf6097
Refactor to move trace id utilities
TonyCTHsu Dec 22, 2022
753a0cb
Change logging implementation
TonyCTHsu Dec 23, 2022
a810fc1
Refactor to move trace id utilities
TonyCTHsu Dec 22, 2022
2b09387
Change implementation for generation
TonyCTHsu Dec 23, 2022
588b2ad
Implement propagation
TonyCTHsu Dec 23, 2022
96f665a
Refactor to remove configuration for 128 bit trace id propagation
TonyCTHsu Jan 10, 2023
fc9f914
Skip set `_dd.p.tid` internally
TonyCTHsu Jan 11, 2023
53a47a2
Fix datadog inject!
TonyCTHsu Jan 11, 2023
644bc28
Merge branch 'master' into tonycthsu/128bits-trace-id-propagation
TonyCTHsu Jan 24, 2023
3db0910
Refactor with Utils::TraceId
TonyCTHsu Jan 24, 2023
37f5dfc
Fix opentracer spec
TonyCTHsu Jan 24, 2023
99dbb92
Fix propagation and spec
TonyCTHsu Jan 24, 2023
26d5ca3
Remove obsolete spec
TonyCTHsu Jan 24, 2023
82c343e
Fix serializable trace legacy JSON format
TonyCTHsu Jan 24, 2023
41bdfdc
Merge branch 'master' into tonycthsu/128bits-trace-id-propagation
TonyCTHsu Jan 31, 2023
779117e
Implement 128 bit trace id with timestamp
TonyCTHsu Jan 31, 2023
6ec95b9
Change base to keyword argument
TonyCTHsu Jan 31, 2023
28c3c3e
Merge branch 'master' into tonycthsu/128bits-trace-id-propagation
TonyCTHsu Mar 1, 2023
d47fb52
WIP
TonyCTHsu Mar 1, 2023
0da3589
WIP
TonyCTHsu Mar 1, 2023
52c7a9a
WIP
TonyCTHsu Mar 1, 2023
4f8a8ad
Extract helpers
TonyCTHsu Mar 1, 2023
ffb81de
Cleanup
TonyCTHsu Mar 2, 2023
dcc964c
Change SerializableSpan without mutating Span
TonyCTHsu Mar 3, 2023
11ad7a7
Generate timestamp with Core::Utils::Time
TonyCTHsu Mar 3, 2023
15d1cc6
Remove truncation when parsing hex
TonyCTHsu Mar 3, 2023
c9ea54b
Remove lenient conversion #to_i for Utils::TraceId
TonyCTHsu Mar 3, 2023
352e45e
Lint
TonyCTHsu Mar 3, 2023
867b104
Add documentation and comment
TonyCTHsu Mar 3, 2023
4fa5f78
Caching `logging_trace_id`
TonyCTHsu Mar 3, 2023
b618b70
Merge branch 'master' into tonycthsu/128bits-trace-id-propagation
TonyCTHsu Mar 3, 2023
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
1 change: 1 addition & 0 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,7 @@ end
| `tracing.sampling.default_rate` | `DD_TRACE_SAMPLE_RATE` | `nil` | Sets the trace sampling rate between `0.0` (0%) and `1.0` (100%). See [Application-side sampling](#application-side-sampling) for details. |
| `tracing.sampling.rate_limit` | `DD_TRACE_RATE_LIMIT` | `100` (per second) | Sets a maximum number of traces per second to sample. Set a rate limit to avoid the ingestion volume overages in the case of traffic spikes. |
| `tracing.sampling.span_rules` | `DD_SPAN_SAMPLING_RULES`,`ENV_SPAN_SAMPLING_RULES_FILE` | `nil` | Sets [Single Span Sampling](#single-span-sampling) rules. These rules allow you to keep spans even when their respective traces are dropped. |
| `tracing.trace_id_128_bit_generation_enabled` | `DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED` | `false` | `true` to generate 128 bits trace ID and `false` to generate 64 bits trace ID |
| `tracing.report_hostname` | `DD_TRACE_REPORT_HOSTNAME` | `false` | Adds hostname tag to traces. |
| `tracing.test_mode.enabled` | `DD_TRACE_TEST_MODE_ENABLED` | `false` | Enables or disables test mode, for use of tracing in test suites. |
| `tracing.test_mode.trace_flush` | | `nil` | Object that determines trace flushing behavior. |
Expand Down
2 changes: 2 additions & 0 deletions lib/datadog/tracing/configuration/ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Configuration
# e.g. Env vars, default values, enums, etc...
module Ext
ENV_ENABLED = 'DD_TRACE_ENABLED'.freeze
ENV_TRACE_ID_128_BIT_GENERATION_ENABLED = 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED'.freeze

# @public_api
module Analytics
Expand All @@ -14,6 +15,7 @@ module Analytics
# @public_api
module Correlation
ENV_LOGS_INJECTION_ENABLED = 'DD_LOGS_INJECTION'.freeze
ENV_TRACE_ID_128_BIT_LOGGING_ENABLED = 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED'.freeze
end

# @public_api
Expand Down
20 changes: 20 additions & 0 deletions lib/datadog/tracing/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,26 @@ def self.extended(base)
o.lazy
end

# Enable 128 bit trace id generation.
#
# @default `DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED` environment variable, otherwise `false`
# @return [Boolean]
option :trace_id_128_bit_generation_enabled do |o|
TonyCTHsu marked this conversation as resolved.
Show resolved Hide resolved
o.default { env_to_bool(Tracing::Configuration::Ext::ENV_TRACE_ID_128_BIT_GENERATION_ENABLED, false) }
o.lazy
end

# Enable 128 bit trace id injected for logging.
TonyCTHsu marked this conversation as resolved.
Show resolved Hide resolved
#
# @default `DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED` environment variable, otherwise `false`
# @return [Boolean]
#
# It is not supported by our backend yet. Do not enable it.
option :trace_id_128_bit_logging_enabled do |o|
o.default { env_to_bool(Tracing::Configuration::Ext::Correlation::ENV_TRACE_ID_128_BIT_LOGGING_ENABLED, false) }
o.lazy
end

# A custom tracer instance.
#
# It must respect the contract of {Datadog::Tracing::Tracer}.
Expand Down
16 changes: 15 additions & 1 deletion lib/datadog/tracing/correlation.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require_relative '../core'
require_relative 'utils'
require_relative 'metadata/ext'

module Datadog
module Tracing
Expand Down Expand Up @@ -64,11 +66,23 @@ def to_log_format
attributes << "#{LOG_ATTR_ENV}=#{env}" unless env.nil?
attributes << "#{LOG_ATTR_SERVICE}=#{service}"
attributes << "#{LOG_ATTR_VERSION}=#{version}" unless version.nil?
attributes << "#{LOG_ATTR_TRACE_ID}=#{trace_id}"
attributes << "#{LOG_ATTR_TRACE_ID}=#{logging_trace_id}"
attributes << "#{LOG_ATTR_SPAN_ID}=#{span_id}"
attributes.join(' ')
end
end

private

def logging_trace_id
@logging_trace_id ||=
if Datadog.configuration.tracing.trace_id_128_bit_logging_enabled &&
!Tracing::Utils::TraceId.to_high_order(@trace_id).zero?
Kernel.format('%032x', trace_id)
else
Tracing::Utils::TraceId.to_low_order(@trace_id)
end
end
end

module_function
Expand Down
17 changes: 12 additions & 5 deletions lib/datadog/tracing/distributed/b3_multi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require_relative 'helpers'
require_relative '../trace_digest'
require_relative '../utils'

module Datadog
module Tracing
Expand Down Expand Up @@ -45,13 +46,19 @@ def inject!(digest, data = {})
def extract(data)
# DEV: B3 doesn't have "origin"
fetcher = @fetcher.new(data)
trace_id = fetcher.id(@trace_id_key, base: 16)
span_id = fetcher.id(@span_id_key, base: 16)
# We don't need to try and convert sampled since B3 supports 0/1 (AUTO_REJECT/AUTO_KEEP)
sampling_priority = fetcher.number(@sampled_key)

trace_id = Helpers.parse_hex_id(fetcher[@trace_id_key])

# Return early if this propagation is not valid
return unless trace_id && span_id
return if trace_id.nil? || trace_id <= 0 || trace_id > Tracing::Utils::TraceId::MAX

span_id = Helpers.parse_hex_id(fetcher[@span_id_key])

# Return early if this propagation is not valid
return if span_id.nil? || span_id <= 0 || span_id >= Tracing::Utils::EXTERNAL_MAX_ID

# We don't need to try and convert sampled since B3 supports 0/1 (AUTO_REJECT/AUTO_KEEP)
sampling_priority = Helpers.parse_decimal_id(fetcher[@sampled_key])

TraceDigest.new(
trace_id: trace_id,
Expand Down
13 changes: 8 additions & 5 deletions lib/datadog/tracing/distributed/b3_single.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,15 @@ def extract(env)
return unless value

parts = value.split('-')
trace_id = Helpers.value_to_id(parts[0], base: 16) unless parts.empty?
span_id = Helpers.value_to_id(parts[1], base: 16) if parts.length > 1
sampling_priority = Helpers.value_to_number(parts[2]) if parts.length > 2
trace_id = Helpers.parse_hex_id(parts[0]) unless parts.empty?
# Return early if this propagation is not valid
return if trace_id.nil? || trace_id <= 0 || trace_id > Tracing::Utils::TraceId::MAX

# Return if this propagation is not valid
return unless trace_id && span_id
span_id = Helpers.parse_hex_id(parts[1]) if parts.length > 1
# Return early if this propagation is not valid
return if span_id.nil? || span_id <= 0 || span_id >= Tracing::Utils::EXTERNAL_MAX_ID

sampling_priority = Helpers.parse_decimal_id(parts[2]) if parts.length > 2

TraceDigest.new(
span_id: span_id,
Expand Down
68 changes: 58 additions & 10 deletions lib/datadog/tracing/distributed/datadog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
require_relative '../metadata/ext'
require_relative '../trace_digest'
require_relative 'datadog_tags_codec'
require_relative '../utils'
require_relative 'helpers'

module Datadog
module Tracing
Expand Down Expand Up @@ -38,21 +40,26 @@ def initialize(
def inject!(digest, data)
return if digest.nil?

data[@trace_id_key] = digest.trace_id.to_s
data[@trace_id_key] = Tracing::Utils::TraceId.to_low_order(digest.trace_id).to_s

data[@parent_id_key] = digest.span_id.to_s
data[@sampling_priority_key] = digest.trace_sampling_priority.to_s if digest.trace_sampling_priority
data[@origin_key] = digest.trace_origin.to_s if digest.trace_origin

inject_tags(digest, data)
build_tags(digest).tap do |tags|
inject_tags!(tags, data) unless tags.empty?
end

data
end

def extract(data)
fetcher = @fetcher.new(data)
trace_id = fetcher.id(@trace_id_key)
parent_id = fetcher.id(@parent_id_key)
sampling_priority = fetcher.number(@sampling_priority_key)

trace_id = parse_trace_id(fetcher)
parent_id = parse_parent_id(fetcher)

sampling_priority = Helpers.parse_decimal_id(fetcher[@sampling_priority_key])
origin = fetcher[@origin_key]

# Return early if this propagation is not valid
Expand All @@ -63,6 +70,10 @@ def extract(data)

trace_distributed_tags = extract_tags(fetcher)

# If trace id is 128 bits long,
# Concatentated high order 64 bit hex-encoded `tid` tag
trace_id = extract_trace_id!(trace_id, trace_distributed_tags)

TraceDigest.new(
span_id: parent_id,
trace_id: trace_id,
Expand All @@ -74,20 +85,57 @@ def extract(data)

private

def parse_trace_id(fetcher_object)
trace_id = Helpers.parse_decimal_id(fetcher_object[@trace_id_key])

return unless trace_id
return if trace_id <= 0 || trace_id >= Tracing::Utils::EXTERNAL_MAX_ID

trace_id
end

def parse_parent_id(fetcher_object)
parent_id = Helpers.parse_decimal_id(fetcher_object[@parent_id_key])

return unless parent_id
return if parent_id <= 0 || parent_id >= Tracing::Utils::EXTERNAL_MAX_ID

parent_id
end

def build_tags(digest)
high_order = Tracing::Utils::TraceId.to_high_order(digest.trace_id)
tags = digest.trace_distributed_tags || {}

return tags if high_order == 0

tags.merge(Tracing::Metadata::Ext::Distributed::TAG_TID => high_order.to_s(16))
end

# Side effect: Remove high order 64 bit hex-encoded `tid` tag from distributed tags
def extract_trace_id!(trace_id, tags)
return trace_id unless tags
return trace_id unless (high_order = tags.delete(Tracing::Metadata::Ext::Distributed::TAG_TID))

Tracing::Utils::TraceId.concatenate(high_order.to_i(16), trace_id)
end

# Export trace distributed tags through the `x-datadog-tags` key.
#
# DEV: This method accesses global state (the active trace) to record its error state as a trace tag.
# DEV: This means errors cannot be reported if there's not active span.
# DEV: Ideally, we'd have a dedicated error reporting stream for all of ddtrace.
def inject_tags(digest, data)
return if digest.trace_distributed_tags.nil? || digest.trace_distributed_tags.empty?
def inject_tags!(tags, data)
return set_tags_propagation_error(reason: 'disabled') if tags_disabled?

tags = DatadogTagsCodec.encode(digest.trace_distributed_tags)
encoded_tags = DatadogTagsCodec.encode(tags)

return set_tags_propagation_error(reason: 'inject_max_size') if tags_too_large?(tags.size, scenario: 'inject')
return set_tags_propagation_error(reason: 'inject_max_size') if tags_too_large?(
encoded_tags.size,
scenario: 'inject'
)

data[@tags_key] = tags
data[@tags_key] = encoded_tags
rescue => e
set_tags_propagation_error(reason: 'encoding_error')
::Datadog.logger.warn(
Expand Down
8 changes: 0 additions & 8 deletions lib/datadog/tracing/distributed/fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ def initialize(data)
def [](key)
@data[key]
end

def id(key, base: 10)
Helpers.value_to_id(self[key], base: base)
end

def number(key, base: 10)
Helpers.value_to_number(self[key], base: base)
end
end
end
end
Expand Down
54 changes: 21 additions & 33 deletions lib/datadog/tracing/distributed/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,54 +20,42 @@ def self.clamp_sampling_priority(sampling_priority)
sampling_priority
end

def self.truncate_base16_number(value)
def self.parse_decimal_id(value)
return unless value

value = value.to_s

num = value.to_i

# Ensure the parsed number is the same as the original string value
# e.g. We want to make sure to throw away `'nan'.to_i == 0`
return unless num.to_s(10) == value

num
end

def self.parse_hex_id(value)
return unless value

# Lowercase if we want to parse base16 e.g. 3E8 => 3e8
# DEV: Ruby will parse `3E8` just fine, but to test
# `num.to_s(base) == value` we need to lowercase
value = value.downcase

# Truncate to trailing 16 characters if length is greater than 16
# https://github.com/apache/incubator-zipkin/blob/21fe362899fef5c593370466bc5707d3837070c2/zipkin/src/main/java/zipkin2/storage/StorageComponent.java#L49-L53
# DEV: This ensures we truncate B3 128-bit trace and span ids to 64-bit
value = value[value.length - 16, 16] if value.length > 16
value = value.to_s.downcase

# Remove any leading zeros
# DEV: When we call `num.to_s(16)` later Ruby will not add leading zeros
# for us so we want to make sure the comparision will work as expected
# DEV: regex, remove all leading zeros up until we find the last 0 in the string
# or we find the first non-zero, this allows `'0000' -> '0'` and `'00001' -> '1'`
value.sub(/^0*(?=(0$)|[^0])/, '')
end

def self.value_to_id(value, base: 10)
id = value_to_number(value, base: base)

# Return early if we could not parse a number
return if id.nil?

# Zero or greater than max allowed value of 2**64
return if id.zero? || id > Tracing::Utils::EXTERNAL_MAX_ID

id < 0 ? id + (2**64) : id
end

def self.value_to_number(value, base: 10)
# It's important to make a difference between no data and zero.
return if value.nil?

# Be sure we have a string
value = value.to_s

# If we are parsing base16 number then truncate to 64-bit
value = Helpers.truncate_base16_number(value) if base == 16
value = value.sub(/^0*(?=(0$)|[^0])/, '')

# Convert value to an integer
# DEV: Ruby `.to_i` will return `0` if a number could not be parsed
num = value.to_i(base)
num = value.to_i(16)

# Ensure the parsed number is the same as the original string value
# e.g. We want to make sure to throw away `'nan'.to_i == 0`
return unless num.to_s(base) == value
return unless num.to_s(16) == value

num
end
Expand Down
Loading