jaeger-client 0.7.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +3 -0
  3. data/.rubocop.yml +3 -0
  4. data/.travis.yml +11 -2
  5. data/Makefile +1 -0
  6. data/README.md +127 -13
  7. data/crossdock/Dockerfile +29 -0
  8. data/crossdock/Gemfile +6 -0
  9. data/crossdock/Gemfile.lock +35 -0
  10. data/crossdock/docker-compose.yml +68 -0
  11. data/crossdock/jaeger-docker-compose.yml +48 -0
  12. data/crossdock/rules.mk +35 -0
  13. data/crossdock/server +173 -0
  14. data/jaeger-client.gemspec +4 -2
  15. data/lib/jaeger/client.rb +42 -18
  16. data/lib/jaeger/client/version.rb +1 -1
  17. data/lib/jaeger/encoders/thrift_encoder.rb +142 -0
  18. data/lib/jaeger/extractors.rb +173 -0
  19. data/lib/jaeger/http_sender.rb +28 -0
  20. data/lib/jaeger/injectors.rb +83 -0
  21. data/lib/jaeger/rate_limiter.rb +61 -0
  22. data/lib/jaeger/recurring_executor.rb +35 -0
  23. data/lib/jaeger/reporters.rb +7 -0
  24. data/lib/jaeger/reporters/composite_reporter.rb +17 -0
  25. data/lib/jaeger/reporters/in_memory_reporter.rb +30 -0
  26. data/lib/jaeger/reporters/logging_reporter.rb +22 -0
  27. data/lib/jaeger/reporters/null_reporter.rb +11 -0
  28. data/lib/jaeger/{client/async_reporter.rb → reporters/remote_reporter.rb} +21 -20
  29. data/lib/jaeger/{client/async_reporter → reporters/remote_reporter}/buffer.rb +2 -2
  30. data/lib/jaeger/samplers.rb +8 -0
  31. data/lib/jaeger/samplers/const.rb +24 -0
  32. data/lib/jaeger/samplers/guaranteed_throughput_probabilistic.rb +47 -0
  33. data/lib/jaeger/samplers/per_operation.rb +79 -0
  34. data/lib/jaeger/samplers/probabilistic.rb +38 -0
  35. data/lib/jaeger/samplers/rate_limiting.rb +51 -0
  36. data/lib/jaeger/samplers/remote_controlled.rb +119 -0
  37. data/lib/jaeger/samplers/remote_controlled/instructions_fetcher.rb +34 -0
  38. data/lib/jaeger/scope.rb +38 -0
  39. data/lib/jaeger/scope_manager.rb +47 -0
  40. data/lib/jaeger/scope_manager/scope_identifier.rb +13 -0
  41. data/lib/jaeger/scope_manager/scope_stack.rb +33 -0
  42. data/lib/jaeger/span.rb +97 -0
  43. data/lib/jaeger/span/thrift_log_builder.rb +18 -0
  44. data/lib/jaeger/span_context.rb +57 -0
  45. data/lib/jaeger/thrift_tag_builder.rb +41 -0
  46. data/lib/jaeger/trace_id.rb +48 -0
  47. data/lib/jaeger/tracer.rb +213 -0
  48. data/lib/jaeger/udp_sender.rb +26 -0
  49. data/lib/jaeger/udp_sender/transport.rb +40 -0
  50. metadata +78 -31
  51. data/lib/jaeger/client/carrier.rb +0 -26
  52. data/lib/jaeger/client/encoders/thrift_encoder.rb +0 -94
  53. data/lib/jaeger/client/extractors.rb +0 -88
  54. data/lib/jaeger/client/http_sender.rb +0 -30
  55. data/lib/jaeger/client/injectors.rb +0 -54
  56. data/lib/jaeger/client/samplers.rb +0 -4
  57. data/lib/jaeger/client/samplers/const.rb +0 -29
  58. data/lib/jaeger/client/samplers/probabilistic.rb +0 -30
  59. data/lib/jaeger/client/scope.rb +0 -40
  60. data/lib/jaeger/client/scope_manager.rb +0 -49
  61. data/lib/jaeger/client/scope_manager/scope_identifier.rb +0 -15
  62. data/lib/jaeger/client/scope_manager/scope_stack.rb +0 -35
  63. data/lib/jaeger/client/span.rb +0 -84
  64. data/lib/jaeger/client/span/thrift_log_builder.rb +0 -20
  65. data/lib/jaeger/client/span/thrift_tag_builder.rb +0 -45
  66. data/lib/jaeger/client/span_context.rb +0 -59
  67. data/lib/jaeger/client/trace_id.rb +0 -41
  68. data/lib/jaeger/client/tracer.rb +0 -189
  69. data/lib/jaeger/client/udp_sender.rb +0 -27
  70. data/lib/jaeger/client/udp_sender/transport.rb +0 -42
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stdout.sync = true
4
+
5
+ require 'sinatra/base'
6
+ require 'webrick'
7
+ require 'jaeger/client'
8
+ require 'net/http'
9
+ require 'uri'
10
+
11
+ class HealthServer < Sinatra::Application
12
+ get '/' do
13
+ status 200
14
+ end
15
+ end
16
+
17
+ class HttpServer < Sinatra::Application
18
+ post '/start_trace' do
19
+ puts "Got request to start trace: #{trace_request}"
20
+
21
+ parent_context = tracer.extract(OpenTracing::FORMAT_RACK, request.env)
22
+ server_span = tracer.start_span('/start_trace', child_of: parent_context)
23
+
24
+ server_span.set_baggage_item('crossdock-baggage-key', trace_request['baggage'])
25
+ if trace_request.key?('sampled')
26
+ server_span.set_tag('sampling.priority', trace_request['sampled'] ? 1 : 0)
27
+ end
28
+
29
+ response = {
30
+ span: observe_span(server_span),
31
+ notImplementedError: ''
32
+ }
33
+
34
+ if trace_request['downstream']
35
+ downstream = trace_request['downstream']
36
+ transport = downstream['transport']
37
+
38
+ response[:downstream] =
39
+ if transport == 'HTTP'
40
+ call_downstream_http(downstream, server_span)
41
+ elsif transport == 'DUMMY'
42
+ { notImplementedError: 'Dummy has not been implemented' }
43
+ else
44
+ { notImplementedError: "Unrecognized transport received: #{transport}" }
45
+ end
46
+ end
47
+
48
+ puts "Response: #{response}"
49
+
50
+ server_span.finish
51
+ body JSON.dump(response)
52
+ end
53
+
54
+ post '/join_trace' do
55
+ puts 'Got request to join trace' \
56
+ "\n Params: #{trace_request}" \
57
+ "\n Headers: #{request_headers(request)}"
58
+
59
+ parent_context = tracer.extract(OpenTracing::FORMAT_RACK, request.env)
60
+ server_span = tracer.start_span('/join_trace', child_of: parent_context)
61
+
62
+ response = {
63
+ span: observe_span(server_span),
64
+ notImplementedError: ''
65
+ }
66
+
67
+ if trace_request['downstream']
68
+ downstream = trace_request['downstream']
69
+ transport = downstream['transport']
70
+
71
+ response[:downstream] =
72
+ if transport == 'HTTP'
73
+ call_downstream_http(downstream, server_span)
74
+ elsif transport == 'DUMMY'
75
+ { notImplementedError: 'Dummy has not been implemented' }
76
+ else
77
+ { notImplementedError: "Unrecognized transport received: #{transport}" }
78
+ end
79
+ end
80
+
81
+ puts "Response: #{response}"
82
+
83
+ server_span.finish
84
+ body JSON.dump(response)
85
+ end
86
+
87
+ post '/create_traces' do
88
+ puts "Got request to create traces: #{trace_request}"
89
+
90
+ trace_request['count'].times do
91
+ span = tracer.start_span(trace_request['operation'], tags: trace_request['tags'])
92
+ span.finish
93
+ end
94
+
95
+ status 200
96
+ end
97
+
98
+ private
99
+
100
+ def tracer
101
+ @tracer ||= Jaeger::Client.build(
102
+ service_name: 'crossdock-ruby',
103
+ host: 'jaeger-agent',
104
+ port: 6831,
105
+ flush_interval: 1,
106
+ sampler: Jaeger::Samplers::Const.new(true)
107
+ )
108
+ end
109
+
110
+ def trace_request
111
+ @trace_request ||= begin
112
+ request.body.rewind
113
+ JSON.parse(request.body.read)
114
+ end
115
+ end
116
+
117
+ def observe_span(span)
118
+ if span
119
+ {
120
+ traceId: span.context.to_trace_id,
121
+ sampled: span.context.sampled?,
122
+ baggage: span.get_baggage_item('crossdock-baggage-key')
123
+ }
124
+ else
125
+ {
126
+ traceId: 'no span found',
127
+ sampled: false,
128
+ baggage: 'no span found'
129
+ }
130
+ end
131
+ end
132
+
133
+ def call_downstream_http(downstream, server_span)
134
+ downstream_url = "http://#{downstream['host']}:#{downstream['port']}/join_trace"
135
+
136
+ client_span = tracer.start_span('client-span', child_of: server_span)
137
+
138
+ headers = { 'Content-Type' => 'application/json' }
139
+ tracer.inject(client_span.context, OpenTracing::FORMAT_RACK, headers)
140
+
141
+ response = Net::HTTP.post(
142
+ URI(downstream_url),
143
+ JSON.dump(
144
+ serverRole: downstream['serverRole'],
145
+ downstream: downstream['downstream']
146
+ ),
147
+ headers
148
+ )
149
+
150
+ client_span.finish
151
+
152
+ if response.is_a?(Net::HTTPSuccess)
153
+ JSON.parse(response.body)
154
+ else
155
+ { error: response.body }
156
+ end
157
+ end
158
+
159
+ def request_headers(request)
160
+ request.env.select do |key, _value|
161
+ key.start_with?('HTTP_')
162
+ end
163
+ end
164
+ end
165
+
166
+ threads = []
167
+ threads << Thread.new do
168
+ Rack::Handler::WEBrick.run(HealthServer, Port: 8080, Host: '0.0.0.0')
169
+ end
170
+ threads << Thread.new do
171
+ Rack::Handler::WEBrick.run(HttpServer, Port: 8081, Host: '0.0.0.0')
172
+ end
173
+ threads.each(&:join)
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = 'OpenTracing Tracer implementation for Jaeger in Ruby'
13
13
  spec.description = ''
