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.
- checksums.yaml +4 -4
- data/.gitmodules +3 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +11 -2
- data/Makefile +1 -0
- data/README.md +127 -13
- data/crossdock/Dockerfile +29 -0
- data/crossdock/Gemfile +6 -0
- data/crossdock/Gemfile.lock +35 -0
- data/crossdock/docker-compose.yml +68 -0
- data/crossdock/jaeger-docker-compose.yml +48 -0
- data/crossdock/rules.mk +35 -0
- data/crossdock/server +173 -0
- data/jaeger-client.gemspec +4 -2
- data/lib/jaeger/client.rb +42 -18
- data/lib/jaeger/client/version.rb +1 -1
- data/lib/jaeger/encoders/thrift_encoder.rb +142 -0
- data/lib/jaeger/extractors.rb +173 -0
- data/lib/jaeger/http_sender.rb +28 -0
- data/lib/jaeger/injectors.rb +83 -0
- data/lib/jaeger/rate_limiter.rb +61 -0
- data/lib/jaeger/recurring_executor.rb +35 -0
- data/lib/jaeger/reporters.rb +7 -0
- data/lib/jaeger/reporters/composite_reporter.rb +17 -0
- data/lib/jaeger/reporters/in_memory_reporter.rb +30 -0
- data/lib/jaeger/reporters/logging_reporter.rb +22 -0
- data/lib/jaeger/reporters/null_reporter.rb +11 -0
- data/lib/jaeger/{client/async_reporter.rb → reporters/remote_reporter.rb} +21 -20
- data/lib/jaeger/{client/async_reporter → reporters/remote_reporter}/buffer.rb +2 -2
- data/lib/jaeger/samplers.rb +8 -0
- data/lib/jaeger/samplers/const.rb +24 -0
- data/lib/jaeger/samplers/guaranteed_throughput_probabilistic.rb +47 -0
- data/lib/jaeger/samplers/per_operation.rb +79 -0
- data/lib/jaeger/samplers/probabilistic.rb +38 -0
- data/lib/jaeger/samplers/rate_limiting.rb +51 -0
- data/lib/jaeger/samplers/remote_controlled.rb +119 -0
- data/lib/jaeger/samplers/remote_controlled/instructions_fetcher.rb +34 -0
- data/lib/jaeger/scope.rb +38 -0
- data/lib/jaeger/scope_manager.rb +47 -0
- data/lib/jaeger/scope_manager/scope_identifier.rb +13 -0
- data/lib/jaeger/scope_manager/scope_stack.rb +33 -0
- data/lib/jaeger/span.rb +97 -0
- data/lib/jaeger/span/thrift_log_builder.rb +18 -0
- data/lib/jaeger/span_context.rb +57 -0
- data/lib/jaeger/thrift_tag_builder.rb +41 -0
- data/lib/jaeger/trace_id.rb +48 -0
- data/lib/jaeger/tracer.rb +213 -0
- data/lib/jaeger/udp_sender.rb +26 -0
- data/lib/jaeger/udp_sender/transport.rb +40 -0
- metadata +78 -31
- data/lib/jaeger/client/carrier.rb +0 -26
- data/lib/jaeger/client/encoders/thrift_encoder.rb +0 -94
- data/lib/jaeger/client/extractors.rb +0 -88
- data/lib/jaeger/client/http_sender.rb +0 -30
- data/lib/jaeger/client/injectors.rb +0 -54
- data/lib/jaeger/client/samplers.rb +0 -4
- data/lib/jaeger/client/samplers/const.rb +0 -29
- data/lib/jaeger/client/samplers/probabilistic.rb +0 -30
- data/lib/jaeger/client/scope.rb +0 -40
- data/lib/jaeger/client/scope_manager.rb +0 -49
- data/lib/jaeger/client/scope_manager/scope_identifier.rb +0 -15
- data/lib/jaeger/client/scope_manager/scope_stack.rb +0 -35
- data/lib/jaeger/client/span.rb +0 -84
- data/lib/jaeger/client/span/thrift_log_builder.rb +0 -20
- data/lib/jaeger/client/span/thrift_tag_builder.rb +0 -45
- data/lib/jaeger/client/span_context.rb +0 -59
- data/lib/jaeger/client/trace_id.rb +0 -41
- data/lib/jaeger/client/tracer.rb +0 -189
- data/lib/jaeger/client/udp_sender.rb +0 -27
- data/lib/jaeger/client/udp_sender/transport.rb +0 -42
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Jaeger
|
6
|
+
class HttpSender
|
7
|
+
def initialize(url:, headers: {}, encoder:, 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 => error
|
25
|
+
@logger.error("Failure while sending a batch of spans: #{error}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jaeger
|
4
|
+
module Injectors
|
5
|
+
def self.context_as_jaeger_string(span_context)
|
6
|
+
[
|
7
|
+
span_context.trace_id.to_s(16),
|
8
|
+
span_context.span_id.to_s(16),
|
9
|
+
span_context.parent_id.to_s(16),
|
10
|
+
span_context.flags.to_s(16)
|
11
|
+
].join(':')
|
12
|
+
end
|
13
|
+
|
14
|
+
class JaegerTextMapCodec
|
15
|
+
def self.inject(span_context, carrier)
|
16
|
+
carrier['uber-trace-id'] = Injectors.context_as_jaeger_string(span_context)
|
17
|
+
span_context.baggage.each do |key, value|
|
18
|
+
carrier["uberctx-#{key}"] = value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class JaegerRackCodec
|
24
|
+
def self.inject(span_context, carrier)
|
25
|
+
carrier['uber-trace-id'] =
|
26
|
+
CGI.escape(Injectors.context_as_jaeger_string(span_context))
|
27
|
+
span_context.baggage.each do |key, value|
|
28
|
+
carrier["uberctx-#{key}"] = CGI.escape(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class JaegerBinaryCodec
|
34
|
+
def self.inject(_span_context, _carrier)
|
35
|
+
warn 'Jaeger::Client with binary format is not supported yet'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class B3RackCodec
|
40
|
+
def self.inject(span_context, carrier)
|
41
|
+
carrier['x-b3-traceid'] = TraceId.to_hex(span_context.trace_id)
|
42
|
+
carrier['x-b3-spanid'] = TraceId.to_hex(span_context.span_id)
|
43
|
+
carrier['x-b3-parentspanid'] = TraceId.to_hex(span_context.parent_id)
|
44
|
+
|
45
|
+
# flags (for debug) and sampled headers are mutually exclusive
|
46
|
+
if span_context.flags == Jaeger::SpanContext::Flags::DEBUG
|
47
|
+
carrier['x-b3-flags'] = '1'
|
48
|
+
else
|
49
|
+
carrier['x-b3-sampled'] = span_context.flags.to_s(16)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class TraceContextRackCodec
|
55
|
+
def self.inject(span_context, carrier)
|
56
|
+
flags = span_context.sampled? || span_context.debug? ? 1 : 0
|
57
|
+
|
58
|
+
carrier['traceparent'] = format(
|
59
|
+
'%<version>s-%<trace_id>s-%<span_id>s-%<flags>s',
|
60
|
+
version: '00',
|
61
|
+
trace_id: span_context.trace_id.to_s(16).rjust(32, '0'),
|
62
|
+
span_id: span_context.span_id.to_s(16).rjust(16, '0'),
|
63
|
+
flags: flags.to_s(16).rjust(2, '0')
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
DEFAULT_INJECTORS = {
|
69
|
+
OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec,
|
70
|
+
OpenTracing::FORMAT_BINARY => JaegerBinaryCodec,
|
71
|
+
OpenTracing::FORMAT_RACK => JaegerRackCodec
|
72
|
+
}.freeze
|
73
|
+
|
74
|
+
def self.prepare(injectors)
|
75
|
+
DEFAULT_INJECTORS.reduce(injectors) do |acc, (format, default)|
|
76
|
+
provided_injectors = Array(injectors[format])
|
77
|
+
provided_injectors += [default] if provided_injectors.empty?
|
78
|
+
|
79
|
+
acc.merge(format => provided_injectors)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jaeger
|
4
|
+
# RateLimiter is based on leaky bucket algorithm, formulated in terms of a
|
5
|
+
# credits balance that is replenished every time check_credit() method is
|
6
|
+
# called (tick) by the amount proportional to the time elapsed since the
|
7
|
+
# last tick, up to the max_balance. A call to check_credit() takes a cost
|
8
|
+
# of an item we want to pay with the balance. If the balance exceeds the
|
9
|
+
# cost of the item, the item is "purchased" and the balance reduced,
|
10
|
+
# indicated by returned value of true. Otherwise the balance is unchanged
|
11
|
+
# and return false.
|
12
|
+
#
|
13
|
+
# This can be used to limit a rate of messages emitted by a service by
|
14
|
+
# instantiating the Rate Limiter with the max number of messages a service
|
15
|
+
# is allowed to emit per second, and calling check_credit(1.0) for each
|
16
|
+
# message to determine if the message is within the rate limit.
|
17
|
+
#
|
18
|
+
# It can also be used to limit the rate of traffic in bytes, by setting
|
19
|
+
# credits_per_second to desired throughput as bytes/second, and calling
|
20
|
+
# check_credit() with the actual message size.
|
21
|
+
class RateLimiter
|
22
|
+
def initialize(credits_per_second:, max_balance:)
|
23
|
+
@credits_per_second = credits_per_second
|
24
|
+
@max_balance = max_balance
|
25
|
+
@balance = max_balance
|
26
|
+
@last_tick = Time.now
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_credit(item_cost)
|
30
|
+
update_balance
|
31
|
+
|
32
|
+
return false if @balance < item_cost
|
33
|
+
|
34
|
+
@balance -= item_cost
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(credits_per_second:, max_balance:)
|
39
|
+
update_balance
|
40
|
+
|
41
|
+
@credits_per_second = credits_per_second
|
42
|
+
|
43
|
+
# The new balance should be proportional to the old balance
|
44
|
+
@balance = max_balance * @balance / @max_balance
|
45
|
+
@max_balance = max_balance
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def update_balance
|
51
|
+
current_time = Time.now
|
52
|
+
elapsed_time = current_time - @last_tick
|
53
|
+
@last_tick = current_time
|
54
|
+
|
55
|
+
@balance += elapsed_time * @credits_per_second
|
56
|
+
return if @balance <= @max_balance
|
57
|
+
|
58
|
+
@balance = @max_balance
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jaeger
|
4
|
+
# Executes a given block periodically. The block will be executed only once
|
5
|
+
# when interval is set to 0.
|
6
|
+
class RecurringExecutor
|
7
|
+
def initialize(interval:)
|
8
|
+
@interval = interval
|
9
|
+
end
|
10
|
+
|
11
|
+
def start(&block)
|
12
|
+
raise 'Already running' if @thread
|
13
|
+
|
14
|
+
@thread = Thread.new do
|
15
|
+
if @interval <= 0
|
16
|
+
yield
|
17
|
+
else
|
18
|
+
loop do
|
19
|
+
yield
|
20
|
+
sleep @interval
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def running?
|
27
|
+
@thread && @thread.alive?
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@thread.kill
|
32
|
+
@thread = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'reporters/composite_reporter'
|
4
|
+
require_relative 'reporters/in_memory_reporter'
|
5
|
+
require_relative 'reporters/logging_reporter'
|
6
|
+
require_relative 'reporters/null_reporter'
|
7
|
+
require_relative 'reporters/remote_reporter'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jaeger
|
4
|
+
module Reporters
|
5
|
+
class CompositeReporter
|
6
|
+
def initialize(reporters:)
|
7
|
+
@reporters = reporters
|
8
|
+
end
|
9
|
+
|
10
|
+
def report(span)
|
11
|
+
@reporters.each do |reporter|
|
12
|
+
reporter.report(span)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jaeger
|
4
|
+
module Reporters
|
5
|
+
class InMemoryReporter
|
6
|
+
def initialize
|
7
|
+
@spans = []
|
8
|
+
@mutex = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def report(span)
|
12
|
+
@mutex.synchronize do
|
13
|
+
@spans << span
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def spans
|
18
|
+
@mutex.synchronize do
|
19
|
+
@spans
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear
|
24
|
+
@mutex.synchronize do
|
25
|
+
@spans.clear
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jaeger
|
4
|
+
module Reporters
|
5
|
+
class LoggingReporter
|
6
|
+
def initialize(logger: Logger.new($stdout))
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def report(span)
|
11
|
+
span_info = {
|
12
|
+
operation_name: span.operation_name,
|
13
|
+
start_time: span.start_time.iso8601,
|
14
|
+
end_time: span.end_time.iso8601,
|
15
|
+
trace_id: span.context.to_trace_id,
|
16
|
+
span_id: span.context.to_span_id
|
17
|
+
}
|
18
|
+
@logger.info "Span reported: #{span_info}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,28 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
require_relative './async_reporter/buffer'
|
3
|
+
require_relative './remote_reporter/buffer'
|
6
4
|
|
7
5
|
module Jaeger
|
8
|
-
module
|
9
|
-
class
|
10
|
-
def
|
11
|
-
reporter = new(sender)
|
12
|
-
|
13
|
-
# start flush thread
|
14
|
-
Thread.new do
|
15
|
-
loop do
|
16
|
-
reporter.flush
|
17
|
-
sleep(flush_interval)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
reporter
|
22
|
-
end
|
23
|
-
|
24
|
-
def initialize(sender)
|
6
|
+
module Reporters
|
7
|
+
class RemoteReporter
|
8
|
+
def initialize(sender:, flush_interval:)
|
25
9
|
@sender = sender
|
10
|
+
@flush_interval = flush_interval
|
26
11
|
@buffer = Buffer.new
|
27
12
|
end
|
28
13
|
|
@@ -34,8 +19,24 @@ module Jaeger
|
|
34
19
|
|
35
20
|
def report(span)
|
36
21
|
return if !span.context.sampled? && !span.context.debug?
|
22
|
+
|
23
|
+
init_reporter_thread
|
37
24
|
@buffer << span
|
38
25
|
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def init_reporter_thread
|
30
|
+
return if @initializer_pid == Process.pid
|
31
|
+
|
32
|
+
@initializer_pid = Process.pid
|
33
|
+
Thread.new do
|
34
|
+
loop do
|
35
|
+
flush
|
36
|
+
sleep(@flush_interval)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'samplers/const'
|
4
|
+
require_relative 'samplers/guaranteed_throughput_probabilistic'
|
5
|
+
require_relative 'samplers/per_operation'
|
6
|
+
require_relative 'samplers/probabilistic'
|
7
|
+
require_relative 'samplers/rate_limiting'
|
8
|
+
require_relative 'samplers/remote_controlled'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jaeger
|
4
|
+
module Samplers
|
5
|
+
# Const sampler
|
6
|
+
#
|
7
|
+
# A sampler that always makes the same decision for new traces depending
|
8
|
+
# on the initialization value. Use `Jaeger::Samplers::Const.new(true)`
|
9
|
+
# to mark all new traces as sampled.
|
10
|
+
class Const
|
11
|
+
def initialize(decision)
|
12
|
+
@decision = decision
|
13
|
+
@tags = {
|
14
|
+
'sampler.type' => 'const',
|
15
|
+
'sampler.param' => @decision ? 1 : 0
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def sample(*)
|
20
|
+
[@decision, @tags]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jaeger
|
4
|
+
module Samplers
|
5
|
+
# A sampler that leverages both Probabilistic sampler and RateLimiting
|
6
|
+
# sampler. The RateLimiting is used as a guaranteed lower bound sampler
|
7
|
+
# such that every operation is sampled at least once in a time interval
|
8
|
+
# defined by the lower_bound. ie a lower_bound of 1.0 / (60 * 10) will
|
9
|
+
# sample an operation at least once every 10 minutes.
|
10
|
+
#
|
11
|
+
# The Probabilistic sampler is given higher priority when tags are
|
12
|
+
# emitted, ie. if is_sampled() for both samplers return true, the tags
|
13
|
+
# for Probabilistic sampler will be used.
|
14
|
+
class GuaranteedThroughputProbabilistic
|
15
|
+
attr_reader :tags, :probabilistic_sampler, :lower_bound_sampler
|
16
|
+
|
17
|
+
def initialize(lower_bound:, rate:, lower_bound_sampler: nil)
|
18
|
+
@probabilistic_sampler = Probabilistic.new(rate: rate)
|
19
|
+
@lower_bound_sampler = lower_bound_sampler || RateLimiting.new(max_traces_per_second: lower_bound)
|
20
|
+
@lower_bound_tags = {
|
21
|
+
'sampler.type' => 'lowerbound',
|
22
|
+
'sampler.param' => rate
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def update(lower_bound:, rate:)
|
27
|
+
is_updated = @probabilistic_sampler.update(rate: rate)
|
28
|
+
is_updated = @lower_bound_sampler.update(max_traces_per_second: lower_bound) || is_updated
|
29
|
+
@lower_bound_tags['sampler.param'] = rate
|
30
|
+
is_updated
|
31
|
+
end
|
32
|
+
|
33
|
+
def sample(*args)
|
34
|
+
is_sampled, probabilistic_tags = @probabilistic_sampler.sample(*args)
|
35
|
+
if is_sampled
|
36
|
+
# We still call lower_bound_sampler to update the rate limiter budget
|
37
|
+
@lower_bound_sampler.sample(*args)
|
38
|
+
|
39
|
+
return [is_sampled, probabilistic_tags]
|
40
|
+
end
|
41
|
+
|
42
|
+
is_sampled, _tags = @lower_bound_sampler.sample(*args)
|
43
|
+
[is_sampled, @lower_bound_tags]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|