dox-jaeger-client 2.0.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 (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