14
- spec.homepage = ''
14
+ spec.homepage = 'https://github.com/salemove/jaeger-client-ruby'
15
15
  spec.license = 'MIT'
16
16
 
17
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -20,11 +20,13 @@ Gem::Specification.new do |spec|
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
  spec.require_paths = ['lib']
22
22
 
23
- spec.add_development_dependency 'bundler', '~> 1.14'
23
+ spec.add_development_dependency 'bundler'
24
24
  spec.add_development_dependency 'rake', '~> 10.0'
25
25
  spec.add_development_dependency 'rspec', '~> 3.0'
26
26
  spec.add_development_dependency 'rubocop', '~> 0.54.0'
27
27
  spec.add_development_dependency 'rubocop-rspec', '~> 1.24.0'
28
+ spec.add_development_dependency 'timecop', '~> 0.9'
29
+ spec.add_development_dependency 'webmock', '~> 3.4.2'
28
30
 
29
31
  spec.add_dependency 'opentracing', '~> 0.3'
30
32
  spec.add_dependency 'thrift'
@@ -5,24 +5,42 @@ $LOAD_PATH.push(File.dirname(__FILE__) + '/../../thrift/gen-rb')
5
5
  require 'opentracing'
6
6
  require 'jaeger/thrift/agent'
7
7
  require 'logger'
