freddy 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7f70c4bc02ac5f44642acf811ecc4aa895d76327
4
- data.tar.gz: 56373103982583fc804f8a90783de797f8cc371b
3
+ metadata.gz: 55a9bea9337f7d20d2e713bd012ed8f216fef328
4
+ data.tar.gz: 2c49767c9226fdf1b882cf221cec8ff7726203f9
5
5
  SHA512:
6
- metadata.gz: 1e7c1f7fd21ef20f040af3cfadfcf42c9e8e2e44b05d08617e1225d60eb75f9b223dc6cade579bffa0dd08708da89d71896ca4d463d0f44be15ffb81535a047d
7
- data.tar.gz: c3f9e83af176f8c8731adf77449e6b32c6adddce91dbb1025db00156bf45c96caac47b68c93607e311624f06a8ed39c43bc878678b9bc4a9d4df001fd40547d4
6
+ metadata.gz: 7d3c30c1d5d54c72539821bc465f0887371ee30fdcbed3fa4711ee3b9f65137922025a4aa6956ca862b08b851d5bc8bff2d4aac7279a80250475c45a90e475ab
7
+ data.tar.gz: 13f606726903475c3619ee4f7dd1d18ebe257cbb48844cb0831cf81809b468eeb7143c4d4519b8cd1e6fa00e9d4703dcf9ce1282ebf70d4867419c85a89f42ae
data/README.md CHANGED
@@ -135,6 +135,35 @@ The following operations are supported:
135
135
  responder_handler.shutdown
