freddy 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 55a9bea9337f7d20d2e713bd012ed8f216fef328
4
- data.tar.gz: 2c49767c9226fdf1b882cf221cec8ff7726203f9
3
+ metadata.gz: 780979fe0cb5c2d61b74d2356643695dc3380e1d
4
+ data.tar.gz: 7778664a870a3b04ea9b0d697445ec2843a4a98a
5
5
  SHA512:
6
- metadata.gz: 7d3c30c1d5d54c72539821bc465f0887371ee30fdcbed3fa4711ee3b9f65137922025a4aa6956ca862b08b851d5bc8bff2d4aac7279a80250475c45a90e475ab
7
- data.tar.gz: 13f606726903475c3619ee4f7dd1d18ebe257cbb48844cb0831cf81809b468eeb7143c4d4519b8cd1e6fa00e9d4703dcf9ce1282ebf70d4867419c85a89f42ae
6
+ metadata.gz: dd21e0ca6b7140f2096db0062f8793941928bab1f8f23b0c6c6f0e1a5641c2a244a7d624a543fae642d38d1af9f0b4cd6a736d77195d355699ef582135a68c39
7
+ data.tar.gz: ce44daf3488a6b7805d179f0176fb0b71a2080836526660a5482665a854c6e0bc214ac7a09e7c7fa4b5426a52dd65a2da253c4e3996c69420a275d91a2de56fe
data/.travis.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.0
4
- - jruby-9.0.4.0
3
+ - 2.4.1
4
+ - jruby-9.1.5.0
5
5
  addons:
6
6
  code_climate:
7
7
  repo_token: 1f3842b985fdeff6a36168165d491ca5f444667e9381a85c899a61706a9dd285
data/Gemfile CHANGED
@@ -4,5 +4,6 @@ gem 'rspec'
4
4
  gem 'pry'
5
5
  gem 'codeclimate-test-reporter'
6
6
  gem 'hamster', '~> 3.0'
7
+ gem 'logasm-tracer', '0.2.0'
7
8
 
8
9
  gemspec
data/README.md CHANGED
@@ -137,33 +137,39 @@ responder_handler.shutdown
137
137
 
138
138
  ## Request Tracing
139
139
 