8
+ require 'time'
9
+ require 'net/http'
10
+ require 'cgi'
11
+ require 'json'
8
12
 
9
- require_relative 'client/tracer'
10
- require_relative 'client/span'
11
- require_relative 'client/span_context'
12
- require_relative 'client/scope'
13
- require_relative 'client/scope_manager'
14
- require_relative 'client/carrier'
15
- require_relative 'client/trace_id'
16
- require_relative 'client/udp_sender'
17
- require_relative 'client/async_reporter'
13
+ require_relative 'tracer'
14
+ require_relative 'span'
15
+ require_relative 'span_context'
16
+ require_relative 'scope'
17
+ require_relative 'scope_manager'
18
+ require_relative 'trace_id'
19
+ require_relative 'udp_sender'
20
+ require_relative 'http_sender'
21
+ require_relative 'reporters'
18
22
  require_relative 'client/version'
19
- require_relative 'client/samplers'
20
- require_relative 'client/encoders/thrift_encoder'
21
- require_relative 'client/injectors'
22
- require_relative 'client/extractors'
23
+ require_relative 'samplers'
24
+ require_relative 'encoders/thrift_encoder'
25
+ require_relative 'injectors'
26
+ require_relative 'extractors'
27
+ require_relative 'rate_limiter'
28
+ require_relative 'thrift_tag_builder'
29
+ require_relative 'recurring_executor'
23
30
 
