-
Notifications
You must be signed in to change notification settings - Fork 375
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* [core] Add support for injecting/extract B3 headers * reorganize distributed tracing header parsing * work on headers * finish up header parsing tests * fix linting issues * fix existing tests * refactor existing tests * fix linting * fix base16 trace id truncation and parsing * ensure we return a context * add more tests * move clamp_sampling_priority to helper * simplify logic for inject/extract * Rename to Datadog::DistributedTracing::Headers * fix rubocop issues * refactor helper a bit * add Datadog.configuration.distributed_tracing * move log into unless block * fix line too long
- Loading branch information
1 parent
847f101
commit b1eeb92
Showing
17 changed files
with
1,307 additions
and
467 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
require 'ddtrace/ext/distributed' | ||
require 'ddtrace/distributed_tracing/headers/headers' | ||
require 'ddtrace/distributed_tracing/headers/helpers' | ||
|
||
module Datadog | ||
module DistributedTracing | ||
module Headers | ||
# B3 provides helpers to inject or extract headers for B3 style headers | ||
module B3 | ||
include Ext::DistributedTracing | ||
|
||
def self.inject!(context, env) | ||
return if context.nil? | ||
|
||
# DEV: We need these to be hex encoded | ||
env[B3_HEADER_TRACE_ID] = context.trace_id.to_s(16) | ||
env[B3_HEADER_SPAN_ID] = context.span_id.to_s(16) | ||
|
||
unless context.sampling_priority.nil? | ||
sampling_priority = DistributedTracing::Headers::Helpers.clamp_sampling_priority(context.sampling_priority) | ||
env[B3_HEADER_SAMPLED] = sampling_priority.to_s | ||
end | ||
end | ||
|
||
def self.extract(env) | ||
# Extract values from headers | ||
# DEV: B3 doesn't have "origin" | ||
headers = Headers.new(env) | ||
trace_id = headers.id(B3_HEADER_TRACE_ID, 16) | ||
span_id = headers.id(B3_HEADER_SPAN_ID, 16) | ||
# We don't need to try and convert sampled since B3 supports 0/1 (AUTO_REJECT/AUTO_KEEP) | ||
sampling_priority = headers.number(B3_HEADER_SAMPLED) | ||
|
||
# Return early if this propagation is not valid | ||
return unless trace_id && span_id | ||
|
||
::Datadog::Context.new(trace_id: trace_id, | ||
span_id: span_id, | ||
sampling_priority: sampling_priority) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
require 'ddtrace/ext/distributed' | ||
require 'ddtrace/distributed_tracing/headers/headers' | ||
require 'ddtrace/distributed_tracing/headers/helpers' | ||
|
||
module Datadog | ||
module DistributedTracing | ||
module Headers | ||
# B3Single provides helpers to inject or extract headers for B3 single header style headers | ||
module B3Single | ||
include Ext::DistributedTracing | ||
|
||
def self.inject!(context, env) | ||
return if context.nil? | ||
|
||
# Header format: | ||
# b3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} | ||
# https://github.com/apache/incubator-zipkin-b3-propagation/tree/7c6e9f14d6627832bd80baa87ac7dabee7be23cf#single-header | ||
# DEV: `{SamplingState}` and `{ParentSpanId`}` are optional | ||
|
||
# DEV: We need these to be hex encoded | ||
header = "#{context.trace_id.to_s(16)}-#{context.span_id.to_s(16)}" | ||
|
||
unless context.sampling_priority.nil? | ||
sampling_priority = DistributedTracing::Headers::Helpers.clamp_sampling_priority(context.sampling_priority) | ||
header += "-#{sampling_priority}" | ||
end | ||
|
||
env[B3_HEADER_SINGLE] = header | ||
end | ||
|
||
def self.extract(env) | ||
# Header format: | ||
# b3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} | ||
# https://github.com/apache/incubator-zipkin-b3-propagation/tree/7c6e9f14d6627832bd80baa87ac7dabee7be23cf#single-header | ||
# DEV: `{SamplingState}` and `{ParentSpanId`}` are optional | ||
|
||
headers = Headers.new(env) | ||
value = headers.header(B3_HEADER_SINGLE) | ||
return if value.nil? | ||
|
||
parts = value.split('-') | ||
trace_id = headers.value_to_id(parts[0], 16) unless parts.empty? | ||
span_id = headers.value_to_id(parts[1], 16) if parts.length > 1 | ||
sampling_priority = headers.value_to_number(parts[2]) if parts.length > 2 | ||
|
||
# Return early if this propagation is not valid | ||
return unless trace_id && span_id | ||
|
||
::Datadog::Context.new(trace_id: trace_id, | ||
span_id: span_id, | ||
sampling_priority: sampling_priority) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
require 'ddtrace/ext/distributed' | ||
require 'ddtrace/distributed_tracing/headers/headers' | ||
|
||
module Datadog | ||
module DistributedTracing | ||
module Headers | ||
# Datadog provides helpers to inject or extract headers for Datadog style headers | ||
module Datadog | ||
include Ext::DistributedTracing | ||
|
||
def self.inject!(context, env) | ||
return if context.nil? | ||
|
||
env[HTTP_HEADER_TRACE_ID] = context.trace_id.to_s | ||
env[HTTP_HEADER_PARENT_ID] = context.span_id.to_s | ||
env[HTTP_HEADER_SAMPLING_PRIORITY] = context.sampling_priority.to_s unless context.sampling_priority.nil? | ||
env[HTTP_HEADER_ORIGIN] = context.origin.to_s unless context.origin.nil? | ||
end | ||
|
||
def self.extract(env) | ||
# Extract values from headers | ||
headers = Headers.new(env) | ||
trace_id = headers.id(HTTP_HEADER_TRACE_ID) | ||
parent_id = headers.id(HTTP_HEADER_PARENT_ID) | ||
origin = headers.header(HTTP_HEADER_ORIGIN) | ||
sampling_priority = headers.number(HTTP_HEADER_SAMPLING_PRIORITY) | ||
|
||
# Return early if this propagation is not valid | ||
# DEV: To be valid we need to have a trace id and a parent id or when it is a synthetics trace, just the trace id | ||
# DEV: `DistributedHeaders#id` will not return 0 | ||
return unless (trace_id && parent_id) || (origin == 'synthetics' && trace_id) | ||
|
||
# Return new context | ||
::Datadog::Context.new(trace_id: trace_id, | ||
span_id: parent_id, | ||
origin: origin, | ||
sampling_priority: sampling_priority) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
require 'ddtrace/configuration' | ||
require 'ddtrace/span' | ||
require 'ddtrace/ext/distributed' | ||
|
||
module Datadog | ||
module DistributedTracing | ||
module Headers | ||
# Headers provides easy access and validation methods for Rack headers | ||
class Headers | ||
include Ext::DistributedTracing | ||
|
||
def initialize(env) | ||
@env = env | ||
end | ||
|
||
def header(name) | ||
rack_header = "http-#{name}".upcase!.tr('-', '_') | ||
|
||
hdr = @env[rack_header] | ||
|
||
# Only return the value if it is not an empty string | ||
hdr if hdr != '' | ||
end | ||
|
||
def id(hdr, base = 10) | ||
value_to_id(header(hdr), base) | ||
end | ||
|
||
def value_to_id(value, base = 10) | ||
id = value_to_number(value, 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 > Span::EXTERNAL_MAX_ID | ||
id < 0 ? id + (2**64) : id | ||
end | ||
|
||
def number(hdr, base = 10) | ||
value_to_number(header(hdr), base) | ||
end | ||
|
||
def value_to_number(value, base = 10) | ||
# It's important to make a difference between no header, | ||
# and a header defined to 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 = DistributedTracing::Headers::Helpers.truncate_base16_number(value) if base == 16 | ||
|
||
# Convert header to an integer | ||
# DEV: Ruby `.to_i` will return `0` if a number could not be parsed | ||
num = value.to_i(base) | ||
|
||
# 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 | ||
|
||
num | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
require 'ddtrace/configuration' | ||
require 'ddtrace/span' | ||
require 'ddtrace/ext/priority' | ||
|
||
module Datadog | ||
module DistributedTracing | ||
module Headers | ||
# Helpers module provides common helper functions for distributed tracing headers | ||
module Helpers | ||
# Base provides common methods for distributed header helper classes | ||
def self.clamp_sampling_priority(sampling_priority) | ||
# B3 doesn't have our -1 (USER_REJECT) and 2 (USER_KEEP) priorities so convert to acceptable 0/1 | ||
if sampling_priority < 0 | ||
sampling_priority = Ext::Priority::AUTO_REJECT | ||
elsif sampling_priority > 1 | ||
sampling_priority = Ext::Priority::AUTO_KEEP | ||
end | ||
|
||
sampling_priority | ||
end | ||
|
||
def self.truncate_base16_number(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 | ||
|
||
# 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 = value.sub(/^0*(?=(0$)|[^0])/, '') | ||
|
||
value | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.