jaeger-client 0.7.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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