24
31
  module Jaeger
25
32
  module Client
33
+ # We initially had everything under Jaeger::Client namespace. This however
34
+ # was not very useful and was removed. These assignments are here for
35
+ # backwards compatibility. Fine to remove in the next major version.
36
+ UdpSender = Jaeger::UdpSender
37
+ HttpSender = Jaeger::HttpSender
38
+ Encoders = Jaeger::Encoders
39
+ Samplers = Jaeger::Samplers
40
+ Reporters = Jaeger::Reporters
41
+ Injectors = Jaeger::Injectors
42
+ Extractors = Jaeger::Extractors
43
+
26
44
  DEFAULT_FLUSH_INTERVAL = 10
27
45
 
28
46
  def self.build(host: '127.0.0.1',
@@ -32,15 +50,21 @@ module Jaeger
32
50
  sampler: Samplers::Const.new(true),
33
51
  logger: Logger.new(STDOUT),
34
52
  sender: nil,
53
+ reporter: nil,
35
54
  injectors: {},
36
- extractors: {})
37
- encoder = Encoders::ThriftEncoder.new(service_name: service_name)
55
+ extractors: {},
56
+ tags: {})
57
+ encoder = Encoders::ThriftEncoder.new(service_name: service_name, tags: tags)
38
58
 
39
- if sender.nil?
40
- sender = UdpSender.new(host: host, port: port, encoder: encoder, logger: logger)
59
+ if sender
60
+ warn '[DEPRECATION] Passing `sender` directly to Jaeger::Client.build is deprecated.' \
61
+ 'Please use `reporter` instead.'
41
62
  end
42
63
 
43
- reporter = AsyncReporter.create(sender: sender, flush_interval: flush_interval)
64
+ reporter ||= Reporters::RemoteReporter.new(
65
+ sender: sender || UdpSender.new(host: host, port: port, encoder: encoder, logger: logger),
66
+ flush_interval: flush_interval
67
+ )
44
68
 
