dox-jaeger-client 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +33 -0
  3. data/.gitignore +12 -0
  4. data/.gitmodules +3 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +61 -0
  7. data/.rubocop_todo.yml +13 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/Makefile +1 -0
  11. data/README.md +210 -0
  12. data/Rakefile +9 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/crossdock/Dockerfile +31 -0
  16. data/crossdock/Gemfile +6 -0
  17. data/crossdock/Gemfile.lock +37 -0
  18. data/crossdock/docker-compose.yml +68 -0
  19. data/crossdock/jaeger-docker-compose.yml +53 -0
  20. data/crossdock/rules.mk +35 -0
  21. data/crossdock/server +175 -0
  22. data/jaeger-client.gemspec +37 -0
  23. data/lib/jaeger/client/version.rb +7 -0
  24. data/lib/jaeger/client.rb +77 -0
  25. data/lib/jaeger/encoders/thrift_encoder.rb +173 -0
  26. data/lib/jaeger/extractors.rb +173 -0
  27. data/lib/jaeger/http_sender.rb +28 -0
  28. data/lib/jaeger/injectors.rb +83 -0
  29. data/lib/jaeger/rate_limiter.rb +61 -0
  30. data/lib/jaeger/recurring_executor.rb +35 -0
  31. data/lib/jaeger/reporters/composite_reporter.rb +17 -0
  32. data/lib/jaeger/reporters/in_memory_reporter.rb +30 -0
  33. data/lib/jaeger/reporters/logging_reporter.rb +22 -0
  34. data/lib/jaeger/reporters/null_reporter.rb +11 -0
  35. data/lib/jaeger/reporters/remote_reporter/buffer.rb +29 -0
  36. data/lib/jaeger/reporters/remote_reporter.rb +42 -0
  37. data/lib/jaeger/reporters.rb +7 -0
  38. data/lib/jaeger/samplers/const.rb +24 -0
  39. data/lib/jaeger/samplers/guaranteed_throughput_probabilistic.rb +47 -0
  40. data/lib/jaeger/samplers/per_operation.rb +77 -0
  41. data/lib/jaeger/samplers/probabilistic.rb +40 -0
  42. data/lib/jaeger/samplers/rate_limiting.rb +51 -0
  43. data/lib/jaeger/samplers/remote_controlled/instructions_fetcher.rb +34 -0
  44. data/lib/jaeger/samplers/remote_controlled.rb +119 -0
  45. data/lib/jaeger/samplers.rb +8 -0
  46. data/lib/jaeger/scope.rb +39 -0
  47. data/lib/jaeger/scope_manager/scope_identifier.rb +13 -0
  48. data/lib/jaeger/scope_manager/scope_stack.rb +33 -0
  49. data/lib/jaeger/scope_manager.rb +48 -0
  50. data/lib/jaeger/span/thrift_log_builder.rb +18 -0
  51. data/lib/jaeger/span.rb +97 -0
  52. data/lib/jaeger/span_context.rb +57 -0
  53. data/lib/jaeger/thrift_tag_builder.rb +42 -0
  54. data/lib/jaeger/trace_id.rb +48 -0
  55. data/lib/jaeger/tracer.rb +214 -0
  56. data/lib/jaeger/udp_sender/transport.rb +41 -0
  57. data/lib/jaeger/udp_sender.rb +26 -0
  58. data/script/create_follows_from_trace +51 -0
  59. data/script/create_trace +52 -0
  60. data/thrift/agent.thrift +32 -0
  61. data/thrift/gen-rb/jaeger/thrift/agent/agent.rb +118 -0
  62. data/thrift/gen-rb/jaeger/thrift/agent/agent_constants.rb +15 -0
  63. data/thrift/gen-rb/jaeger/thrift/agent/agent_types.rb +17 -0
  64. data/thrift/gen-rb/jaeger/thrift/agent.rb +116 -0
  65. data/thrift/gen-rb/jaeger/thrift/agent_constants.rb +13 -0
  66. data/thrift/gen-rb/jaeger/thrift/agent_types.rb +15 -0
  67. data/thrift/gen-rb/jaeger/thrift/collector.rb +82 -0
  68. data/thrift/gen-rb/jaeger/thrift/jaeger_constants.rb +13 -0
  69. data/thrift/gen-rb/jaeger/thrift/jaeger_types.rb +211 -0
  70. data/thrift/gen-rb/jaeger/thrift/zipkin/zipkin_collector.rb +84 -0
  71. data/thrift/gen-rb/jaeger/thrift/zipkin/zipkincore_constants.rb +41 -0
  72. data/thrift/gen-rb/jaeger/thrift/zipkin/zipkincore_types.rb +220 -0
  73. data/thrift/jaeger.thrift +88 -0
  74. data/thrift/zipkincore.thrift +300 -0
  75. metadata +260 -0