136
136
  ```
137
137
 
138
+ ## Request Tracing
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.
141
+
142
+ ```ruby
143
+ freddy1 = Freddy.build
144
+ freddy1.respond_do('service1') do |payload, msg_handler|
145
+ puts "Trace id: #{Freddy.trace.id}"
146
+ puts "Span id: #{Freddy.trace.span_id}"
147
+
148
+ freddy1.deliver('service2', {})
149
+ end
150
+
151
+ freddy2 = Freddy.build
152
+ 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}"
156
+ end
157
+ ```
158
+
159
+ In case you already have trace IDs (e.g. provided by REST API) then you can set them yourself:
160
+ ```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
+ })
166
+ ```
138
167
 
139
168
  ## Notes about concurrency
140
169
 
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.0.1'
11
+ spec.version = '1.1.0'
12
12
  spec.authors = ["Salemove TechMovers"]
13
13
  spec.email = ["techmovers@salemove.com"]
14
14
  spec.description = %q{Messaging API}
@@ -37,9 +37,11 @@ class Freddy
37
37
  def process_message(delivery, &block)
38
38
  @consume_thread_pool.process do
39
39
  begin
40
+ Freddy.trace = delivery.trace
40
41
  block.call(delivery)
41
42
  ensure
42
43
  @channel.acknowledge(delivery.tag, false)
44
+ Freddy.trace = nil
43
45
  end
44
46
  end
45
47
  end
@@ -44,9 +44,11 @@ class Freddy
44
44
  @consume_thread_pool.process do
45
45
  begin
46
46
  Consumers.log_receive_event(@logger, queue.name, delivery)
47
+ Freddy.trace = delivery.trace
47
48
  block.call delivery.payload, delivery.routing_key
48
49
  ensure
49
50
  @channel.acknowledge(delivery.tag, false)
51
+ Freddy.trace = nil
50
52
  end
51
53
  end
52
54
  end
@@ -5,7 +5,8 @@ class Freddy
5
5
  message: 'Received message',
6
6
  queue: queue_name,
7
7
  payload: delivery.payload,
8
- correlation_id: delivery.correlation_id
8
+ correlation_id: delivery.correlation_id,
9
+ trace: Freddy.trace.to_h
9
10
  )
10
11
  end
11
12
  end
@@ -1,12 +1,13 @@
1
1
  class Freddy
2
2
  class Delivery
3
- attr_reader :routing_key, :payload, :tag
3
+ attr_reader :routing_key, :payload, :tag, :trace
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 || {})
10
11
  end
11
12
 
12
13
  def correlation_id
@@ -20,5 +21,18 @@ class Freddy
20
21
  def reply_to
21
22
  @metadata.reply_to
22
23
  end
24
+
25
+ private
26
+
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
36
+ end
23
37
  end
24
38
  end
@@ -12,7 +12,12 @@ class Freddy
12
12
  Producers.log_send_event(@logger, payload, destination)
13
13
 
14
14
  properties = properties.merge(
15
- routing_key: destination, content_type: CONTENT_TYPE
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
21
  )
17
22
 
18
23
  @exchange.publish Payload.dump(payload), properties
@@ -12,7 +12,14 @@ class Freddy
12
12
  def produce(destination, payload, properties)
13
13
  Producers.log_send_event(@logger, payload, destination)
14
14
 
15
- properties = properties.merge(routing_key: destination, content_type: CONTENT_TYPE)
15
+ properties = properties.merge(
16
+ 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
+ }
22
+ )
16
23
  json_payload = Payload.dump(payload)
17
24
 
18
25
  # Connection adapters handle thread safety for #publish themselves. No
@@ -41,7 +41,11 @@ class Freddy
41
41
  properties = properties.merge(
42
42
  routing_key: destination, content_type: CONTENT_TYPE,
43
43
  correlation_id: correlation_id, reply_to: @response_queue.name,
44
- mandatory: true, type: 'request'
44
+ mandatory: true, type: 'request',
45
+ headers: {
46
+ 'x-trace-id' => Freddy.trace.id,
47
+ 'x-span-id' => Freddy.trace.span_id
48
+ }
45
49
  )
46
50
  json_payload = Payload.dump(payload)
47
51
 
@@ -50,7 +54,8 @@ class Freddy
50
54
  queue: destination,
51
55
  payload: payload,
52
56
  response_queue: @response_queue.name,
53
- correlation_id: correlation_id
57
+ correlation_id: correlation_id,
58
+ trace: Freddy.trace.to_h
54
59
  )
55
60
 
56
61
  # Connection adapters handle thread safety for #publish themselves. No
@@ -1,7 +1,7 @@
1
1
  class Freddy
2
2
  module Producers
3
3
  def self.log_send_event(logger, payload, destination)
4
- logger.debug message: 'Sending message', queue: destination, payload: payload
4
+ logger.debug message: 'Sending message', queue: destination, payload: payload, trace: Freddy.trace.to_h
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,40 @@
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
data/lib/freddy.rb CHANGED
@@ -30,6 +30,14 @@ class Freddy
30
30
  new(connection, logger, max_concurrency)
31
31
  end
32
32
 
33
+ def self.trace
34
+ Thread.current[:freddy_trace] || Traces::NO_TRACE
35
+ end
36
+
37
+ def self.trace=(trace)
38
+ Thread.current[:freddy_trace] = trace
39
+ end
40
+
33
41
  def initialize(connection, logger, max_concurrency)
34
42
  @connection = connection
35
43
  @logger = logger
@@ -0,0 +1,9 @@
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
@@ -27,7 +27,8 @@ describe 'Logging' do
27
27
  message: 'Received message',
28
28
  queue: destination,
29
29
  payload: payload,
30
- correlation_id: anything
30
+ correlation_id: anything,
31
+ trace: anything
31
32
  )
32
33
  end
33
34
 
@@ -38,7 +39,8 @@ describe 'Logging' do
38
39
  queue: destination,
39
40
  payload: payload,
40
41
  response_queue: anything,
41
- correlation_id: anything
42
+ correlation_id: anything,
43
+ trace: anything
42
44
  )
43
45
  end
44
46
  end
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Tracing' do
4
+ context 'when receiving an untraced request' do
5
+ let(:freddy) { Freddy.build(logger, config) }
6
+ let(:destination) { random_destination }
7
+
8
+ before do
9
+ freddy.respond_to(destination) do |payload, msg_handler|
10
+ msg_handler.success({
11
+ trace_id: Freddy.trace.id,
12
+ parent_id: Freddy.trace.parent_id,
13
+ span_id: Freddy.trace.span_id
14
+ })
15
+ end
16
+ end
17
+
18
+ after { freddy.close }
19
+
20
+ it 'has generated trace_id' do
21
+ response = freddy.deliver_with_response(destination, {})
22
+ expect(response.fetch(:trace_id)).to_not be_nil
23
+ end
24
+
25
+ it 'has no parent_id' do
26
+ response = freddy.deliver_with_response(destination, {})
27
+ expect(response.fetch(:parent_id)).to be_nil
28
+ end
29
+
30
+ it 'has generated span_id' do
31
+ response = freddy.deliver_with_response(destination, {})
32
+ expect(response.fetch(:span_id)).to_not be_nil
33
+ end
34
+ end
35
+
36
+ context 'when receiving a traced request' do
37
+ let(:freddy) { Freddy.build(logger, config) }
38
+ let(:freddy2) { Freddy.build(logger, config) }
39
+
40
+ let(:destination) { random_destination }
41
+ let(:destination2) { random_destination }
42
+
43
+ before do
44
+ freddy.respond_to(destination) do |payload, msg_handler|
45
+ msg_handler.success({
46
+ trace_initiator: {
47
+ trace_id: Freddy.trace.id,
48
+ parent_id: Freddy.trace.parent_id,
49
+ span_id: Freddy.trace.span_id
50
+ },
51
+ current_receiver: freddy.deliver_with_response(destination2, {})
52
+ })
53
+ end
54
+
55
+ freddy2.respond_to(destination2) do |payload, msg_handler|
56
+ msg_handler.success({
57
+ trace_id: Freddy.trace.id,
58
+ parent_id: Freddy.trace.parent_id,
59
+ span_id: Freddy.trace.span_id
60
+ })
61
+ end
62
+ end
63
+
64
+ after do
65
+ freddy.close
66
+ freddy2.close
67
+ end
68
+
69
+ it 'has trace_id from the trace initiator' do
70
+ response = freddy.deliver_with_response(destination, {})
71
+ trace_initiator = response.fetch(:trace_initiator)
72
+ current_receiver = response.fetch(:current_receiver)
73
+ expect(trace_initiator.fetch(:trace_id)).to eq(current_receiver.fetch(:trace_id))
74
+ end
75
+
76
+ it 'has request initiator span_id as parent_id' do
77
+ response = freddy.deliver_with_response(destination, {})
78
+ trace_initiator = response.fetch(:trace_initiator)
79
+ current_receiver = response.fetch(:current_receiver)
80
+ expect(trace_initiator.fetch(:span_id)).to eq(current_receiver.fetch(:parent_id))
81
+ end
82
+
83
+ it 'has generated span_id' do
84
+ response = freddy.deliver_with_response(destination, {})
85
+ trace_initiator = response.fetch(:trace_initiator)
86
+ current_receiver = response.fetch(:current_receiver)
87
+ expect(current_receiver.fetch(:span_id)).to_not be_nil
88
+ expect(current_receiver.fetch(:span_id)).to_not eq(trace_initiator.fetch(:span_id))
89
+ end
90
+ end
91
+
92
+ context 'when receiving a nested traced request' do
93
+ let(:freddy) { Freddy.build(logger, config) }
94
+ let(:freddy2) { Freddy.build(logger, config) }
95
+ let(:freddy3) { Freddy.build(logger, config) }
96
+
97
+ let(:destination) { random_destination }
98
+ let(:destination2) { random_destination }
99
+ let(:destination3) { random_destination }
100
+
101
+ before do
102
+ freddy.respond_to(destination) do |payload, msg_handler|
103
+ msg_handler.success({
104
+ trace_initiator: {
105
+ trace_id: Freddy.trace.id,
106
+ parent_id: Freddy.trace.parent_id,
107
+ span_id: Freddy.trace.span_id
108
+ }
109
+ }.merge(freddy.deliver_with_response(destination2, {})))
110
+ end
111
+
112
+ freddy2.respond_to(destination2) do |payload, msg_handler|
113
+ msg_handler.success({
114
+ previous_receiver: {
115
+ trace_id: Freddy.trace.id,
116
+ parent_id: Freddy.trace.parent_id,
117
+ span_id: Freddy.trace.span_id
118
+ },
119
+ current_receiver: freddy2.deliver_with_response(destination3, {})
120
+ })
121
+ end
122
+
123
+ freddy3.respond_to(destination3) do |payload, msg_handler|
124
+ msg_handler.success({
125
+ trace_id: Freddy.trace.id,
126
+ parent_id: Freddy.trace.parent_id,
127
+ span_id: Freddy.trace.span_id
128
+ })
129
+ end
130
+ end
131
+
132
+ after do
133
+ freddy.close
134
+ freddy2.close
135
+ freddy3.close
136
+ end
137
+
138
+ it 'has trace_id from the trace initiator' do
139
+ response = freddy.deliver_with_response(destination, {})
140
+ trace_initiator = response.fetch(:trace_initiator)
141
+ current_receiver = response.fetch(:current_receiver)
142
+ expect(trace_initiator.fetch(:trace_id)).to eq(current_receiver.fetch(:trace_id))
143
+ end
144
+
145
+ it 'has request initiator span_id as parent_id' do
146
+ response = freddy.deliver_with_response(destination, {})
147
+ previous_receiver = response.fetch(:previous_receiver)
148
+ current_receiver = response.fetch(:current_receiver)
149
+ expect(previous_receiver.fetch(:span_id)).to eq(current_receiver.fetch(:parent_id))
150
+ end
151
+
152
+ it 'has generated span_id' do
153
+ response = freddy.deliver_with_response(destination, {})
154
+ previous_receiver = response.fetch(:previous_receiver)
155
+ current_receiver = response.fetch(:current_receiver)
156
+ expect(current_receiver.fetch(:span_id)).to_not be_nil
157
+ expect(current_receiver.fetch(:span_id)).to_not eq(previous_receiver.fetch(:span_id))
158
+ end
159
+ end
160
+ 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.0.1
4
+ version: 1.1.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-02-08 00:00:00.000000000 Z
11
+ date: 2017-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -120,6 +120,7 @@ files:
120
120
  - lib/freddy/responder_handler.rb
121
121
  - lib/freddy/sync_response_container.rb
122
122
  - lib/freddy/timeout_error.rb
123
+ - lib/freddy/traces.rb
123
124
  - spec/freddy/consumers/respond_to_consumer_spec.rb
124
125
  - spec/freddy/error_response_spec.rb
125
126
  - spec/freddy/freddy_spec.rb
@@ -127,10 +128,12 @@ files:
127
128
  - spec/freddy/payload_spec.rb
128
129
  - spec/freddy/responder_handler_spec.rb
129
130
  - spec/freddy/sync_response_container_spec.rb
131
+ - spec/freddy/traces/trace_id_spec.rb
130
132
  - spec/integration/concurrency_spec.rb
131
133
  - spec/integration/logging_spec.rb
132
134
  - spec/integration/reply_spec.rb
133
135
  - spec/integration/tap_into_with_group_spec.rb
136
+ - spec/integration/tracing_spec.rb
134
137
  - spec/spec_helper.rb
135
138
  homepage:
136
139
  licenses:
@@ -164,8 +167,10 @@ test_files:
164
167
  - spec/freddy/payload_spec.rb
165
168
  - spec/freddy/responder_handler_spec.rb
166
169
  - spec/freddy/sync_response_container_spec.rb
170
+ - spec/freddy/traces/trace_id_spec.rb
167
171
  - spec/integration/concurrency_spec.rb
168
172
  - spec/integration/logging_spec.rb
169
173
  - spec/integration/reply_spec.rb
170
174
  - spec/integration/tap_into_with_group_spec.rb
175
+ - spec/integration/tracing_spec.rb
171
176
  - spec/spec_helper.rb