zipkin 1.5.0 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: '08c97e0494e2aada96486d717d1cd8b645da89c2'
4
- data.tar.gz: dc7681e5f0a7c4efe293f22ad496f9c352675137
2
+ SHA256:
3
+ metadata.gz: d406755c3fae0a6ad07050d0bfa19a903ce5e5ca414ad0530d172061ef0e2a69
4
+ data.tar.gz: 8f377efd4748398e92dc6cf565f40819e25cf26ca0a0b2f9c119e21ed7ce36c6
5
5
  SHA512:
6
- metadata.gz: 3f80229eec38c0f34c83276264b90073195b80e74f5389569ff5458957e9f90e85a02856684a169a116239cb5c804a9eef69161b32851ac38222545090a26306
7
- data.tar.gz: b2ee56339a6e2eb7aa615becf7fd163fd86d66e165fb065a3f50e9cc89fd217a3be3628f7484491f90c866b8460b4f4eea0c487575de29693da928895307d059
6
+ metadata.gz: c94f44c0d12367b67bddef7c928fa27528a13eedddf4a1b99c4c3eec34553ee8a6c949dc43797e00ce13ba8a47f59b615f4779fd7375ea1d28e2dda1a1fc6058
7
+ data.tar.gz: 904580f268829d5699001cc260ed53c26def532e84e50bf6eb9b9e44c89daa5861482d77a24722b729d775c1b5dbb514812d4ff68ac0b242fbfbf873409baffc
@@ -1,5 +1,5 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.4.0
5
- before_install: gem install bundler -v 1.14.4
4
+ - 2.6.0
5
+ before_install: gem install bundler
data/README.md CHANGED
@@ -31,6 +31,16 @@ end
31
31
 