140
- Freddy helps application developers to implement distributed tracing. Freddy provides trace ID, parent ID and span ID when receiving a request. These can be accessed through a thread-local variable `Freddy.trace`. Calling `deliver` or `deliver_with_response` will pass these IDs to the next service.
140
+ Freddy supports [OpenTracing API|https://github.com/opentracing/opentracing-ruby]. You must set a global tracer which then freddy will use:
141
+ ```ruby
142
+ OpenTracing.global_tracing = MyTracerImplementation.new(...)
143
+ ```
144
+
145
+ For example you can use [Logasm::Tracer](https://github.com/salemove/logasm-tracer) which will log all incoming and outgoing requests with trace ID, parent ID and span ID.
146
+
147
+ Current trace can be accessed through a thread-local variable `Freddy.trace`. Calling `deliver` or `deliver_with_response` will pass trace context to down-stream services.
148
+
149
+ Accessing trace information when using Logasm::Tracer implementation:
141
150
 
142
151
  ```ruby
143
152
  freddy1 = Freddy.build
144
153
  freddy1.respond_do('service1') do |payload, msg_handler|
145
- puts "Trace id: #{Freddy.trace.id}"
146
- puts "Span id: #{Freddy.trace.span_id}"
154
+ puts "Trace id: #{Freddy.trace.context.trace_id}"
155
+ puts "Span id: #{Freddy.trace.context.span_id}"
147
156
 
148
157
  freddy1.deliver('service2', {})
149
158
  end
150
159
 
151
160
  freddy2 = Freddy.build
152
161
  freddy2.tap_into('service2') do |payload|
153
- puts "Has same trace_id as the request in service1: #{Freddy.trace.id}"
154
- puts "Has service1 request span id as parent id: #{Freddy.trace.parent_id}"
155
- puts "Has its own generated span id: #{Freddy.trace.span_id}"
162
+ puts "Has same trace_id as the request in service1: #{Freddy.trace.context.trace_id}"
163
+ puts "Has service1 request span id as parent id: #{Freddy.trace.context.parent_id}"
164
+ puts "Has its own generated span id: #{Freddy.trace.context.span_id}"
156
165
  end
157
166
  ```
158
167
 
159
- In case you already have trace IDs (e.g. provided by REST API) then you can set them yourself:
168
+ In case you already have an ongoing OpenTracing span (e.g. provided by REST API) then you can pass the trace information to Freddy by doing:
160
169
  ```ruby
161
- Freddy.trace = Freddy::Traces::Trace.new({
162
- id: 'YourTraceId',
163
- parent_id: 'YourParentId', # Use nil in case this is the root of the trace
164
- span_id: 'YourSpanId'
165
- })
170
+ Freddy.trace = trace_span
166
171
  ```
172
+ The `trace_span` must implement OpenTracing::Span interface.
167
173
 
168
174
  ## Notes about concurrency
169
175
 
data/freddy.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  else
9
9
  spec.name = "freddy"
10
10
  end
11
- spec.version = '1.1.0'
11
+ spec.version = '1.2.0'
12
12
  spec.authors = ["Salemove TechMovers"]
13
13
  spec.email = ["techmovers@salemove.com"]
14
14
  spec.description = %q{Messaging API}
@@ -32,4 +32,5 @@ Gem::Specification.new do |spec|
32
32
  end
33
33
 
34
34
  spec.add_dependency "thread", "~> 0.1"
35
+ spec.add_dependency "opentracing", "~> 0.3"
35
36
  end
data/lib/freddy.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'json'
2
2
  require 'thread/pool'
3
3
  require 'securerandom'
4
+ require 'opentracing'
4
5
 
5
6
  Dir[File.dirname(__FILE__) + '/freddy/*.rb'].each(&method(:require))
6
7
 
@@ -25,13 +26,14 @@ class Freddy
25
26
  # @example
26
27
  # Freddy.build(Logger.new(STDOUT), user: 'thumper', pass: 'howdy')
27
28
  def self.build(logger = Logger.new(STDOUT), max_concurrency: DEFAULT_MAX_CONCURRENCY, **config)
28
- connection = Adapters.determine.connect(config)
29
+ OpenTracing.global_tracer ||= OpenTracing::Tracer.new
29
30
 
31
+ connection = Adapters.determine.connect(config)
30
32
  new(connection, logger, max_concurrency)
31
33
  end
32
34
 
33
35
  def self.trace
34
- Thread.current[:freddy_trace] || Traces::NO_TRACE
36
+ Thread.current[:freddy_trace]
35
37
  end
36
38
 
37
39
  def self.trace=(trace)
@@ -84,7 +86,6 @@ class Freddy
84
86
  handler_adapter_factory = MessageHandlerAdapters::Factory.new(producer)
85
87
 
86
88
  Consumers::RespondToConsumer.consume(
87
- logger: @logger,
88
89
  thread_pool: Thread.pool(@prefetch_buffer_size),
89
90
  destination: destination,
90
91
  channel: channel,
@@ -119,7 +120,6 @@ class Freddy
119
120
  @logger.debug "Tapping into messages that match #{pattern}"
120
121
 
121
122
  Consumers::TapIntoConsumer.consume(
122
- logger: @logger,
123
123
  thread_pool: Thread.pool(@prefetch_buffer_size),
124
124
  pattern: pattern,
125
125
  channel: @connection.create_channel(prefetch: @prefetch_buffer_size),
@@ -1,15 +1 @@
1
- class Freddy
2
- module Consumers
3
- def self.log_receive_event(logger, queue_name, delivery)
4
- logger.debug(
5
- message: 'Received message',
6
- queue: queue_name,
7
- payload: delivery.payload,
8
- correlation_id: delivery.correlation_id,
9
- trace: Freddy.trace.to_h
10
- )
11
- end
12
- end
13
- end
14
-
15
1
  Dir[File.dirname(__FILE__) + '/consumers/*.rb'].each(&method(:require))
@@ -5,8 +5,7 @@ class Freddy
5
5
  new(*attrs).consume(&block)
6
6
  end
7
7
 
8
- def initialize(logger:, thread_pool:, destination:, channel:, handler_adapter_factory:)
9
- @logger = logger
8
+ def initialize(thread_pool:, destination:, channel:, handler_adapter_factory:)
10
9
  @consume_thread_pool = thread_pool
11
10
  @destination = destination
12
11
  @channel = channel
@@ -15,8 +14,6 @@ class Freddy
15
14
 
16
15
  def consume(&block)
17
16
  consumer = consume_from_destination do |delivery|
18
- Consumers.log_receive_event(@logger, @destination, delivery)
19
-
20
17
  adapter = @handler_adapter_factory.for(delivery)
21
18
 
22
19
  msg_handler = MessageHandler.new(adapter, delivery)
@@ -37,10 +34,18 @@ class Freddy
37
34
  def process_message(delivery, &block)
38
35
  @consume_thread_pool.process do
39
36
  begin
40
- Freddy.trace = delivery.trace
37
+ Freddy.trace = delivery.build_trace("freddy:respond:#{@destination}")
38
+ Freddy.trace.log(
39
+ event: 'Received message through respond_to',
40
+ queue: @destination,
41
+ payload: delivery.payload,
42
+ correlation_id: delivery.correlation_id
43
+ )
44
+
41
45
  block.call(delivery)
42
46
  ensure
43
47
  @channel.acknowledge(delivery.tag, false)
48
+ Freddy.trace.finish
44
49
  Freddy.trace = nil
45
50
  end
46
51
  end
@@ -8,16 +8,9 @@ class Freddy
8
8
  def consume(channel, queue, &block)
9
9
  @logger.debug "Consuming messages on #{queue.name}"
10
10
  queue.subscribe do |delivery|
11
- process_message(channel, queue, delivery, &block)
11
+ block.call(delivery)
12
12
  end
13
13
  end
14
-
15
- private
16
-
17
- def process_message(channel, queue, delivery, &block)
18
- Consumers.log_receive_event(@logger, queue.name, delivery)
19
- block.call(delivery)
20
- end
21
14
  end
22
15
  end
23
16
  end
@@ -5,8 +5,7 @@ class Freddy
5
5
  new(*attrs).consume(&block)
6
6
  end
7
7
 
8
- def initialize(logger:, thread_pool:, pattern:, channel:, options:)
9
- @logger = logger
8
+ def initialize(thread_pool:, pattern:, channel:, options:)
10
9
  @consume_thread_pool = thread_pool
11
10
  @pattern = pattern
12
11
  @channel = channel
@@ -43,11 +42,20 @@ class Freddy
43
42
  def process_message(queue, delivery, &block)
44
43
  @consume_thread_pool.process do
45
44
  begin
46
- Consumers.log_receive_event(@logger, queue.name, delivery)
47
- Freddy.trace = delivery.trace
45
+ Freddy.trace = delivery.build_trace("freddy:observe:#{@pattern}",
46
+ tags: {queue: @pattern},
47
+ force_follows_from: true
48
+ )
49
+ Freddy.trace.log(
50
+ event: 'Received message through tap_into',
51
+ payload: delivery.payload,
52
+ correlation_id: delivery.correlation_id
53
+ )
54
+
48
55
  block.call delivery.payload, delivery.routing_key
49
56
  ensure
50
57
  @channel.acknowledge(delivery.tag, false)
58
+ Freddy.trace.finish
51
59
  Freddy.trace = nil
52
60
  end
53
61
  end
@@ -1,13 +1,12 @@
1
1
  class Freddy
2
2
  class Delivery
3
- attr_reader :routing_key, :payload, :tag, :trace
3
+ attr_reader :routing_key, :payload, :tag
4
4
 
5
5
  def initialize(payload, metadata, routing_key, tag)
6
6
  @payload = payload
7
7
  @metadata = metadata
8
8
  @routing_key = routing_key
9
9
  @tag = tag
10
- @trace = build_trace(metadata.headers || {})
11
10
  end
12
11
 
13
12
  def correlation_id
@@ -22,17 +21,25 @@ class Freddy
22
21
  @metadata.reply_to
23
22
  end
24
23
 
24
+ def build_trace(operation_name, tags: {}, force_follows_from: false)
25
+ carrier = TraceCarrier.new(@metadata)
26
+ parent =
27
+ if carrier.has_required_fields? && expecting_response? && !force_follows_from
28
+ OpenTracing.global_tracer.extract(OpenTracing::FORMAT_TEXT_MAP, carrier)
29
+ else
30
+ nil
31
+ end
32
+
33
+ # Creating a child span when the message sender is expecting a response.
34
+ # Otherwise creating a new trace because the OpenTracing client does not
35
+ # support FollowsFrom yet.
36
+ OpenTracing.start_span(operation_name, child_of: parent, tags: tags)
37
+ end
38
+
25
39
  private
26
40
 
27
- def build_trace(headers)
28
- if headers['x-trace-id'] && headers['x-span-id']
29
- Traces::Trace.build_from_existing_trace(
30
- id: headers['x-trace-id'],
31
- parent_id: headers['x-span-id']
32
- )
33
- else
34
- Traces::Trace.build
35
- end
41
+ def expecting_response?
42
+ type == 'request'
36
43
  end
37
44
  end
38
45
  end
@@ -1,9 +1 @@
1
- class Freddy
2
- module Producers
3
- def self.log_send_event(logger, payload, destination)
4
- logger.debug message: 'Sending message', queue: destination, payload: payload, trace: Freddy.trace.to_h
5
- end
6
- end
7
- end
8
-
9
1
  Dir[File.dirname(__FILE__) + '/producers/*.rb'].each(&method(:require))
@@ -9,15 +9,11 @@ class Freddy
9
9
  end
10
10
 
11
11
  def produce(destination, payload, properties)
12
- Producers.log_send_event(@logger, payload, destination)
12
+ Freddy.trace.log event: 'Sending response', queue: destination, payload: payload
13
13
 
14
14
  properties = properties.merge(
15
15
  routing_key: destination,
16
- content_type: CONTENT_TYPE,
17
- headers: {
18
- 'x-trace-id' => Freddy.trace.id,
19
- 'x-span-id' => Freddy.trace.span_id
20
- }
16
+ content_type: CONTENT_TYPE
21
17
  )
22
18
 
23
19
  @exchange.publish Payload.dump(payload), properties
@@ -10,22 +10,29 @@ class Freddy
10
10
  end
11
11
 
12
12
  def produce(destination, payload, properties)
13
- Producers.log_send_event(@logger, payload, destination)
13
+ span = OpenTracing.start_span("freddy:notify:#{destination}",
14
+ child_of: Freddy.trace,
15
+ tags: {queue: destination}
16
+ )
17
+ span.log event: 'Sending message', payload: payload
14
18
 
15
19
  properties = properties.merge(
16
20
  routing_key: destination,
17
- content_type: CONTENT_TYPE,
18
- headers: {
19
- 'x-trace-id' => Freddy.trace.id,
20
- 'x-span-id' => Freddy.trace.span_id
21
- }
21
+ content_type: CONTENT_TYPE
22
22
  )
23
+ OpenTracing.global_tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, TraceCarrier.new(properties))
23
24
  json_payload = Payload.dump(payload)
24
25
 
25
26
  # Connection adapters handle thread safety for #publish themselves. No
26
27
  # need to lock these.
27
28
  @topic_exchange.publish json_payload, properties.dup
28
29
  @exchange.publish json_payload, properties.dup
30
+ ensure
31
+ # We don't know how many listeners there are and we do not know when
32
+ # this message gets processed. Instead we close the span immediately.
33
+ # Listeners should use FollowsFrom to add trace information.
34
+ # https://github.com/opentracing/specification/blob/master/specification.md
35
+ span.finish
29
36
  end
30
37
  end
31
38
  end
@@ -23,14 +23,20 @@ class Freddy
23
23
  end
24
24
 
25
25
  def produce(destination, payload, timeout_in_seconds:, delete_on_timeout:, **properties)
26
+ span = OpenTracing.start_span("freddy:request:#{destination}",
27
+ child_of: Freddy.trace,
28
+ tags: {queue: destination}
29
+ )
30
+
26
31
  correlation_id = SecureRandom.uuid
27
32
 
28
33
  container = SyncResponseContainer.new(
29
- on_timeout(correlation_id, destination, timeout_in_seconds)
34
+ on_timeout(correlation_id, destination, timeout_in_seconds, span)
30
35
  )
31
36
 
32
37
  @request_manager.store(correlation_id,
33
38
  callback: container,
39
+ trace: span,
34
40
  destination: destination
35
41
  )
36
42
 
@@ -41,21 +47,16 @@ class Freddy
41
47
  properties = properties.merge(
42
48
  routing_key: destination, content_type: CONTENT_TYPE,
43
49
  correlation_id: correlation_id, reply_to: @response_queue.name,
44
- mandatory: true, type: 'request',
45
- headers: {
46
- 'x-trace-id' => Freddy.trace.id,
47
- 'x-span-id' => Freddy.trace.span_id
48
- }
50
+ mandatory: true, type: 'request'
49
51
  )
52
+ OpenTracing.global_tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, TraceCarrier.new(properties))
50
53
  json_payload = Payload.dump(payload)
51
54
 
52
- @logger.debug(
53
- message: 'Publishing request',
54
- queue: destination,
55
+ span.log(
56
+ event: 'Publishing request',
55
57
  payload: payload,
56
58
  response_queue: @response_queue.name,
57
- correlation_id: correlation_id,
58
- trace: Freddy.trace.to_h
59
+ correlation_id: correlation_id
59
60
  )
60
61
 
61
62
  # Connection adapters handle thread safety for #publish themselves. No
@@ -82,14 +83,17 @@ class Freddy
82
83
  @logger.debug "Got response for request to #{request[:destination]} "\
83
84
  "with correlation_id #{delivery.correlation_id}"
84
85
  request[:callback].call(delivery.payload, delivery)
86
+ ensure
87
+ request[:trace].finish
85
88
  end
86
89
 
87
- def on_timeout(correlation_id, destination, timeout_in_seconds)
90
+ def on_timeout(correlation_id, destination, timeout_in_seconds, trace)
88
91
  Proc.new do
89
92
  @logger.warn "Request timed out waiting response from #{destination}"\
90
93
  ", correlation id #{correlation_id}"
91
94
 
92
95
  @request_manager.delete(correlation_id)
96
+ trace.finish
93
97
  end
94
98
  end
95
99
  end
@@ -0,0 +1,30 @@
1
+ class Freddy
2
+ # Carrier for rabbitmq following OpenTracing API
3
+ # See https://github.com/opentracing/opentracing-ruby/blob/master/lib/opentracing/carrier.rb
4
+ class TraceCarrier
5
+ def initialize(properties)
6
+ @properties = properties
7
+ end
8
+
9
+ def [](key)
10
+ @properties.headers && @properties.headers["x-trace-#{key}"]
11
+ end
12
+
13
+ def []=(key, value)
14
+ @properties[:headers] ||= {}
15
+ @properties[:headers]["x-trace-#{key}"] = value
16
+ end
17
+
18
+ def each(&block)
19
+ Hash[
20
+ (@properties.headers || {})
21
+ .select {|key, _| key =~ /^x-trace/}
22
+ .map {|key, value| [key.sub(/x-trace-/, ''), value]}
23
+ ].each(&block)
24
+ end
25
+
26
+ def has_required_fields?
27
+ self['trace-id'] && self['parent-id'] && self['span-id']
28
+ end
29
+ end
30
+ end
@@ -3,7 +3,6 @@ require 'spec_helper'
3
3
  describe Freddy::Consumers::RespondToConsumer do
4
4
  let(:consumer) do
5
5
  described_class.new(
6
- logger: logger,
7
6
  thread_pool: thread_pool,
8
7
  destination: destination,
9
8
  channel: channel,
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Freddy::TraceCarrier do
4
+ subject(:carrier) { described_class.new(properties) }
5
+
6
+ context 'when adding trace information' do
7
+ let(:properties) { {x: 'y'} }
8
+ let(:key_name) { 'some-key' }
9
+ let(:key_value) { 'some-key' }
10
+
11
+ it 'adds a header with x-trace- prefix' do
12
+ carrier[key_name] = key_value
13
+ expect(properties[:headers]["x-trace-#{key_name}"]).to eq(key_value)
14
+ end
15
+ end
16
+
17
+ context 'when extracting trace information' do
18
+ let(:key_name) { 'some-key' }
19
+ let(:serialized_key_name) { "x-trace-#{key_name}" }
20
+ let(:key_value) { 'some-key' }
21
+
22
+ let(:properties) do
23
+ double(headers: {serialized_key_name => key_value})
24
+ end
25
+
26
+ it 'extracts a header with x-trace- prefix' do
27
+ expect(carrier[key_name]).to eq(key_value)
28
+ end
29
+ end
30
+
31
+ describe '#each' do
32
+ context 'when headers are present' do
33
+ let(:properties) do
34
+ double(
35
+ headers: {
36
+ "x-trace-key1" => "value1",
37
+ "x-trace-key2" => "value2",
38
+ "other-key" => "value3"
39
+ }
40
+ )
41
+ end
42
+
43
+ it 'iterates over keys starting with x-trace- prefix' do
44
+ expect(carrier.each.count).to eq(2)
45
+ end
46
+ end
47
+
48
+ context 'when no headers' do
49
+ let(:properties) { double(headers: nil) }
50
+
51
+ it 'iterates over an empty list' do
52
+ expect(carrier.each.count).to eq(0)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,46 +1,98 @@
1
1
  require 'spec_helper'
2
+ require 'logasm/tracer'
2
3
 
3
- describe 'Logging' do
4
- let(:freddy1) { Freddy.build(logger1, config) }
5
- let(:freddy2) { Freddy.build(logger2, config) }
4
+ describe 'Logging with Logasm::Tracer' do
5
+ let(:logger) { ArrayLogger.new }
6
+ let(:tracer) { Logasm::Tracer.new(logger) }
6
7
 
7
- let(:logger1) { spy('logger1') }
8
- let(:logger2) { spy('logger2') }
8
+ before { OpenTracing.global_tracer = tracer }
9
+ after { OpenTracing.global_tracer = nil }
9
10
 
10
- let(:destination) { random_destination }
11
- let(:payload) { {pay: 'load'} }
11
+ context 'when receiving an untraced request' do
12
+ let(:freddy) { Freddy.build(spy, config) }
13
+ let(:destination) { random_destination }
12
14
 
13
- after { [freddy1, freddy2].each(&:close) }
14
-
15
- before do
16
- freddy1.respond_to destination do |payload, msg_handler|
17
- msg_handler.success
15
+ before do
16
+ freddy.respond_to(destination) do |payload, msg_handler|
17
+ msg_handler.success({})
18
+ end
18
19
  end
19
20
 
20
- freddy2.deliver_with_response(destination, payload) { }
21
- default_sleep
22
- end
21
+ after { freddy.close }
22
+
23
+ it 'generates a trace' do
24
+ freddy.deliver_with_response(destination, {})
25
+
26
+ expect(logger.calls.map(&:first)).to eq([
27
+ # Initiator
28
+ "Span [freddy:request:#{destination}] started",
29
+ "Span [freddy:request:#{destination}] Publishing request",
30
+
31
+ # Service
32
+ "Span [freddy:respond:#{destination}] started",
33
+ "Span [freddy:respond:#{destination}] Received message through respond_to",
34
+ "Span [freddy:respond:#{destination}] Sending response",
35
+ "Span [freddy:respond:#{destination}] finished",
23
36
 
24
- it 'logs all consumed messages' do
25
- expect(logger1).to have_received(:info).with(/Listening for requests on \S+/)
26
- expect(logger1).to have_received(:debug).with(
27
- message: 'Received message',
28
- queue: destination,
29
- payload: payload,
30
- correlation_id: anything,
31
- trace: anything
32
- )
37
+ # Initiator
38
+ "Span [freddy:request:#{destination}] finished"
39
+ ])
40
+ end
33
41
  end
34
42
 
35
- it 'logs all produced messages' do
36
- expect(logger2).to have_received(:debug).with(/Consuming messages on \S+/)
37
- expect(logger2).to have_received(:debug).with(
38
- message: 'Publishing request',
39
- queue: destination,
40
- payload: payload,
41
- response_queue: anything,
42
- correlation_id: anything,
43
- trace: anything
44
- )
43
+ context 'when receiving a traced request' do
44
+ let(:freddy) { Freddy.build(spy, config) }
45
+ let(:freddy2) { Freddy.build(spy, config) }
46
+
47
+ let(:destination) { random_destination }
48
+ let(:destination2) { random_destination }
49
+
50
+ before do
51
+ freddy.respond_to(destination) do |payload, msg_handler|
52
+ msg_handler.success({
53
+ trace_initiator: {},
54
+ current_receiver: freddy.deliver_with_response(destination2, {})
55
+ })
56
+ end
57
+
58
+ freddy2.respond_to(destination2) do |payload, msg_handler|
59
+ msg_handler.success({})
60
+ end
61
+ end
62
+
63
+ after do
64
+ freddy.close
65
+ freddy2.close
66
+ end
67
+
68
+ it 'generates a trace' do
69
+ freddy.deliver_with_response(destination, {})
70
+
71
+ expect(logger.calls.map(&:first)).to eq([
72
+ # Initiator
73
+ "Span [freddy:request:#{destination}] started",
74
+ "Span [freddy:request:#{destination}] Publishing request",
75
+
76
+ # Service 1
77
+ "Span [freddy:respond:#{destination}] started",
78
+ "Span [freddy:respond:#{destination}] Received message through respond_to",
79
+ "Span [freddy:request:#{destination2}] started",
80
+ "Span [freddy:request:#{destination2}] Publishing request",
81
+
82
+ # Service 2
83
+ "Span [freddy:respond:#{destination2}] started",
84
+ "Span [freddy:respond:#{destination2}] Received message through respond_to",
85
+ "Span [freddy:respond:#{destination2}] Sending response",
86
+ "Span [freddy:respond:#{destination2}] finished",
87
+
88
+ # Service 1
89
+ "Span [freddy:request:#{destination2}] finished",
90
+ "Span [freddy:respond:#{destination}] Sending response",
91
+ "Span [freddy:respond:#{destination}] finished",
92
+
93
+ # Initiator
94
+ "Span [freddy:request:#{destination}] finished"
95
+ ])
96
+ end
45
97
  end
46
98
  end
@@ -1,6 +1,13 @@
1
1
  require 'spec_helper'
2
+ require 'logasm/tracer'
2
3
 
3
4
  describe 'Tracing' do
5
+ let(:tracer) { Logasm::Tracer.new(logger) }
6
+ let(:logger) { spy }
7
+
8
+ before { OpenTracing.global_tracer = tracer }
9
+ after { OpenTracing.global_tracer = nil }
10
+
4
11
  context 'when receiving an untraced request' do
5
12
  let(:freddy) { Freddy.build(logger, config) }
6
13
  let(:destination) { random_destination }
@@ -8,9 +15,9 @@ describe 'Tracing' do
8
15
  before do
9
16
  freddy.respond_to(destination) do |payload, msg_handler|
10
17
  msg_handler.success({
11
- trace_id: Freddy.trace.id,
12
- parent_id: Freddy.trace.parent_id,
13
- span_id: Freddy.trace.span_id
18
+ trace_id: Freddy.trace.context.trace_id,
19
+ parent_id: Freddy.trace.context.parent_id,
20
+ span_id: Freddy.trace.context.span_id
14
21
  })
15
22
  end
16
23
  end
@@ -44,9 +51,9 @@ describe 'Tracing' do
44
51
  freddy.respond_to(destination) do |payload, msg_handler|
45
52
  msg_handler.success({
46
53
  trace_initiator: {
47
- trace_id: Freddy.trace.id,
48
- parent_id: Freddy.trace.parent_id,
49
- span_id: Freddy.trace.span_id
54
+ trace_id: Freddy.trace.context.trace_id,
55
+ parent_id: Freddy.trace.context.parent_id,
56
+ span_id: Freddy.trace.context.span_id
50
57
  },
51
58
  current_receiver: freddy.deliver_with_response(destination2, {})
52
59
  })
@@ -54,9 +61,9 @@ describe 'Tracing' do
54
61
 
55
62
  freddy2.respond_to(destination2) do |payload, msg_handler|
56
63
  msg_handler.success({
57
- trace_id: Freddy.trace.id,
58
- parent_id: Freddy.trace.parent_id,
59
- span_id: Freddy.trace.span_id
64
+ trace_id: Freddy.trace.context.trace_id,
65
+ parent_id: Freddy.trace.context.parent_id,
66
+ span_id: Freddy.trace.context.span_id
60
67
  })
61
68
  end
62
69
  end
@@ -73,11 +80,10 @@ describe 'Tracing' do
73
80
  expect(trace_initiator.fetch(:trace_id)).to eq(current_receiver.fetch(:trace_id))
74
81
  end
75
82
 
76
- it 'has request initiator span_id as parent_id' do
83
+ it 'has parent_id' do
77
84
  response = freddy.deliver_with_response(destination, {})
78
- trace_initiator = response.fetch(:trace_initiator)
79
85
  current_receiver = response.fetch(:current_receiver)
80
- expect(trace_initiator.fetch(:span_id)).to eq(current_receiver.fetch(:parent_id))
86
+ expect(current_receiver.fetch(:parent_id)).to_not be_nil
81
87
  end
82
88
 
83
89
  it 'has generated span_id' do
@@ -102,9 +108,9 @@ describe 'Tracing' do
102
108
  freddy.respond_to(destination) do |payload, msg_handler|
103
109
  msg_handler.success({
104
110
  trace_initiator: {
105
- trace_id: Freddy.trace.id,
106
- parent_id: Freddy.trace.parent_id,
107
- span_id: Freddy.trace.span_id
111
+ trace_id: Freddy.trace.context.trace_id,
112
+ parent_id: Freddy.trace.context.parent_id,
113
+ span_id: Freddy.trace.context.span_id
108
114
  }
109
115
  }.merge(freddy.deliver_with_response(destination2, {})))
110
116
  end
@@ -112,9 +118,9 @@ describe 'Tracing' do
112
118
  freddy2.respond_to(destination2) do |payload, msg_handler|
113
119
  msg_handler.success({
114
120
  previous_receiver: {
115
- trace_id: Freddy.trace.id,
116
- parent_id: Freddy.trace.parent_id,
117
- span_id: Freddy.trace.span_id
121
+ trace_id: Freddy.trace.context.trace_id,
122
+ parent_id: Freddy.trace.context.parent_id,
123
+ span_id: Freddy.trace.context.span_id
118
124
  },
119
125
  current_receiver: freddy2.deliver_with_response(destination3, {})
120
126
  })
@@ -122,9 +128,9 @@ describe 'Tracing' do
122
128
 
123
129
  freddy3.respond_to(destination3) do |payload, msg_handler|
124
130
  msg_handler.success({
125
- trace_id: Freddy.trace.id,
126
- parent_id: Freddy.trace.parent_id,
127
- span_id: Freddy.trace.span_id
131
+ trace_id: Freddy.trace.context.trace_id,
132
+ parent_id: Freddy.trace.context.parent_id,
133
+ span_id: Freddy.trace.context.span_id
128
134
  })
129
135
  end
130
136
  end
@@ -142,11 +148,10 @@ describe 'Tracing' do
142
148
  expect(trace_initiator.fetch(:trace_id)).to eq(current_receiver.fetch(:trace_id))
143
149
  end
144
150
 
145
- it 'has request initiator span_id as parent_id' do
151
+ it 'has parent_id' do
146
152
  response = freddy.deliver_with_response(destination, {})
147
- previous_receiver = response.fetch(:previous_receiver)
148
153
  current_receiver = response.fetch(:current_receiver)
149
- expect(previous_receiver.fetch(:span_id)).to eq(current_receiver.fetch(:parent_id))
154
+ expect(current_receiver.fetch(:parent_id)).to_not be_nil
150
155
  end
151
156
 
152
157
  it 'has generated span_id' do
data/spec/spec_helper.rb CHANGED
@@ -22,6 +22,10 @@ RSpec.configure do |config|
22
22
  config.run_all_when_everything_filtered = true
23
23
  config.filter_run :focus
24
24
  config.order = 'random'
25
+
26
+ config.before do
27
+ OpenTracing.global_tracer ||= OpenTracing::Tracer.new
28
+ end
25
29
  end
26
30
 
27
31
  def random_destination
@@ -61,3 +65,15 @@ def spawn_echo_responder(freddy, queue_name)
61
65
  msg_handler.success(payload)
62
66
  end
63
67
  end
68
+
69
+ class ArrayLogger
70
+ attr_accessor :calls
71
+
72
+ def initialize
73
+ @calls = []
74
+ end
75
+
76
+ def info(*args)
77
+ @calls << args
78
+ end
79
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: freddy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Salemove TechMovers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-29 00:00:00.000000000 Z
11
+ date: 2017-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: opentracing
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.3'
83
97
  description: Messaging API
84
98
  email:
85
99
  - techmovers@salemove.com
@@ -120,7 +134,7 @@ files:
120
134
  - lib/freddy/responder_handler.rb
121
135
  - lib/freddy/sync_response_container.rb
122
136
  - lib/freddy/timeout_error.rb
123
- - lib/freddy/traces.rb
137
+ - lib/freddy/trace_carrier.rb
124
138
  - spec/freddy/consumers/respond_to_consumer_spec.rb
125
139
  - spec/freddy/error_response_spec.rb
126
140
  - spec/freddy/freddy_spec.rb
@@ -128,7 +142,7 @@ files:
128
142
  - spec/freddy/payload_spec.rb
129
143
  - spec/freddy/responder_handler_spec.rb
130
144
  - spec/freddy/sync_response_container_spec.rb
131
- - spec/freddy/traces/trace_id_spec.rb
145
+ - spec/freddy/trace_carrier_spec.rb
132
146
  - spec/integration/concurrency_spec.rb
133
147
  - spec/integration/logging_spec.rb
134
148
  - spec/integration/reply_spec.rb
@@ -167,7 +181,7 @@ test_files:
167
181
  - spec/freddy/payload_spec.rb
168
182
  - spec/freddy/responder_handler_spec.rb
169
183
  - spec/freddy/sync_response_container_spec.rb
170
- - spec/freddy/traces/trace_id_spec.rb
184
+ - spec/freddy/trace_carrier_spec.rb
171
185
  - spec/integration/concurrency_spec.rb
172
186
  - spec/integration/logging_spec.rb
173
187
  - spec/integration/reply_spec.rb
data/lib/freddy/traces.rb DELETED
@@ -1,40 +0,0 @@
1
- class Freddy
2
- module Traces
3
- class Trace
4
- def self.build
5
- id = TraceId.generate
6
- span_id = TraceId.generate
7
- new(id: id, parent_id: nil, span_id: span_id)
8
- end
9
-
10
- def self.build_from_existing_trace(id:, parent_id:)
11
- span_id = TraceId.generate
12
- new(id: id, parent_id: parent_id, span_id: span_id)
13
- end
14
-
15
- attr_reader :id, :parent_id, :span_id
16
-
17
- def initialize(id:, parent_id:, span_id:)
18
- @id = id
19
- @parent_id = parent_id
20
- @span_id = span_id
21
- end
22
-
23
- def to_h
24
- {id: @id, parent_id: @parent_id, span_id: @span_id}
25
- end
26
- end
27
-
28
- module TraceId
29
- TRACE_ID_UPPER_BOUND = 2 ** 64
30
-
31
- # Generates 64-bit lower-hex encoded ID. This was chosen to be compatible
32
- # with tracing frameworks like zipkin.
33
- def self.generate
34
- rand(TRACE_ID_UPPER_BOUND).to_s(16)
35
- end
36
- end
37
-
38
- NO_TRACE = Trace.new(id: nil, parent_id: nil, span_id: nil)
39
- end
40
- end
@@ -1,9 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Freddy::Traces::TraceId do
4
- describe '.generate' do
5
- it 'generates a trace id' do
6
- expect(described_class.generate).to_not be_nil
7
- end
8
- end
9
- end