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

Add W3C traceparent headers to distributed tracing #2310

Draft
wants to merge 1 commit 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
w3c traceparent
  • Loading branch information
sl0thentr0py committed May 13, 2024
commit 9974c9f6ddf6f344cbff532a123825665dd694f6
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ Gemfile.lock
.ruby-gemset
.idea
*.rdb
.yardoc/

examples/**/node_modules
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
config.enabled_patches += [:graphql]
end
```
- Add [W3C traceparent header](https://www.w3.org/TR/trace-context/#traceparent-header) support ([#2310](https://github.com/getsentry/sentry-ruby/pull/2310))

The SDK now also propagates and accepts incoming W3C `traceparent` headers along with the currently implemented `sentry-trace` and `baggage` headers.

### Bug Fixes

Expand Down
13 changes: 12 additions & 1 deletion sentry-ruby/lib/sentry-ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ module Sentry
LOGGER_PROGNAME = "sentry".freeze

SENTRY_TRACE_HEADER_NAME = "sentry-trace".freeze

W3C_TRACEPARENT_HEADER_NAME = "traceparent".freeze
BAGGAGE_HEADER_NAME = "baggage".freeze

THREAD_LOCAL = :sentry_hub
Expand Down Expand Up @@ -533,6 +533,17 @@ def get_traceparent
get_current_hub.get_traceparent
end

# Returns the W3C traceparent header for distributed tracing.
# Can be either from the currently active span or the propagation context.
#
# @see https://www.w3.org/TR/trace-context/#traceparent-header W3C Traceparent specification
#
# @return [String, nil]
def get_w3c_traceparent
return nil unless initialized?
get_current_hub.get_w3c_traceparent
end

# Returns the baggage header for distributed tracing.
# Can be either from the currently active span or the propagation context.
#
Expand Down
10 changes: 10 additions & 0 deletions sentry-ruby/lib/sentry/hub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,13 @@ def get_traceparent
current_scope.propagation_context.get_traceparent
end

def get_w3c_traceparent
return nil unless current_scope

current_scope.get_span&.get_w3c_traceparent ||
current_scope.propagation_context.get_w3c_traceparent
end

def get_baggage
return nil unless current_scope

Expand All @@ -278,6 +285,9 @@ def get_trace_propagation_headers
traceparent = get_traceparent
headers[SENTRY_TRACE_HEADER_NAME] = traceparent if traceparent

w3c_traceparent = get_w3c_traceparent
headers[W3C_TRACEPARENT_HEADER_NAME] = w3c_traceparent if w3c_traceparent

baggage = get_baggage
headers[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?

Expand Down
63 changes: 52 additions & 11 deletions sentry-ruby/lib/sentry/propagation_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
"[ \t]*$" # whitespace
)

W3C_TRACEPARENT_REGEX = Regexp.new(
"^[ \t]*" + # whitespace
"([0-9a-f]{2})?" + # version
"-?([0-9a-f]{32})?" + # trace_id
"-?([0-9a-f]{16})?" + # parent_span_id
"-?([0-9a-f]{2})?" + # trace_flags
"[ \t]*$" # whitespace
)

W3C_TRACEPARENT_VERSION = '00'

# An uuid that can be used to identify a trace.
# @return [String]
attr_reader :trace_id
Expand Down Expand Up @@ -42,28 +53,38 @@

if env
sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
w3c_traceparent_header = env["HTTP_TRACEPARENT"] || env[W3C_TRACEPARENT_HEADER_NAME]
baggage_header = env["HTTP_BAGGAGE"] || env[BAGGAGE_HEADER_NAME]

if sentry_trace_header
sentry_trace_data = self.class.extract_sentry_trace(sentry_trace_header)

if sentry_trace_data
@trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
@incoming_trace = true
end
elsif w3c_traceparent_header
traceparent_data = self.class.extract_w3c_traceparent(w3c_traceparent_header)

Check warning on line 67 in sentry-ruby/lib/sentry/propagation_context.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/propagation_context.rb#L67

Added line #L67 was not covered by tests

@baggage =
if baggage_header && !baggage_header.empty?
Baggage.from_incoming_header(baggage_header)
else
# If there's an incoming sentry-trace but no incoming baggage header,
# for instance in traces coming from older SDKs,
# baggage will be empty and frozen and won't be populated as head SDK.
Baggage.new({})
end

@baggage.freeze!
if traceparent_data
@trace_id, @parent_span_id, @parent_sampled = traceparent_data

Check warning on line 70 in sentry-ruby/lib/sentry/propagation_context.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/propagation_context.rb#L69-L70

Added lines #L69 - L70 were not covered by tests
@incoming_trace = true
end
end

if @incoming_trace
@baggage =
if baggage_header && !baggage_header.empty?
Baggage.from_incoming_header(baggage_header)
else
# If there's an incoming sentry-trace but no incoming baggage header,
# for instance in traces coming from older SDKs,
# baggage will be empty and frozen and won't be populated as head SDK.
Baggage.new({})
end

@baggage.freeze!
end
end

@trace_id ||= SecureRandom.uuid.delete("-")
Expand All @@ -84,6 +105,20 @@
[trace_id, parent_span_id, parent_sampled]
end

# Extract the trace_id, parent_span_id and parent_sampled values from a W3C traceparent header.
#
# @param traceparent [String] the traceparent header value from the previous transaction.
# @return [Array, nil]
def self.extract_w3c_traceparent(traceparent)
match = W3C_TRACEPARENT_REGEX.match(traceparent)
Dismissed Show dismissed Hide dismissed
return nil if match.nil?

Check warning on line 114 in sentry-ruby/lib/sentry/propagation_context.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/propagation_context.rb#L113-L114

Added lines #L113 - L114 were not covered by tests

trace_id, parent_span_id, trace_flags = match[2..4]
parent_sampled = (trace_flags.hex & 0x01) == 0x01

Check warning on line 117 in sentry-ruby/lib/sentry/propagation_context.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/propagation_context.rb#L116-L117

Added lines #L116 - L117 were not covered by tests

[version, trace_id, parent_span_id, parent_sampled]

Check warning on line 119 in sentry-ruby/lib/sentry/propagation_context.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/propagation_context.rb#L119

Added line #L119 was not covered by tests
end

# Returns the trace context that can be used to embed in an Event.
# @return [Hash]
def get_trace_context
Expand All @@ -100,6 +135,12 @@
"#{trace_id}-#{span_id}"
end

# Returns the w3c traceparent header from the propagation context.
# @return [String]
def get_w3c_traceparent
"#{W3C_TRACEPARENT_VERSION}-#{trace_id}-#{span_id}-00"
end

# Returns the Baggage from the propagation context or populates as head SDK if empty.
# @return [Baggage, nil]
def get_baggage
Expand Down
8 changes: 8 additions & 0 deletions sentry-ruby/lib/sentry/span.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "securerandom"
require "sentry/propagation_context"
require "sentry/metrics/local_aggregator"

module Sentry
Expand Down Expand Up @@ -141,6 +142,13 @@ def to_sentry_trace
"#{@trace_id}-#{@span_id}-#{sampled_flag}"
end

# Generates a w3c traceparent header that can be used to connect other transactions.
# @return [String]
def get_w3c_traceparent
trace_flags = @sampled ? '01' : '00'
"#{PropagationContext::W3C_TRACEPARENT_VERSION}-#{@trace_id}-#{@span_id}-#{trace_flags}"
end

# Generates a W3C Baggage header string for distributed tracing
# from the incoming baggage stored on the transaction.
# @return [String, nil]
Expand Down
3 changes: 2 additions & 1 deletion sentry-ruby/spec/sentry_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,8 @@
it "returns a Hash of sentry-trace and baggage" do
expect(described_class.get_trace_propagation_headers).to eq({
"sentry-trace" => described_class.get_traceparent,
"baggage" => described_class.get_baggage
"baggage" => described_class.get_baggage,
"traceparent" => described_class.get_w3c_traceparent
})
end
end
Expand Down