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

Support W3C headers #74

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
11 changes: 7 additions & 4 deletions lib/lightstep/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ def initialize(
ref = ref.context if (Span === ref)

if SpanContext === ref
@context = SpanContext.new(id: LightStep.guid, trace_id: ref.trace_id)
@context = SpanContext.new(id: LightStep.guid, trace_id: ref.trace_id, trace_state: ref.trace_state)
set_baggage(ref.baggage)

set_tag(:parent_span_guid, ref.id)
else
@context = SpanContext.new(id: LightStep.guid, trace_id: LightStep.guid)
Expand All @@ -83,7 +84,8 @@ def set_baggage_item(key, value)
@context = SpanContext.new(
id: context.id,
trace_id: context.trace_id,
baggage: context.baggage.merge({key => value})
baggage: context.baggage.merge({key => value}),
trace_state: context.trace_state
)
self
end
Expand All @@ -94,7 +96,8 @@ def set_baggage(baggage = {})
@context = SpanContext.new(
id: context.id,
trace_id: context.trace_id,
baggage: baggage
baggage: baggage,
trace_state: context.trace_state
)
end

Expand Down Expand Up @@ -155,7 +158,7 @@ def to_h
{
runtime_guid: tracer.guid,
span_guid: context.id,
trace_guid: context.trace_id,
trace_guid: context.trace_id[-16..-1] || context.trace_id, # Hack to ensure the reported ID is <= 16 bytes
span_name: operation_name,
attributes: tags.map {|key, value|
{Key: key.to_s, Value: value}
Expand Down
5 changes: 3 additions & 2 deletions lib/lightstep/span_context.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module LightStep
# SpanContext holds the data for a span that gets inherited to child spans
class SpanContext
attr_reader :id, :trace_id, :baggage
attr_reader :id, :trace_id, :baggage, :trace_state

def initialize(id:, trace_id:, baggage: {})
def initialize(id:, trace_id:, baggage: {}, trace_state: [])
@id = id.freeze
@trace_id = trace_id.freeze
@baggage = baggage.freeze
@trace_state = trace_state.freeze
end
end
end
81 changes: 75 additions & 6 deletions lib/lightstep/tracer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'json'
require 'base64'
require 'concurrent'

require 'opentracing'
Expand Down Expand Up @@ -262,6 +263,14 @@ def configure(component_name:, access_token: nil, transport: nil, tags: {})
DEFAULT_MAX_SPAN_RECORDS = 1000
MIN_MAX_SPAN_RECORDS = 1

TRACE_PARENT = 'traceparent'.freeze
TRACE_PARENT_VERSION = 0
TRACE_PARENT_REGEX = /\h{2}-(\h{32})-(\h{16})-\h{2}/

TRACE_STATE = 'tracestate'.freeze
TRACE_STATE_VENDOR = 'lightstep'.freeze
MAX_TRACE_STATE_SIZE = 512

def inject_to_text_map(span_context, carrier)
carrier[CARRIER_SPAN_ID] = span_context.id
carrier[CARRIER_TRACE_ID] = span_context.trace_id unless span_context.trace_id.nil?
Expand All @@ -273,9 +282,20 @@ def inject_to_text_map(span_context, carrier)
end

def extract_from_text_map(carrier)
trace_id = carrier[CARRIER_TRACE_ID]
id = carrier[CARRIER_SPAN_ID]

if trace_id.nil? || trace_id.empty? || id.nil? || id.empty?
matches = TRACE_PARENT_REGEX.match(carrier[TRACE_PARENT])
unless matches.nil?
trace_id = matches[1]
id = matches[2]
end
end

# If the carrier does not have both the span_id and trace_id key
# skip the processing and just return a normal span
if !carrier.has_key?(CARRIER_SPAN_ID) || !carrier.has_key?(CARRIER_TRACE_ID)
if trace_id.nil? || trace_id.empty? || id.nil? || id.empty?
return nil
end

Expand All @@ -287,35 +307,84 @@ def extract_from_text_map(carrier)
end
baggage
end

trace_state = (carrier[TRACE_STATE] || '').split(',')
.map { |item| item.match(/^(.*)=(.*)$/).captures }
.find_all { |vendor, state| !vendor.nil? && !vendor.empty? && !state.nil? }
.reduce([]) do |memo, (vendor, value)|
if vendor == TRACE_STATE_VENDOR
baggage = baggage.merge(decode_tracestate_baggage(value))
else
memo << "#{vendor}=#{value}"
end

memo
end

SpanContext.new(
id: carrier[CARRIER_SPAN_ID],
trace_id: carrier[CARRIER_TRACE_ID],
id: id,
trace_id: trace_id,
baggage: baggage,
trace_state: trace_state
)
end

def inject_to_rack(span_context, carrier)
carrier[CARRIER_SPAN_ID] = span_context.id
carrier[CARRIER_TRACE_ID] = span_context.trace_id unless span_context.trace_id.nil?
carrier[CARRIER_SAMPLED] = 'true'
carrier[TRACE_PARENT] = format('%<version>02x-%<trace_id>s-%<span_id>s-01', {
version: TRACE_PARENT_VERSION,
trace_id: span_context.trace_id.rjust(32, '0'),
span_id: span_context.id.rjust(16, '0')
})

span_context.baggage.each do |key, value|
span_context.baggage.sort.each do |key, value|
if key =~ /[^A-Za-z0-9\-_]/
# TODO: log the error internally
next
end

carrier[CARRIER_BAGGAGE_PREFIX + key] = value
end

encoded_baggage = ''

unless span_context.baggage.empty?
encoded_baggage = Base64.urlsafe_encode64(JSON.generate(span_context.baggage), padding: false)
end

trace_state = "#{TRACE_STATE_VENDOR}=#{encoded_baggage}"

for item in span_context.trace_state
if trace_state.length + item.length + 1 > MAX_TRACE_STATE_SIZE
break
end

trace_state << ',' << item
end

carrier[TRACE_STATE] = trace_state
end

def extract_from_rack(env)
extract_from_text_map(env.reduce({}){|memo, tuple|
raw_header, value = tuple
header = raw_header.gsub(/^HTTP_/, '').tr('_', '-').downcase
header = raw_header.to_s.gsub(/^HTTP_/, '').tr('_', '-').downcase

memo[header] = value if header.start_with?(CARRIER_TRACER_STATE_PREFIX, CARRIER_BAGGAGE_PREFIX)
memo[header] = value if header.start_with?(CARRIER_TRACER_STATE_PREFIX, CARRIER_BAGGAGE_PREFIX) || header == TRACE_PARENT || header == TRACE_STATE
memo
})
end

def decode_tracestate_baggage(value)
begin
decoded_baggage = Base64.urlsafe_decode64(value || '')
rescue ArgumentError
decoded_baggage = nil
end

JSON.parse(decoded_baggage)
end
end
end
Loading