data/crossdock/server ADDED
@@ -0,0 +1,175 @@
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
+ case transport
40
+ when 'HTTP'
41
+ call_downstream_http(downstream, server_span)
42
+ when 'DUMMY'
43
+ { notImplementedError: 'Dummy has not been implemented' }
44
+ else
45
+ { notImplementedError: "Unrecognized transport received: #{transport}" }
46
+ end
47
+ end
48
+
49
+ puts "Response: #{response}"
50
+
51
+ server_span.finish
52
+ body JSON.dump(response)
53
+ end
54
+
55
+ post '/join_trace' do
56
+ puts 'Got request to join trace' \
57
+ "\n Params: #{trace_request}" \
58
+ "\n Headers: #{request_headers(request)}"
59
+
60
+ parent_context = tracer.extract(OpenTracing::FORMAT_RACK, request.env)
61
+ server_span = tracer.start_span('/join_trace', child_of: parent_context)
62
+
63
+ response = {
64
+ span: observe_span(server_span),
65
+ notImplementedError: ''
66
+ }
67
+
68
+ if trace_request['downstream']
69
+ downstream = trace_request['downstream']
70
+ transport = downstream['transport']
71
+
72
+ response[:downstream] =
73
+ case transport
74
+ when 'HTTP'
75
+ call_downstream_http(downstream, server_span)
76
+ when 'DUMMY'
77
+ { notImplementedError: 'Dummy has not been implemented' }
78
+ else
79
+ { notImplementedError: "Unrecognized transport received: #{transport}" }
80
+ end
81
+ end
82
+
83
+ puts "Response: #{response}"
84
+
85
+ server_span.finish
86
+ body JSON.dump(response)
87
+ end
88
+
89
+ post '/create_traces' do
90
+ puts "Got request to create traces: #{trace_request}"
91
+
92
+ trace_request['count'].times do
93
+ span = tracer.start_span(trace_request['operation'], tags: trace_request['tags'])
94
+ span.finish
95
+ end
96
+
97
+ status 200
98
+ end
99
+
100
+ private
101
+
102
+ def tracer
103
+ @tracer ||= Jaeger::Client.build(
104
+ service_name: 'crossdock-ruby',
105
+ host: 'jaeger-agent',
106
+ port: 6831,
107
+ flush_interval: 1,
108
+ sampler: Jaeger::Samplers::Const.new(true)
109
+ )
110
+ end
111
+
112
+ def trace_request
113
+ @trace_request ||= begin
114
+ request.body.rewind
115
+ JSON.parse(request.body.read)
116
+ end
117
+ end
118
+
119
+ def observe_span(span)
120
+ if span
121
+ {
122
+ traceId: span.context.to_trace_id,
123
+ sampled: span.context.sampled?,
124
+ baggage: span.get_baggage_item('crossdock-baggage-key')
125
+ }
126
+ else
127
+ {
128
+ traceId: 'no span found',
129
+ sampled: false,
130
+ baggage: 'no span found'
131
+ }
132
+ end
133
+ end
134
+
135
+ def call_downstream_http(downstream, server_span)
136
+ downstream_url = "http://#{downstream['host']}:#{downstream['port']}/join_trace"
137
+
138
+ client_span = tracer.start_span('client-span', child_of: server_span)
139
+
140
+ headers = { 'Content-Type' => 'application/json' }
141
+ tracer.inject(client_span.context, OpenTracing::FORMAT_RACK, headers)
142
+
143
+ response = Net::HTTP.post(
144
+ URI(downstream_url),
145
+ JSON.dump(
146
+ serverRole: downstream['serverRole'],
147
+ downstream: downstream['downstream']
148
+ ),
149
+ headers
150
+ )
151
+
152
+ client_span.finish
153
+
154
+ if response.is_a?(Net::HTTPSuccess)
155
+ JSON.parse(response.body)
156
+ else
157
+ { error: response.body }
158
+ end
159
+ end
160
+
161
+ def request_headers(request)
162
+ request.env.select do |key, _value|
163
+ key.start_with?('HTTP_')
164
+ end
165
+ end
166
+ end
167
+
168
+ threads = []
169
+ threads << Thread.new do
170
+ Rack::Handler::WEBrick.run(HealthServer, Port: 8080, Host: '0.0.0.0')
171
+ end
172
+ threads << Thread.new do
173
+ Rack::Handler::WEBrick.run(HttpServer, Port: 8081, Host: '0.0.0.0')
174
+ end
175
+ threads.each(&:join)
@@ -0,0 +1,37 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'jaeger/client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'dox-jaeger-client'
8
+ spec.version = Jaeger::Client::VERSION
9
+ spec.authors = ['Jeremiah Hemphill']
10
+ spec.email = ['jhemphill@doximity.com']
11
+ spec.metadata['rubygems_mfa_required'] = 'true'
12
+
13
+ spec.summary = 'OpenTracing Tracer implementation for Jaeger in Ruby with ruby 3.2 support'
14
+ spec.description = 'OpenTracing Tracer implementation for Jaeger in Ruby with ruby 3.2 support'
15
+ spec.homepage = 'https://github.com/doximity/jaeger-client-ruby'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.required_ruby_version = ['>= 2.7', '< 3.3']
25
+
26
+ spec.add_development_dependency 'bundler'
27
+ spec.add_development_dependency 'rake', '~> 13.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.10'
29
+ spec.add_development_dependency 'rubocop', '~> 1.25'
30
+ spec.add_development_dependency 'rubocop-rake', '~> 0.6'
31
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.8'
32
+ spec.add_development_dependency 'timecop', '~> 0.9'
33
+ spec.add_development_dependency 'webmock', '~> 3.14'
34
+
35
+ spec.add_dependency 'opentracing', '~> 0.3'
36
+ spec.add_dependency 'thrift'
37
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Client
5
+ VERSION = '2.0.0'
6
+ end
7
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push("#{File.dirname(__FILE__)}/../../thrift/gen-rb")
4
+
5
+ require 'opentracing'
6
+ require 'jaeger/thrift/agent'
7
+ require 'logger'
8
+ require 'time'
9
+ require 'net/http'
10
+ require 'cgi'
11
+ require 'json'
12
+
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'
22
+ require_relative 'client/version'
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'
30
+
31
+ module Jaeger
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
+
44
+ DEFAULT_FLUSH_INTERVAL = 10
45
+
46
+ def self.build(service_name:,
47
+ host: '127.0.0.1',
48
+ port: 6831,
49
+ flush_interval: DEFAULT_FLUSH_INTERVAL,
50
+ sampler: Samplers::Const.new(true),
51
+ logger: Logger.new($stdout),
52
+ sender: nil,
53
+ reporter: nil,
54
+ injectors: {},
55
+ extractors: {},
56
+ tags: {})
57
+ encoder = Encoders::ThriftEncoder.new(service_name: service_name, tags: tags, logger: logger)
58
+
59
+ if sender
60
+ warn '[DEPRECATION] Passing `sender` directly to Jaeger::Client.build is deprecated.' \
61
+ 'Please use `reporter` instead.'
62
+ end
63
+
64
+ reporter ||= Reporters::RemoteReporter.new(
65
+ sender: sender || UdpSender.new(host: host, port: port, encoder: encoder, logger: logger),
66
+ flush_interval: flush_interval
67
+ )
68
+
69
+ Tracer.new(
70
+ reporter: reporter,
71
+ sampler: sampler,
72
+ injectors: Injectors.prepare(injectors),
73
+ extractors: Extractors.prepare(extractors)
74
+ )
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jaeger
4
+ module Encoders
5
+ class ThriftEncoder
6
+ def initialize(service_name:, logger:, tags: {})
7
+ @service_name = service_name
8
+ @logger = logger
9
+ @tags = prepare_tags(tags)
10
+ @process = Jaeger::Thrift::Process.new('serviceName' => @service_name, 'tags' => @tags)
11
+ end
12
+
13
+ def encode(spans)
14
+ encode_batch(spans.map(&method(:encode_span)))
15
+ end
16
+
17
+ def encode_limited_size(spans, protocol_class, max_message_length)
18
+ batches = []
19
+ current_batch = []
20
+ current_batch_size = 0
21
+
22
+ max_spans_length = calculate_max_spans_length(protocol_class, max_message_length)
23
+
24
+ spans.each do |span|
25
+ encoded_span = encode_span(span)
26
+ span_size = message_size(encoded_span, protocol_class)
27
+
28
+ if span_size > max_spans_length
29
+ @logger.warn("Skip span #{span.operation_name} with size #{span_size}")
30
+ next
31
+ end
32
+
33
+ if (current_batch_size + span_size) > max_spans_length
34
+ batches << encode_batch(current_batch)
35
+ current_batch = []
36
+ current_batch_size = 0
37
+ end
38
+
39
+ current_batch << encoded_span
40
+ current_batch_size += span_size
41
+ end
42
+ batches << encode_batch(current_batch) unless current_batch.empty?
43
+ batches
44
+ end
45
+
46
+ private
47
+
48
+ def encode_batch(encoded_spans)
49
+ Jaeger::Thrift::Batch.new('process' => @process, 'spans' => encoded_spans)
50
+ end
51
+
52
+ def encode_span(span)
53
+ context = span.context
54
+ start_ts, duration = build_timestamps(span)
55
+
56
+ Jaeger::Thrift::Span.new(
57
+ 'traceIdLow' => TraceId.uint64_id_to_int64(trace_id_to_low(context.trace_id)),
58
+ 'traceIdHigh' => TraceId.uint64_id_to_int64(trace_id_to_high(context.trace_id)),
59
+ 'spanId' => TraceId.uint64_id_to_int64(context.span_id),
60
+ 'parentSpanId' => TraceId.uint64_id_to_int64(context.parent_id),
61
+ 'operationName' => span.operation_name,
62
+ 'references' => build_references(span.references || []),
63
+ 'flags' => context.flags,
64
+ 'startTime' => start_ts,
65
+ 'duration' => duration,
66
+ 'tags' => span.tags,
67
+ 'logs' => span.logs
68
+ )
69
+ end
70
+
71
+ def build_references(references)
72
+ references.map do |ref|
73
+ Jaeger::Thrift::SpanRef.new(
74
+ 'refType' => span_ref_type(ref.type),
75
+ 'traceIdLow' => TraceId.uint64_id_to_int64(trace_id_to_low(ref.context.trace_id)),
76
+ 'traceIdHigh' => TraceId.uint64_id_to_int64(trace_id_to_high(ref.context.trace_id)),
77
+ 'spanId' => TraceId.uint64_id_to_int64(ref.context.span_id)
78
+ )
79
+ end
80
+ end
81
+
82
+ def build_timestamps(span)
83
+ start_ts = (span.start_time.to_f * 1_000_000).to_i
84
+ end_ts = (span.end_time.to_f * 1_000_000).to_i
85
+ duration = end_ts - start_ts
86
+ [start_ts, duration]
87
+ end
88
+
89
+ def span_ref_type(type)
90
+ case type
91
+ when OpenTracing::Reference::CHILD_OF
92
+ Jaeger::Thrift::SpanRefType::CHILD_OF
93
+ when OpenTracing::Reference::FOLLOWS_FROM
94
+ Jaeger::Thrift::SpanRefType::FOLLOWS_FROM
95
+ else
96
+ warn "Jaeger::Client with format #{type} is not supported yet"
97
+ nil
98
+ end
99
+ end
100
+
101
+ def prepare_tags(tags)
102
+ with_default_tags = tags.dup
103
+ with_default_tags['jaeger.version'] = "Ruby-#{Jaeger::Client::VERSION}"
104
+ with_default_tags['hostname'] ||= Socket.gethostname
105
+
106
+ unless with_default_tags['ip']
107
+ ipv4 = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }
108
+ with_default_tags['ip'] = ipv4.ip_address unless ipv4.nil?
109
+ end
110
+
111
+ with_default_tags.map do |key, value|
112
+ ThriftTagBuilder.build(key, value)
113
+ end
114
+ end
115
+
116
+ # Returns the right most 64 bits of trace id
117
+ def trace_id_to_low(trace_id)
118
+ trace_id & TraceId::MAX_64BIT_UNSIGNED_INT
119
+ end
120
+
121
+ # Returns the left most 64 bits of trace id
122
+ def trace_id_to_high(trace_id)
123
+ (trace_id >> 64) & TraceId::MAX_64BIT_UNSIGNED_INT
124
+ end
125
+
126
+ class DummyTransport
127
+ attr_accessor :size
128
+
129
+ def initialize
130
+ @size = 0
131
+ end
132
+
133
+ def write(buf)
134
+ @size += buf.size
135
+ end
136
+
137
+ def flush
138
+ @size = 0
139
+ end
140
+
141
+ def close; end
142
+ end
143
+
144
+ def message_size(message, protocol_class)
145
+ transport = DummyTransport.new
146
+ protocol = protocol_class.new(transport)
147
+ message.write(protocol)
148
+ transport.size
149
+ end
150
+
151
+ # Compact protocol have dynamic size of list header
152
+ # https://erikvanoosten.github.io/thrift-missing-specification/#_list_and_set_2
153
+ BATCH_SPANS_SIZE_WINDOW = 4
154
+
155
+ def empty_batch_size_cache
156
+ @empty_batch_size_cache ||= {}
157
+ end
158
+
159
+ def caclulate_empty_batch_size(protocol_class)
160
+ empty_batch_size_cache[protocol_class] ||=
161
+ message_size(encode_batch([]), protocol_class) + BATCH_SPANS_SIZE_WINDOW
162
+ end
163
+
164
+ def calculate_max_spans_length(protocol_class, max_message_length)
165
+ empty_batch_size = caclulate_empty_batch_size(protocol_class)
166
+ max_spans_length = max_message_length - empty_batch_size
167
+ return max_spans_length if max_spans_length.positive?
168
+
169
+ raise "Batch header have size #{empty_batch_size}, but limit #{max_message_length}"
170
+ end
171
+ end
172
+ end
173
+ 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'
70
+ SPAN_ID = 'HTTP_X_B3_SPANID'
71
+ PARENT_SPAN_ID = 'HTTP_X_B3_PARENTSPANID'
72
+ FLAGS = 'HTTP_X_B3_FLAGS'
73
+ SAMPLED = 'HTTP_X_B3_SAMPLED'
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'
84
+ SPAN_ID = 'x-b3-spanid'
85
+ PARENT_SPAN_ID = 'x-b3-parentspanid'
86
+ FLAGS = 'x-b3-flags'
87
+ SAMPLED = 'x-b3-sampled'
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})-(.+)$/.freeze
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}))?$/.freeze
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
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Jaeger
6
+ class HttpSender
7
+ def initialize(url:, encoder:, headers: {}, logger: Logger.new($stdout))
8
+ @encoder = encoder
9
+ @logger = logger
10
+
11
+ @uri = URI(url)
12
+ @uri.query = 'format=jaeger.thrift'
13
+
14
+ @transport = ::Thrift::HTTPClientTransport.new(@uri.to_s)
15
+ @transport.add_headers(headers)
16
+
17
+ @serializer = ::Thrift::Serializer.new
18
+ end
19
+
20
+ def send_spans(spans)
21
+ batch = @encoder.encode(spans)
22
+ @transport.write(@serializer.serialize(batch))
23
+ @transport.flush
24
+ rescue StandardError => e
25
+ @logger.error("Failure while sending a batch of spans: #{e}")
26
+ end
27
+ end
28
+ end