45
69
  Tracer.new(
46
70
  reporter: reporter,
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jaeger
4
4
  module Client
5
- VERSION = '0.7.1'.freeze
5
+ VERSION = '1.1.0'.freeze
6
6
  end
7
7
  end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Encoders
5
+ class ThriftEncoder
6
+ def initialize(service_name:, tags: {})
7
+ @service_name = service_name
8
+ @tags = prepare_tags(tags)
9
+ @process = Jaeger::Thrift::Process.new('serviceName' => @service_name, 'tags' => @tags)
10
+ end
11
+
12
+ def encode(spans)
13
+ encode_batch(spans.map(&method(:encode_span)))
14
+ end
15
+
16
+ def encode_limited_size(spans, protocol_class, max_message_length)
17
+ batches = []
18
+ current_batch = []
19
+ transport.flush
20
+ spans.each do |span|
21
+ encoded_span = encode_span(span)
22
+ if aggregated_span_size(encoded_span, protocol_class) > max_message_length && !current_batch.empty?
23
+ batches << encode_batch(current_batch)
24
+ current_batch = []
25
+ transport.flush
26
+ end
27
+ current_batch << encoded_span
28
+ end
29
+ batches << encode_batch(current_batch) unless current_batch.empty?
30
+ batches
31
+ end
32
+
33
+ private
34
+
35
+ def encode_batch(encoded_spans)
36
+ Jaeger::Thrift::Batch.new('process' => @process, 'spans' => encoded_spans)
37
+ end
38
+
39
+ def encode_span(span)
40
+ context = span.context
41
+ start_ts, duration = build_timestamps(span)
42
+
43
+ Jaeger::Thrift::Span.new(
44
+ 'traceIdLow' => TraceId.uint64_id_to_int64(trace_id_to_low(context.trace_id)),
45
+ 'traceIdHigh' => TraceId.uint64_id_to_int64(trace_id_to_high(context.trace_id)),
46
+ 'spanId' => TraceId.uint64_id_to_int64(context.span_id),
47
+ 'parentSpanId' => TraceId.uint64_id_to_int64(context.parent_id),
48
+ 'operationName' => span.operation_name,
49
+ 'references' => build_references(span.references || []),
50
+ 'flags' => context.flags,
51
+ 'startTime' => start_ts,
52
+ 'duration' => duration,
53
+ 'tags' => span.tags,
54
+ 'logs' => span.logs
55
+ )
56
+ end
57
+
58
+ def build_references(references)
59
+ references.map do |ref|
60
+ Jaeger::Thrift::SpanRef.new(
61
+ 'refType' => span_ref_type(ref.type),
62
+ 'traceIdLow' => TraceId.uint64_id_to_int64(ref.context.trace_id),
63
+ 'traceIdHigh' => 0,
64
+ 'spanId' => TraceId.uint64_id_to_int64(ref.context.span_id)
65
+ )
66
+ end
67
+ end
68
+
69
+ def build_timestamps(span)
70
+ start_ts = (span.start_time.to_f * 1_000_000).to_i
71
+ end_ts = (span.end_time.to_f * 1_000_000).to_i
72
+ duration = end_ts - start_ts
73
+ [start_ts, duration]
74
+ end
75
+
76
+ def span_ref_type(type)
77
+ case type
78
+ when OpenTracing::Reference::CHILD_OF
79
+ Jaeger::Thrift::SpanRefType::CHILD_OF
80
+ when OpenTracing::Reference::FOLLOWS_FROM
81
+ Jaeger::Thrift::SpanRefType::FOLLOWS_FROM
82
+ else
83
+ warn "Jaeger::Client with format #{type} is not supported yet"
84
+ nil
85
+ end
86
+ end
87
+
88
+ def prepare_tags(tags)
89
+ with_default_tags = tags.dup
90
+ with_default_tags['jaeger.version'] = 'Ruby-' + Jaeger::Client::VERSION
91
+ with_default_tags['hostname'] ||= Socket.gethostname
92
+
93
+ unless with_default_tags['ip']
94
+ ipv4 = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }
95
+ with_default_tags['ip'] = ipv4.ip_address unless ipv4.nil?
96
+ end
97
+
98
+ with_default_tags.map do |key, value|
99
+ ThriftTagBuilder.build(key, value)
100
+ end
101
+ end
102
+
103
+ # Returns the right most 64 bits of trace id
104
+ def trace_id_to_low(trace_id)
105
+ trace_id & TraceId::MAX_64BIT_UNSIGNED_INT
106
+ end
107
+
108
+ # Returns the left most 64 bits of trace id
109
+ def trace_id_to_high(trace_id)
110
+ (trace_id >> 64) & TraceId::MAX_64BIT_UNSIGNED_INT
111
+ end
112
+
113
+ class DummyTransport
114
+ attr_accessor :size
115
+
116
+ def initialize
117
+ @size = 0
118
+ end
119
+
120
+ def write(buf)
121
+ @size += buf.size
122
+ end
123
+
124
+ def flush
125
+ @size = 0
126
+ end
127
+
128
+ def close; end
129
+ end
130
+
131
+ def aggregated_span_size(span, protocol_class)
132
+ @protocol ||= protocol_class.new(transport)
133
+ span.write(@protocol)
134
+ transport.size
135
+ end
136
+
137
+ def transport
138
+ @transport ||= DummyTransport.new
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Extractors
5
+ class SerializedJaegerTrace
6
+ def self.parse(trace)
7
+ return nil if !trace || trace == ''
8
+
9
+ trace_arguments = trace.split(':')
10
+ return nil if trace_arguments.size != 4
11
+
12
+ trace_id = TraceId.base16_hex_id_to_uint128(trace_arguments[0])
13
+ span_id, parent_id, flags = trace_arguments[1..3].map(&TraceId.method(:base16_hex_id_to_uint64))
14
+
15
+ return nil if trace_id.zero? || span_id.zero?
16
+
17
+ SpanContext.new(
18
+ trace_id: trace_id,
19
+ parent_id: parent_id,
20
+ span_id: span_id,
21
+ flags: flags
22
+ )
23
+ end
24
+ end
25
+
26
+ class JaegerTextMapCodec
27
+ def self.extract(carrier)
28
+ context = SerializedJaegerTrace.parse(carrier['uber-trace-id'])
29
+ return nil unless context
30
+
31
+ carrier.each do |key, value|
32
+ baggage_match = key.match(/\Auberctx-([\w-]+)\Z/)
33
+ if baggage_match
34
+ context.set_baggage_item(baggage_match[1], value)
35
+ end
36
+ end
37
+
38
+ context
39
+ end
40
+ end
41
+
42
+ class JaegerRackCodec
43
+ def self.extract(carrier)
44
+ serialized_trace = carrier['HTTP_UBER_TRACE_ID']
45
+ serialized_trace = CGI.unescape(serialized_trace) if serialized_trace
46
+ context = SerializedJaegerTrace.parse(serialized_trace)
47
+ return nil unless context
48
+
49
+ carrier.each do |key, value|
50
+ baggage_match = key.match(/\AHTTP_UBERCTX_(\w+)\Z/)
51
+ if baggage_match
52
+ key = baggage_match[1].downcase.tr('_', '-')
53
+ context.set_baggage_item(key, CGI.unescape(value))
54
+ end
55
+ end
56
+
57
+ context
58
+ end
59
+ end
60
+
61
+ class JaegerBinaryCodec
62
+ def self.extract(_carrier)
63
+ warn 'Jaeger::Client with binary format is not supported yet'
64
+ end
65
+ end
66
+
67
+ class B3RackCodec
68
+ class Keys
69
+ TRACE_ID = 'HTTP_X_B3_TRACEID'.freeze
70
+ SPAN_ID = 'HTTP_X_B3_SPANID'.freeze
71
+ PARENT_SPAN_ID = 'HTTP_X_B3_PARENTSPANID'.freeze
72
+ FLAGS = 'HTTP_X_B3_FLAGS'.freeze
73
+ SAMPLED = 'HTTP_X_B3_SAMPLED'.freeze
74
+ end.freeze
75
+
76
+ def self.extract(carrier)
77
+ B3CodecCommon.extract(carrier, Keys)
78
+ end
79
+ end
80
+
81
+ class B3TextMapCodec
82
+ class Keys
83
+ TRACE_ID = 'x-b3-traceid'.freeze
84
+ SPAN_ID = 'x-b3-spanid'.freeze
85
+ PARENT_SPAN_ID = 'x-b3-parentspanid'.freeze
86
+ FLAGS = 'x-b3-flags'.freeze
87
+ SAMPLED = 'x-b3-sampled'.freeze
88
+ end.freeze
89
+
90
+ def self.extract(carrier)
91
+ B3CodecCommon.extract(carrier, Keys)
92
+ end
93
+ end
94
+
95
+ class B3CodecCommon
96
+ def self.extract(carrier, keys)
97
+ return nil if carrier[keys::TRACE_ID].nil? || carrier[keys::SPAN_ID].nil?
98
+
99
+ trace_id = if carrier[keys::TRACE_ID].length <= 16
100
+ TraceId.base16_hex_id_to_uint64(carrier[keys::TRACE_ID])
101
+ else
102
+ TraceId.base16_hex_id_to_uint128(carrier[keys::TRACE_ID])
103
+ end
104
+
105
+ span_id = TraceId.base16_hex_id_to_uint64(carrier[keys::SPAN_ID])
106
+ parent_id = TraceId.base16_hex_id_to_uint64(carrier[keys::PARENT_SPAN_ID])
107
+ flags = parse_flags(carrier[keys::FLAGS], carrier[keys::SAMPLED])
108
+
109
+ return nil if span_id.zero? || trace_id.zero?
110
+
111
+ SpanContext.new(
112
+ trace_id: trace_id,
113
+ parent_id: parent_id,
114
+ span_id: span_id,
115
+ flags: flags
116
+ )
117
+ end
118
+
119
+ # if the flags header is '1' then the sampled header should not be present
120
+ def self.parse_flags(flags_header, sampled_header)
121
+ if flags_header == '1'
122
+ Jaeger::SpanContext::Flags::DEBUG
123
+ else
124
+ TraceId.base16_hex_id_to_uint64(sampled_header)
125
+ end
126
+ end
127
+ private_class_method :parse_flags
128
+ end
129
+
130
+ class TraceContextRackCodec
131
+ # Internal regex used to identify the TraceContext version
132
+ VERSION_PATTERN = /^([0-9a-fA-F]{2})-(.+)$/
133
+
134
+ # Internal regex used to parse fields in version 0
135
+ HEADER_V0_PATTERN = /^([0-9a-fA-F]{32})-([0-9a-fA-F]{16})(-([0-9a-fA-F]{2}))?$/
136
+
137
+ def self.extract(carrier)
138
+ header_value = carrier['HTTP_TRACEPARENT']
139
+
140
+ version_match = VERSION_PATTERN.match(header_value)
141
+ return nil unless version_match
142
+
143
+ # We currently only support version 0
144
+ return nil if version_match[1].to_i(16) != 0
145
+
146
+ match = HEADER_V0_PATTERN.match(version_match[2])
147
+ return nil unless match
148
+
149
+ trace_id = TraceId.base16_hex_id_to_uint128(match[1])
150
+ span_id = TraceId.base16_hex_id_to_uint64(match[2])
151
+ flags = TraceId.base16_hex_id_to_uint64(match[4])
152
+ return nil if trace_id.zero? || span_id.zero?
153
+
154
+ SpanContext.new(trace_id: trace_id, span_id: span_id, flags: flags)
155
+ end
156
+ end
157
+
158
+ DEFAULT_EXTRACTORS = {
159
+ OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec,
160
+ OpenTracing::FORMAT_BINARY => JaegerBinaryCodec,
161
+ OpenTracing::FORMAT_RACK => JaegerRackCodec
162
+ }.freeze
163
+
164
+ def self.prepare(extractors)
165
+ DEFAULT_EXTRACTORS.reduce(extractors) do |acc, (format, default)|
166
+ provided_extractors = Array(extractors[format])
167
+ provided_extractors += [default] if provided_extractors.empty?
168
+
169
+ acc.merge(format => provided_extractors)
170
+ end
171
+ end
172
+ end
173
+ end