32
32
  See [opentracing-ruby](https://github.com/opentracing/opentracing-ruby) for more examples.
33
33
 
34
+ ### Samplers
35
+
36
+ #### Const sampler
37
+
38
+ `Const` sampler always makes the same decision for new traces depending on the initialization value. Set `sampler` to: `Zipkin::Samplers::Const.new(true)` to mark all new traces as sampled.
39
+
40
+ #### Probabilistic sampler
41
+
42
+ `Probabilistic` sampler samples traces with probability equal to `rate` (must be between 0.0 and 1.0). Set `sampler` to `Zipkin::Samplers::Probabilistic.new(rate: 0.1)` to mark 10% of new traces as sampled.
43
+
34
44
  ## Development
35
45
 
36
46
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ require_relative './async_reporter/buffer'
6
+
7
+ module Zipkin
8
+ class AsyncReporter
9
+ def self.create(sender:, flush_interval:)
10
+ reporter = new(sender)
11
+
12
+ # start flush thread
13
+ Thread.new do
14
+ loop do
15
+ reporter.flush
16
+ sleep(flush_interval)
17
+ end
18
+ end
19
+
20
+ reporter
21
+ end
22
+
23
+ def initialize(sender)
24
+ @sender = sender
25
+ @buffer = Buffer.new
26
+ end
27
+
28
+ def flush
29
+ spans = @buffer.retrieve
30
+ @sender.send_spans(spans) if spans.any?
31
+ spans
32
+ end
33
+
34
+ def report(span)
35
+ return unless span.context.sampled?
36
+ @buffer << span
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zipkin
4
+ class AsyncReporter
5
+ class Buffer
6
+ def initialize
7
+ @buffer = []
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def <<(element)
12
+ @mutex.synchronize do
13
+ @buffer << element
14
+ true
15
+ end
16
+ end
17
+
18
+ def retrieve
19
+ @mutex.synchronize do
20
+ elements = @buffer.dup
21
+ @buffer.clear
22
+ elements
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'encoders/json_encoder'
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ require_relative 'json_encoder/timestamp'
6
+ require_relative 'json_encoder/log_annotations'
7
+
8
+ module Zipkin
9
+ module Encoders
10
+ class JsonEncoder
11
+ OT_KIND_TO_ZIPKIN_KIND = {
12
+ 'server' => 'SERVER',
13
+ 'client' => 'CLIENT',
14
+ 'producer' => 'PRODUCER',
15
+ 'consumer' => 'CONSUMER'
16
+ }.freeze
17
+
18
+ CONTENT_TYPE = 'application/json'.freeze
19
+
20
+ module Fields
21
+ TRACE_ID = 'traceId'.freeze
22
+ SPAN_ID = 'id'.freeze
23
+ PARENT_ID = 'parentId'.freeze
24
+ OPERATION_NAME = 'name'.freeze
25
+ KIND = 'kind'.freeze
26
+ TIMESTAMP = 'timestamp'.freeze
27
+ DURATION = 'duration'.freeze
28
+ DEBUG = 'debug'.freeze
29
+ SHARED = 'shared'.freeze
30
+ LOCAL_ENDPOINT = 'localEndpoint'.freeze
31
+ REMOTE_ENDPOINT = 'remoteEndpoint'.freeze
32
+ ANNOTATIONS = 'annotations'.freeze
33
+ TAGS = 'tags'.freeze
34
+
35
+ module Endpoint
36
+ SERVICE_NAME = 'serviceName'.freeze
37
+ IPV4 = 'ipv4'.freeze
38
+ IPV6 = 'ipv6'.freeze
39
+ PORT = 'port'.freeze
40
+ end
41
+ end
42
+
43
+ def initialize(local_endpoint)
44
+ @adapter = defined?(Oj) ? OjAdapter : JsonAdapter
45
+ @local_endpoint = serialize_endpoint(local_endpoint)
46
+ end
47
+
48
+ def content_type
49
+ CONTENT_TYPE
50
+ end
51
+
52
+ def encode(spans)
53
+ @adapter.dump(spans.map(&method(:serialize)))
54
+ end
55
+
56
+ private
57
+
58
+ def serialize(span)
59
+ finish_ts = Timestamp.create(span.end_time)
60
+ start_ts = Timestamp.create(span.start_time)
61
+ duration = finish_ts - start_ts
62
+
63
+ {
64
+ Fields::TRACE_ID => span.context.trace_id,
65
+ Fields::SPAN_ID => span.context.span_id,
66
+ Fields::PARENT_ID => span.context.parent_id,
67
+ Fields::OPERATION_NAME => span.operation_name,
68
+ Fields::KIND => OT_KIND_TO_ZIPKIN_KIND[span.tags[:'span.kind'] || 'server'],
69
+ Fields::TIMESTAMP => start_ts,
70
+ Fields::DURATION => duration,
71
+ Fields::DEBUG => false,
72
+ Fields::SHARED => false,
73
+ Fields::LOCAL_ENDPOINT => @local_endpoint,
74
+ Fields::REMOTE_ENDPOINT => serialize_endpoint(Endpoint.remote_endpoint(span)),
75
+ Fields::ANNOTATIONS => LogAnnotations.build(span),
76
+ Fields::TAGS => span.tags
77
+ }
78
+ end
79
+
80
+ def serialize_endpoint(endpoint)
81
+ return nil unless endpoint
82
+
83
+ {
84
+ Fields::Endpoint::SERVICE_NAME => endpoint.service_name,
85
+ Fields::Endpoint::IPV4 => endpoint.ipv4,
86
+ Fields::Endpoint::IPV6 => endpoint.ipv6,
87
+ Fields::Endpoint::PORT => endpoint.port
88
+ }
89
+ end
90
+
91
+ module OjAdapter
92
+ def self.dump(payload)
93
+ Oj.dump(payload, mode: :object)
94
+ end
95
+ end
96
+
97
+ module JsonAdapter
98
+ def self.dump(payload)
99
+ JSON.dump(payload)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zipkin
4
+ module Encoders
5
+ class JsonEncoder
6
+ module LogAnnotations
7
+ module Fields
8
+ TIMESTAMP = 'timestamp'.freeze
9
+ VALUE = 'value'.freeze
10
+ end
11
+
12
+ def self.build(span)
13
+ span.logs.map do |log|
14
+ {
15
+ Fields::TIMESTAMP => Timestamp.create(log.fetch(:timestamp)),
16
+ Fields::VALUE => format_log_value(log)
17
+ }
18
+ end
19
+ end
20
+
21
+ def self.format_log_value(log)
22
+ if log.keys == %i[event timestamp]
23
+ log.fetch(:event)
24
+ else
25
+ log
26
+ .reject { |key, _value| key == :timestamp }
27
+ .map { |key, value| "#{key}=#{value}" }
28
+ .join(' ')
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zipkin
4
+ module Encoders
5
+ class JsonEncoder
6
+ module Timestamp
7
+ def self.create(time)
8
+ (time.to_f * 1_000_000).to_i
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -17,10 +17,10 @@ module Zipkin
17
17
  end
18
18
 
19
19
  module PeerInfo
20
- SERVICE = :'peer.service'
21
- IPV4 = :'peer.ipv4'
22
- IPV6 = :'peer.ipv6'
23
- PORT = :'peer.port'
20
+ SERVICE = 'peer.service'.freeze
21
+ IPV4 = 'peer.ipv4'.freeze
22
+ IPV6 = 'peer.ipv6'.freeze
23
+ PORT = 'peer.port'.freeze
24
24
 
25
25
  def self.keys
26
26
  [SERVICE, IPV4, IPV6, PORT]
@@ -28,34 +28,38 @@ module Zipkin
28
28
  end
29
29
 
30
30
  def self.local_endpoint(service_name)
31
- {
32
- serviceName: service_name,
33
- ipv4: LOCAL_IP
34
- }
31
+ new(service_name: service_name, ipv4: LOCAL_IP)
35
32
  end
36
33
 
37
34
  def self.remote_endpoint(span)
38
35
  tags = span.tags
39
- kind = tags[:'span.kind'] || SpanKind::SERVER
36
+ kind = tags['span.kind'] || SpanKind::SERVER
40
37
 
41
38
  case kind
42
39
  when SpanKind::SERVER, SpanKind::CLIENT
43
40
  return nil if (tags.keys & PeerInfo.keys).empty?
44
41
 
45
- {
46
- serviceName: tags[PeerInfo::SERVICE],
42
+ new(
43
+ service_name: tags[PeerInfo::SERVICE],
47
44
  ipv4: tags[PeerInfo::IPV4],
48
45
  ipv6: tags[PeerInfo::IPV6],
49
46
  port: tags[PeerInfo::PORT]
50
- }
47
+ )
51
48
  when SpanKind::PRODUCER, SpanKind::CONSUMER
52
- {
53
- serviceName: 'broker'
54
- }
49
+ new(service_name: 'broker')
55
50
  else
56
51
  warn "Unkown span kind: #{kind}"
57
52
  nil
58
53
  end
59
54
  end
55
+
56
+ def initialize(service_name: nil, ipv4: nil, ipv6: nil, port: nil)
57
+ @service_name = service_name
58
+ @ipv4 = ipv4
59
+ @ipv6 = ipv6
60
+ @port = port
61
+ end
62
+
63
+ attr_reader :service_name, :ipv4, :ipv6, :port
60
64
  end
61
65
  end
@@ -2,43 +2,23 @@
2
2
 
3
3
  require 'net/http'
4
4
  require 'uri'
5
- require 'json'
6
5
 
7
6
  module Zipkin
8
- class JsonClient
9
- def initialize(url:, collector:, flush_interval:, logger: Logger.new(STDOUT))
10
- @collector = collector
11
- @flush_interval = flush_interval
7
+ class HTTPClient
8
+ def initialize(url:, encoder:, logger:)
9
+ @encoder = encoder
12
10
  @spans_uri = URI.parse("#{url}/api/v2/spans")
13
11
  @logger = logger
14
12
  end
15
13
 
16
- def start
17
- @thread = Thread.new do
18
- loop do
19
- emit_batch(@collector.retrieve)
20
- sleep @flush_interval
21
- end
22
- end
23
- end
24
-
25
- def stop
26
- @thread.terminate if @thread
27
- emit_batch(@collector.retrieve)
28
- end
29
-
30
- private
31
-
32
- def emit_batch(spans)
33
- return if spans.empty?
34
-
14
+ def send_spans(spans)
35
15
  http = Net::HTTP.new(@spans_uri.host, @spans_uri.port)
36
16
  http.use_ssl = @spans_uri.scheme == 'https'
37
17
  request = Net::HTTP::Post.new(
38
18
  @spans_uri.request_uri,
39
- 'Content-Type' => 'application/json'
19
+ 'Content-Type' => @encoder.content_type
40
20
  )
41
- request.body = JSON.dump(spans)
21
+ request.body = @encoder.encode(spans)
42
22
  response = http.request(request)
43
23
 
44
24
  if response.code != '202'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'samplers/const'
4
+ require_relative 'samplers/probabilistic'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zipkin
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 `Zipkin::Samplers::Const.new(true)`
9
+ # to mark all new traces as sampled.
10
+ class Const
11
+ def initialize(decision)
12
+ @decision = decision
13
+ end
14
+
15
+ def sample?(*)
16
+ @decision
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zipkin
4
+ module Samplers
5
+ # Probabilistic sampler
6
+ #
7
+ # Sample a portion of traces using trace_id as the random decision
8
+ class Probabilistic
9
+ def initialize(rate: 0.001)
10
+ if rate < 0.0 || rate > 1.0
11
+ raise "Sampling rate must be between 0.0 and 1.0, got #{rate.inspect}"
12
+ end
13
+ @boundary = TraceId::TRACE_ID_UPPER_BOUND * rate
14
+ end
15
+
16
+ def sample?(trace_id:, **)
17
+ @boundary >= trace_id.to_i(16)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -4,30 +4,32 @@ module Zipkin
4
4
  class Span
5
5
  attr_accessor :operation_name
6
6
 
7
- attr_reader :context, :start_time, :tags, :logs, :references
7
+ attr_reader :context, :start_time, :end_time, :tags, :logs, :references
8
8
 
9
9
  # Creates a new {Span}
10
10
  #
11
11
  # @param context [SpanContext] the context of the span
12
12
  # @param operation_name [String] the operation name
13
- # @param collector [Collector] the span collector
13
+ # @param reporter [#report] the span reporter
14
14
  #
15
15
  # @return [Span] a new Span
16
16
  def initialize(
17
17
  context,
18
18
  operation_name,
19
- collector,
19
+ reporter,
20
20
  start_time: Time.now,
21
21
  tags: {},
22
22
  references: nil
23
23
  )
24
24
  @context = context
25
25
  @operation_name = operation_name
26
- @collector = collector
26
+ @reporter = reporter
27
27
  @start_time = start_time
28
- @tags = tags
28
+ @tags = {}
29
29
  @logs = []
30
30
  @references = references
31
+
32
+ tags.each(&method(:set_tag))
31
33
  end
32
34
 
33
35
  # Set a tag value on this span
@@ -37,7 +39,7 @@ module Zipkin
37
39
  # a String, Numeric, or Boolean it will be encoded with to_s
38
40
  def set_tag(key, value)
39
41
  sanitized_value = valid_tag_value?(value) ? value : value.to_s
40
- @tags = @tags.merge(key.to_sym => sanitized_value)
42
+ @tags = @tags.merge(key.to_s => sanitized_value)
41
43
  end
42
44
 
43
45
  # Set a baggage item on the span
@@ -78,16 +80,15 @@ module Zipkin
78
80
  #
79
81
  # @param end_time [Time] custom end time, if not now
80
82
  def finish(end_time: Time.now)
81
- @collector.send_span(self, end_time)
83
+ @end_time = end_time
84
+ @reporter.report(self)
82
85
  end
83
86
 
84
87
  private
85
88
 
89
+ # Zipkin supports only strings and numbers
86
90
  def valid_tag_value?(value)
87
- value.is_a?(String) ||
88
- value.is_a?(Numeric) ||
89
- value.is_a?(TrueClass) ||
90
- value.is_a?(FalseClass)
91
+ value.is_a?(String) || value.is_a?(Numeric)
91
92
  end
92
93
  end
93
94
  end
@@ -3,9 +3,10 @@
3
3
  module Zipkin
4
4
  # SpanContext holds the data for a span that gets inherited to child spans
5
5
  class SpanContext
6
- def self.create_parent_context
6
+ def self.create_parent_context(sampler = Samplers::Const.new(true))
7
7
  trace_id = TraceId.generate
8
- new(trace_id: trace_id, span_id: trace_id, sampled: true)
8
+ sampled = sampler.sample?(trace_id: trace_id)
9
+ new(trace_id: trace_id, span_id: trace_id, sampled: sampled)
9
10
  end
10
11
 
11
12
  def self.create_from_parent_context(span_context)
@@ -4,8 +4,28 @@ module Zipkin
4
4
  module TraceId
5
5
  TRACE_ID_UPPER_BOUND = 2**64
6
6
 
7
+ # Random number generator for generating IDs. This is an object that can
8
+ # respond to `#bytes` and uses the system PRNG. The current logic is
9
+ # compatible with Ruby 2.5 (which does not implement the `Random.bytes`
10
+ # class method) and with Ruby 3.0+ (which deprecates `Random::DEFAULT`).
11
+ # When we drop support for Ruby 2.5, this can simply be replaced with
12
+ # the class `Random`.
13
+ #
14
+ # @return [#bytes]
15
+ RANDOM = Random.respond_to?(:bytes) ? Random : Random::DEFAULT
16
+
17
+ # An invalid trace identifier, an 8-byte string with all zero bytes.
18
+ INVALID_TRACE_ID = ("\0" * 8).b
19
+
20
+ # Generates a valid trace identifier, an 8-byte string with at least one
21
+ # non-zero byte.
22
+ #
23
+ # @return [String] a valid trace ID.
7
24
  def self.generate
8
- rand(TRACE_ID_UPPER_BOUND).to_s(16)
25
+ loop do
26
+ id = RANDOM.bytes(8)
27
+ return id.unpack1('H*') if id != INVALID_TRACE_ID
28
+ end
9
29
  end
10
30
  end
11
31
  end
@@ -7,37 +7,36 @@ require_relative 'span'
7
7
  require_relative 'span_context'
8
8
  require_relative 'carrier'
9
9
  require_relative 'trace_id'
10
- require_relative 'json_client'
10
+ require_relative 'http_client'
11
11
  require_relative 'endpoint'
12
- require_relative 'collector'
12
+ require_relative 'async_reporter'
13
13
  require_relative 'scope_manager'
14
14
  require_relative 'scope'
15
+ require_relative 'samplers'
16
+ require_relative 'encoders'
15
17
 
16
18
  module Zipkin
17
19
  class Tracer
18
20
  DEFAULT_FLUSH_INTERVAL = 10
19
21
 
20
- def self.build(url:, service_name:, flush_interval: DEFAULT_FLUSH_INTERVAL, logger: Logger.new(STDOUT))
21
- collector = Collector.new(Endpoint.local_endpoint(service_name))
22
- sender = JsonClient.new(
23
- url: url,
24
- collector: collector,
25
- flush_interval: flush_interval,
26
- logger: logger
27
- )
28
- sender.start
29
- new(collector, sender, logger: logger)
22
+ def self.build(url:,
23
+ service_name:,
24
+ flush_interval: DEFAULT_FLUSH_INTERVAL,
25
+ logger: Logger.new(STDOUT),
26
+ sampler: Samplers::Const.new(true),
27
+ encoder: Encoders::JsonEncoder)
28
+ encoder = encoder.new(Endpoint.local_endpoint(service_name))
29
+ sender = HTTPClient.new(url: url, encoder: encoder, logger: logger)
30
+ reporter = AsyncReporter.create(sender: sender, flush_interval: flush_interval)
31
+ new(reporter, sender, logger: logger, sampler: sampler)
30
32
  end
31
33
 
32
- def initialize(collector, sender, logger: Logger.new(STDOUT))
33
- @collector = collector
34
+ def initialize(reporter, sender, logger: Logger.new(STDOUT), sampler:)
35
+ @reporter = reporter
34
36
  @sender = sender
35
37
  @logger = logger
36
38
  @scope_manager = ScopeManager.new
37
- end
38
-
39
- def stop
40
- @sender.stop
39
+ @sampler = sampler
41
40
  end
42
41
 
43
42
  # @return [ScopeManager] the current ScopeManager, which may be a no-op but
@@ -75,7 +74,7 @@ module Zipkin
75
74
  def start_span(operation_name,
76
75
  child_of: nil,
77
76
  start_time: Time.now,
78
- tags: {},
77
+ tags: nil,
79
78
  references: nil,
80
79
  ignore_active_scope: false,
81
80
  **)
@@ -87,10 +86,10 @@ module Zipkin
87
86
  Span.new(
88
87
  context,
89
88
  operation_name,
90
- @collector,
89
+ @reporter,
91
90
  start_time: start_time,
92
91
  references: references,
93
- tags: tags
92
+ tags: tags || {}
94
93
  )
95
94
  end
96
95
 
@@ -220,7 +219,7 @@ module Zipkin
220
219
  if context
221
220
  SpanContext.create_from_parent_context(context)
222
221
  else
223
- SpanContext.create_parent_context
222
+ SpanContext.create_parent_context(@sampler)
224
223
  end
225
224
  end
226
225
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler'
4
5
  Bundler.setup
@@ -7,8 +8,8 @@ require 'zipkin/tracer'
7
8
 
8
9
  url = ENV['ZIPKIN_URL'] || 'http://localhost:9411'
9
10
 
10
- tracer1 = Zipkin::Tracer.build(url: url, service_name: 'test-service')
11
- tracer2 = Zipkin::Tracer.build(url: url, service_name: 'downstream-service')
11
+ tracer1 = Zipkin::Tracer.build(url: url, service_name: 'test-service', flush_interval: 1)
12
+ tracer2 = Zipkin::Tracer.build(url: url, service_name: 'downstream-service', flush_interval: 1)
12
13
 
13
14
  rpc_span = tracer1.start_span(
14
15
  'receive request',
@@ -45,7 +46,4 @@ async_span = tracer2.start_span(
45
46
  sleep 0.3 # emulate network delay
46
47
  async_span.finish
47
48
 
48
- tracer1.stop
49
- tracer2.stop
50
-
51
49
  puts 'Finished'
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler'
5
+ Bundler.setup
6
+
7
+ require 'zipkin/tracer'
8
+
9
+ url = ENV['ZIPKIN_URL'] || 'http://localhost:9411'
10
+
11
+ tracer1 = Zipkin::Tracer.build(url: url, service_name: 'user-service', flush_interval: 1)
12
+ tracer2 = Zipkin::Tracer.build(url: url, service_name: 'name-service', flush_interval: 1)
13
+
14
+ root_span = tracer1.start_span(
15
+ 'GET /users/:id',
16
+ tags: {
17
+ 'span.kind' => 'server',
18
+ 'http.method' => 'get',
19
+ 'http.url' => 'http://example.com/users/5'
20
+ }
21
+ )
22
+ sleep 1
23
+
24
+ fetch_name_span = tracer1.start_span(
25
+ 'get',
26
+ child_of: root_span,
27
+ tags: {
28
+ 'span.kind' => 'client',
29
+ 'http.method' => 'get',
30
+ 'http.url' => 'GET /users/5/name'
31
+ }
32
+ )
33
+ sleep 0.3 # emulate network delay
34
+
35
+ name_service_span = tracer2.start_span(
36
+ 'GET /users/:id/name',
37
+ child_of: fetch_name_span,
38
+ tags: {
39
+ 'span.kind' => 'server',
40
+ 'http.method' => 'get',
41
+ 'http.url' => 'http://example.com/users/5/name'
42
+ }
43
+ )
44
+ sleep 0.5
45
+ name_service_span.finish
46
+
47
+ sleep 0.2 # emulate network delay
48
+ fetch_name_span.finish
49
+
50
+ sleep 0.1 # doing something with fetched info
51
+ root_span.finish
52
+
53
+ puts 'Finishing...'
54
+ sleep 3
55
+
56
+ puts 'Finished'
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler'
4
5
  Bundler.setup
@@ -7,10 +8,10 @@ require 'zipkin/tracer'
7
8
 
8
9
  url = ENV['ZIPKIN_URL'] || 'http://localhost:9411'
9
10
 
10
- tracer1 = Zipkin::Tracer.build(url: url, service_name: 'test-service')
11
- tracer2 = Zipkin::Tracer.build(url: url, service_name: 'downstream-service')
11
+ tracer1 = Zipkin::Tracer.build(url: url, service_name: 'test-service', flush_interval: 1)
12
+ tracer2 = Zipkin::Tracer.build(url: url, service_name: 'downstream-service-1', flush_interval: 1)
12
13
 
13
- outer_span = tracer1.start_span(
14
+ root_span = tracer1.start_span(
14
15
  'receive request',
15
16
  tags: {
16
17
  'span.kind' => 'server'
@@ -18,39 +19,52 @@ outer_span = tracer1.start_span(
18
19
  )
19
20
  sleep 1
20
21
 
21
- inner_span = tracer1.start_span(
22
- 'fetch info from downstream',
23
- child_of: outer_span,
22
+ fetch_info_from_service1_span = tracer1.start_span(
23
+ 'fetch info from service 1',
24
+ child_of: root_span,
24
25
  tags: {
25
26
  'span.kind' => 'client',
26
- 'peer.service' => 'downstream-service',
27
- 'peer.ipv4' => '6.6.6.6',
27
+ 'peer.ipv4' => '2.2.2.2',
28
28
  'peer.port' => 443
29
29
  }
30
30
  )
31
31
  sleep 0.3 # emulate network delay
32
32
 
33
- downstream_span = tracer2.start_span(
34
- 'downstream operation',
35
- child_of: inner_span,
36
- tags: { 'span.kind' => 'server' }
33
+ service1_span = tracer2.start_span(
34
+ 'service 1 operation',
35
+ child_of: fetch_info_from_service1_span,
36
+ tags: {
37
+ 'span.kind' => 'server',
38
+ 'peer.ipv4' => '1.1.1.1',
39
+ 'peer.port' => 443
40
+ }
37
41
  )
38
- downstream_span.log_kv(event: 'custom-event')
39
- downstream_span.log_kv(foo: 'bar', baz: 'buz')
42
+ service1_span.log_kv(event: 'custom-event')
43
+ service1_span.log_kv(foo: 'bar', baz: 'buz')
40
44
  sleep 0.5
41
- downstream_span.finish
45
+ service1_span.finish
42
46
 
43
47
  sleep 0.2 # emulate network delay
44
48
 
45
- inner_span.finish
49
+ fetch_info_from_service1_span.finish
50
+
51
+ fetch_info_from_service2_span = tracer1.start_span(
52
+ 'fetch info from service 2 which does not have tracing',
53
+ child_of: root_span,
54
+ tags: {
55
+ 'span.kind' => 'client',
56
+ 'peer.service' => 'downstream-service-2',
57
+ 'peer.ipv4' => '3.3.3.3',
58
+ 'peer.port' => 443
59
+ }
60
+ )
61
+ sleep 0.3 # emulate network delay
62
+ fetch_info_from_service2_span.finish
46
63
 
47
64
  sleep 0.1 # doing something with fetched info
48
- outer_span.finish
65
+ root_span.finish
49
66
 
50
67
  puts 'Finishing...'
51
68
  sleep 3
52
69
 
53
- tracer1.stop
54
- tracer2.stop
55
-
56
70
  puts 'Finished'
@@ -3,13 +3,13 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = 'zipkin'
6
- spec.version = '1.5.0'
6
+ spec.version = '1.6.2'
7
7
  spec.authors = ['SaleMove TechMovers']
8
8
  spec.email = ['techmovers@salemove.com']
9
9
 
10
10
  spec.summary = 'OpenTracing Tracer implementation for Zipkin in Ruby'
11
11
  spec.description = ''
12
- spec.homepage = ''
12
+ spec.homepage = 'https://github.com/salemove/zipkin-ruby-opentracing'
13
13
  spec.license = 'MIT'
14
14
 
15
15
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency 'bundler', '~> 1.14'
21
+ spec.add_development_dependency 'bundler', '~> 2.0'
22
22
  spec.add_development_dependency 'rake', '~> 10.0'
23
23
  spec.add_development_dependency 'rspec', '~> 3.0'
24
24
  spec.add_development_dependency 'rubocop', '~> 0.54.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zipkin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - SaleMove TechMovers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-01 00:00:00.000000000 Z
11
+ date: 2021-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.14'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.14'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -126,12 +126,18 @@ files:
126
126
  - bin/console
127
127
  - bin/setup
128
128
  - lib/zipkin.rb
129
+ - lib/zipkin/async_reporter.rb
130
+ - lib/zipkin/async_reporter/buffer.rb
129
131
  - lib/zipkin/carrier.rb
130
- - lib/zipkin/collector.rb
131
- - lib/zipkin/collector/log_annotations.rb
132
- - lib/zipkin/collector/timestamp.rb
132
+ - lib/zipkin/encoders.rb
133
+ - lib/zipkin/encoders/json_encoder.rb
134
+ - lib/zipkin/encoders/json_encoder/log_annotations.rb
135
+ - lib/zipkin/encoders/json_encoder/timestamp.rb
133
136
  - lib/zipkin/endpoint.rb
134
- - lib/zipkin/json_client.rb
137
+ - lib/zipkin/http_client.rb
138
+ - lib/zipkin/samplers.rb
139
+ - lib/zipkin/samplers/const.rb
140
+ - lib/zipkin/samplers/probabilistic.rb
135
141
  - lib/zipkin/scope.rb
136
142
  - lib/zipkin/scope_manager.rb
137
143
  - lib/zipkin/scope_manager/scope_identifier.rb
@@ -141,9 +147,10 @@ files:
141
147
  - lib/zipkin/trace_id.rb
142
148
  - lib/zipkin/tracer.rb
143
149
  - script/create_follows_from_trace
150
+ - script/create_http_trace
144
151
  - script/create_trace
145
152
  - zipkin.gemspec
146
- homepage: ''
153
+ homepage: https://github.com/salemove/zipkin-ruby-opentracing
147
154
  licenses:
148
155
  - MIT
149
156
  metadata: {}
@@ -162,8 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
169
  - !ruby/object:Gem::Version
163
170
  version: '0'
164
171
  requirements: []
165
- rubyforge_project:
166
- rubygems_version: 2.6.11
172
+ rubygems_version: 3.0.3
167
173
  signing_key:
168
174
  specification_version: 4
169
175
  summary: OpenTracing Tracer implementation for Zipkin in Ruby
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'thread'
4
-
5
- require_relative './collector/timestamp'
6
- require_relative './collector/log_annotations'
7
-
8
- module Zipkin
9
- class Collector
10
- OT_KIND_TO_ZIPKIN_KIND = {
11
- 'server' => 'SERVER',
12
- 'client' => 'CLIENT',
13
- 'producer' => 'PRODUCER',
14
- 'consumer' => 'CONSUMER'
15
- }.freeze
16
-
17
- def initialize(local_endpoint)
18
- @buffer = Buffer.new
19
- @local_endpoint = local_endpoint
20
- end
21
-
22
- def retrieve
23
- @buffer.retrieve
24
- end
25
-
26
- def send_span(span, end_time)
27
- finish_ts = Timestamp.create(end_time)
28
- start_ts = Timestamp.create(span.start_time)
29
- duration = finish_ts - start_ts
30
- return unless span.context.sampled?
31
-
32
- @buffer << {
33
- traceId: span.context.trace_id,
34
- id: span.context.span_id,
35
- parentId: span.context.parent_id,
36
- name: span.operation_name,
37
- kind: OT_KIND_TO_ZIPKIN_KIND[span.tags[:'span.kind'] || 'server'],
38
- timestamp: start_ts,
39
- duration: duration,
40
- debug: false,
41
- shared: false,
42
- localEndpoint: @local_endpoint,
43
- remoteEndpoint: Endpoint.remote_endpoint(span),
44
- annotations: LogAnnotations.build(span),
45
- tags: span.tags
46
- }
47
- end
48
-
49
- class Buffer
50
- def initialize
51
- @buffer = []
52
- @mutex = Mutex.new
53
- end
54
-
55
- def <<(element)
56
- @mutex.synchronize do
57
- @buffer << element
58
- true
59
- end
60
- end
61
-
62
- def retrieve
63
- @mutex.synchronize do
64
- elements = @buffer.dup
65
- @buffer.clear
66
- elements
67
- end
68
- end
69
- end
70
- end
71
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Zipkin
4
- class Collector
5
- module LogAnnotations
6
- def self.build(span)
7
- span.logs.map do |log|
8
- {
9
- timestamp: Timestamp.create(log.fetch(:timestamp)),
10
- value: format_log_value(log)
11
- }
12
- end
13
- end
14
-
15
- def self.format_log_value(log)
16
- if log.keys == %i[event timestamp]
17
- log.fetch(:event)
18
- else
19
- log
20
- .reject { |key, _value| key == :timestamp }
21
- .map { |key, value| "#{key}=#{value}" }
22
- .join(' ')
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Zipkin
4
- class Collector
5
- module Timestamp
6
- def self.create(time)
7
- (time.to_f * 1_000_000).to_i
8
- end
9
- end
10
- end
11
- end