freddy 1.6.0 → 2.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 +4 -4
- data/.github/workflows/ci.yml +31 -0
- data/.rubocop.yml +9 -28
- data/.ruby-gemset +1 -1
- data/.ruby-version +1 -1
- data/Gemfile +3 -4
- data/README.md +49 -18
- data/freddy.gemspec +12 -18
- data/lib/freddy/adapters/bunny_adapter.rb +20 -3
- data/lib/freddy/adapters.rb +3 -28
- data/lib/freddy/consumers/respond_to_consumer.rb +3 -13
- data/lib/freddy/consumers/response_consumer.rb +2 -4
- data/lib/freddy/consumers/tap_into_consumer.rb +25 -34
- data/lib/freddy/consumers.rb +1 -1
- data/lib/freddy/delivery.rb +45 -13
- data/lib/freddy/encoding.rb +27 -0
- data/lib/freddy/payload.rb +4 -34
- data/lib/freddy/producers/reply_producer.rb +12 -5
- data/lib/freddy/producers/send_and_forget_producer.rb +9 -12
- data/lib/freddy/producers/send_and_wait_response_producer.rb +19 -33
- data/lib/freddy/producers.rb +1 -1
- data/lib/freddy/request_manager.rb +1 -9
- data/lib/freddy/tracing.rb +37 -0
- data/lib/freddy/version.rb +5 -0
- data/lib/freddy.rb +18 -20
- data/spec/.rubocop.yml +26 -0
- data/spec/freddy/error_response_spec.rb +6 -6
- data/spec/freddy/freddy_spec.rb +23 -0
- data/spec/freddy/payload_spec.rb +25 -16
- data/spec/integration/concurrency_spec.rb +8 -12
- data/spec/integration/tap_into_with_group_spec.rb +15 -0
- data/spec/integration/tracing_spec.rb +45 -32
- data/spec/spec_helper.rb +5 -13
- metadata +47 -20
- data/.npmignore +0 -8
- data/.travis.yml +0 -13
- data/lib/freddy/adapters/march_hare_adapter.rb +0 -64
- data/lib/freddy/trace_carrier.rb +0 -28
- data/spec/freddy/trace_carrier_spec.rb +0 -56
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Freddy
|
4
|
+
module Tracing
|
5
|
+
# NOTE: Make sure you finish the span youself.
|
6
|
+
def self.span_for_produce(exchange, routing_key, payload, correlation_id: nil, timeout_in_seconds: nil)
|
7
|
+
destination = exchange.name
|
8
|
+
destination_kind = exchange.type == :direct ? 'queue' : 'topic'
|
9
|
+
|
10
|
+
attributes = {
|
11
|
+
'payload.type' => (payload[:type] || 'unknown').to_s,
|
12
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_SYSTEM => 'rabbitmq',
|
13
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_RABBITMQ_ROUTING_KEY => routing_key,
|
14
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION => destination,
|
15
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION_KIND => destination_kind,
|
16
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION => 'send'
|
17
|
+
}
|
18
|
+
|
19
|
+
attributes['freddy.timeout_in_seconds'] = timeout_in_seconds if timeout_in_seconds
|
20
|
+
|
21
|
+
if correlation_id
|
22
|
+
attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_CONVERSATION_ID] = correlation_id
|
23
|
+
end
|
24
|
+
|
25
|
+
Freddy.tracer.start_span(
|
26
|
+
".#{routing_key} send",
|
27
|
+
kind: OpenTelemetry::Trace::SpanKind::PRODUCER,
|
28
|
+
attributes: attributes
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.inject_tracing_information_to_properties!(properties)
|
33
|
+
properties[:headers] ||= {}
|
34
|
+
OpenTelemetry.propagation.inject(properties[:headers])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/freddy.rb
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
require 'json'
|
4
4
|
require 'thread/pool'
|
5
5
|
require 'securerandom'
|
6
|
-
require '
|
6
|
+
require 'opentelemetry'
|
7
|
+
require 'opentelemetry/semantic_conventions'
|
8
|
+
require_relative './freddy/version'
|
7
9
|
|
8
|
-
Dir[File.dirname(__FILE__)
|
10
|
+
Dir["#{File.dirname(__FILE__)}/freddy/*.rb"].sort.each(&method(:require))
|
9
11
|
|
10
12
|
class Freddy
|
11
13
|
FREDDY_TOPIC_EXCHANGE_NAME = 'freddy-topic'
|
@@ -26,22 +28,15 @@ class Freddy
|
|
26
28
|
# @return [Freddy]
|
27
29
|
#
|
28
30
|
# @example
|
29
|
-
# Freddy.build(Logger.new(
|
30
|
-
def self.build(logger = Logger.new(
|
31
|
-
OpenTracing.global_tracer ||= OpenTracing::Tracer.new
|
32
|
-
|
31
|
+
# Freddy.build(Logger.new($stdout), user: 'thumper', pass: 'howdy')
|
32
|
+
def self.build(logger = Logger.new($stdout), max_concurrency: DEFAULT_MAX_CONCURRENCY, **config)
|
33
33
|
connection = Adapters.determine.connect(config)
|
34
34
|
new(connection, logger, max_concurrency)
|
35
35
|
end
|
36
36
|
|
37
|
-
# @
|
38
|
-
def self.
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
# @deprecated Use OpenTracing ScopeManager instead
|
43
|
-
def self.trace=(trace)
|
44
|
-
OpenTracing.scope_manager.activate(trace) if OpenTracing.active_span != trace
|
37
|
+
# @private
|
38
|
+
def self.tracer
|
39
|
+
@tracer ||= OpenTelemetry.tracer_provider.tracer('freddy', Freddy::VERSION)
|
45
40
|
end
|
46
41
|
|
47
42
|
def initialize(connection, logger, max_concurrency)
|
@@ -70,7 +65,7 @@ class Freddy
|
|
70
65
|
# Received message as a ruby hash with symbolized keys
|
71
66
|
# @yieldparam [#success, #error] handler
|
72
67
|
# Handler for responding to messages. Use handler#success for successful
|
73
|
-
#
|
68
|
+
# response and handler#error for error response.
|
74
69
|
#
|
75
70
|
# @return [#shutdown]
|
76
71
|
#
|
@@ -104,7 +99,7 @@ class Freddy
|
|
104
99
|
# consuming them. It is useful for general messages that two or more clients
|
105
100
|
# are interested.
|
106
101
|
#
|
107
|
-
# @param [String]
|
102
|
+
# @param [String] pattern_or_patterns
|
108
103
|
# the destination pattern. Use `#` wildcard for matching 0 or more words.
|
109
104
|
# Use `*` to match exactly one word.
|
110
105
|
# @param [Hash] options
|
@@ -129,12 +124,12 @@ class Freddy
|
|
129
124
|
# freddy.tap_into 'notifications.*' do |message|
|
130
125
|
# puts "Notification showed #{message.inspect}"
|
131
126
|
# end
|
132
|
-
def tap_into(
|
133
|
-
@logger.debug "Tapping into messages that match #{
|
127
|
+
def tap_into(pattern_or_patterns, options = {}, &callback)
|
128
|
+
@logger.debug "Tapping into messages that match #{pattern_or_patterns}"
|
134
129
|
|
135
130
|
Consumers::TapIntoConsumer.consume(
|
136
131
|
thread_pool: Thread.pool(@prefetch_buffer_size),
|
137
|
-
|
132
|
+
patterns: Array(pattern_or_patterns),
|
138
133
|
channel: @connection.create_channel(prefetch: @prefetch_buffer_size),
|
139
134
|
options: options,
|
140
135
|
&callback
|
@@ -157,15 +152,18 @@ class Freddy
|
|
157
152
|
# @option options [Integer] :timeout (0)
|
158
153
|
# discards the message after given seconds if nobody consumes it. Message
|
159
154
|
# won't be discarded if timeout it set to 0 (default).
|
160
|
-
#
|
155
|
+
# @option options [String] :compress (nil)
|
156
|
+
# - 'zlib' - compresses the payload with zlib
|
161
157
|
# @return [void]
|
162
158
|
#
|
163
159
|
# @example
|
164
160
|
# freddy.deliver 'Metrics', user_id: 5, metric: 'signed_in'
|
165
161
|
def deliver(destination, payload, options = {})
|
166
162
|
timeout = options.fetch(:timeout, 0)
|
163
|
+
compression_algorithm = options.fetch(:compress, nil)
|
167
164
|
opts = {}
|
168
165
|
opts[:expiration] = (timeout * 1000).to_i if timeout.positive?
|
166
|
+
opts[:content_encoding] = compression_algorithm if compression_algorithm
|
169
167
|
|
170
168
|
@send_and_forget_producer.produce(destination, payload, opts)
|
171
169
|
end
|
data/spec/.rubocop.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
inherit_from: ../.rubocop.yml
|
3
|
+
|
4
|
+
RSpec/ExampleLength:
|
5
|
+
Enabled: no
|
6
|
+
|
7
|
+
RSpec/MultipleExpectations:
|
8
|
+
Enabled: no
|
9
|
+
|
10
|
+
RSpec/MessageSpies:
|
11
|
+
Enabled: no
|
12
|
+
|
13
|
+
RSpec/VerifiedDoubles:
|
14
|
+
Enabled: no
|
15
|
+
|
16
|
+
RSpec/InstanceVariable:
|
17
|
+
Enabled: no
|
18
|
+
|
19
|
+
RSpec/NestedGroups:
|
20
|
+
Enabled: no
|
21
|
+
|
22
|
+
RSpec/DescribeClass:
|
23
|
+
Enabled: no
|
24
|
+
|
25
|
+
RSpec/MultipleMemoizedHelpers:
|
26
|
+
Enabled: no
|
@@ -13,10 +13,10 @@ describe Freddy::ErrorResponse do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
describe '#message' do
|
16
|
-
subject { error.message }
|
16
|
+
subject(:message) { error.message }
|
17
17
|
|
18
18
|
it 'uses error type as a message' do
|
19
|
-
|
19
|
+
expect(message).to eq('SomeError')
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -31,10 +31,10 @@ describe Freddy::ErrorResponse do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
describe '#message' do
|
34
|
-
subject { error.message }
|
34
|
+
subject(:message) { error.message }
|
35
35
|
|
36
36
|
it 'uses error type as a message' do
|
37
|
-
|
37
|
+
expect(message).to eq('SomeError: extra info')
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -49,10 +49,10 @@ describe Freddy::ErrorResponse do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
describe '#message' do
|
52
|
-
subject { error.message }
|
52
|
+
subject(:message) { error.message }
|
53
53
|
|
54
54
|
it 'uses default error message as a message' do
|
55
|
-
|
55
|
+
expect(message).to eq('Use #response to get the error response')
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
data/spec/freddy/freddy_spec.rb
CHANGED
@@ -49,6 +49,29 @@ describe Freddy do
|
|
49
49
|
expect(processed_after_timeout).to be(true)
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
context 'with compress' do
|
54
|
+
it 'compresses the payload' do
|
55
|
+
expect(Freddy::Encoding).to receive(:compress).with(anything, 'zlib').and_call_original
|
56
|
+
|
57
|
+
freddy.tap_into(destination) { |msg| @tapped_message = msg }
|
58
|
+
freddy.deliver(destination, payload, compress: 'zlib')
|
59
|
+
default_sleep
|
60
|
+
|
61
|
+
wait_for { @tapped_message }
|
62
|
+
expect(@tapped_message).to eq(payload)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'without compress' do
|
67
|
+
it 'does not compress the payload' do
|
68
|
+
freddy.tap_into(destination) { |msg| @tapped_message = msg }
|
69
|
+
deliver
|
70
|
+
|
71
|
+
wait_for { @tapped_message }
|
72
|
+
expect(@tapped_message).to eq(payload)
|
73
|
+
end
|
74
|
+
end
|
52
75
|
end
|
53
76
|
|
54
77
|
context 'when making a synchronized request' do
|
data/spec/freddy/payload_spec.rb
CHANGED
@@ -2,19 +2,28 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Freddy::Payload do
|
4
4
|
describe '#dump' do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
5
|
+
context 'with a given Ruby engine' do
|
6
|
+
let(:ts) do
|
7
|
+
RUBY_ENGINE == 'jruby' ? '{"time":"2016-01-04T20:18:00Z"}' : '{"time":"2016-01-04T20:18:00.000000Z"}'
|
8
|
+
end
|
9
|
+
let(:ts_array) do
|
10
|
+
RUBY_ENGINE == 'jruby' ? '{"time":["2016-01-04T20:18:00Z"]}' : '{"time":["2016-01-04T20:18:00.000000Z"]}'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'serializes time objects as iso8601 format strings' do
|
14
|
+
expect(dump(time: Time.utc(2016, 1, 4, 20, 18)))
|
15
|
+
.to eq(ts)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'serializes time objects in an array as iso8601 format strings' do
|
19
|
+
expect(dump(time: [Time.utc(2016, 1, 4, 20, 18)]))
|
20
|
+
.to eq(ts_array)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'serializes time objects in a nested hash as iso8601 format strings' do
|
24
|
+
expect(dump(x: { time: Time.utc(2016, 1, 4, 20, 18) }))
|
25
|
+
.to eq("{\"x\":#{ts}}")
|
26
|
+
end
|
18
27
|
end
|
19
28
|
|
20
29
|
it 'serializes date objects as iso8601 format strings' do
|
@@ -33,17 +42,17 @@ describe Freddy::Payload do
|
|
33
42
|
end
|
34
43
|
|
35
44
|
it 'serializes datetime objects as iso8601 format strings' do
|
36
|
-
expect(dump(datetime: DateTime.new(2016, 1, 4, 20, 18)))
|
45
|
+
expect(dump(datetime: DateTime.new(2016, 1, 4, 20, 18)))
|
37
46
|
.to eq('{"datetime":"2016-01-04T20:18:00+00:00"}')
|
38
47
|
end
|
39
48
|
|
40
49
|
it 'serializes datetime objects in an array as iso8601 format strings' do
|
41
|
-
expect(dump(datetime: [DateTime.new(2016, 1, 4, 20, 18)]))
|
50
|
+
expect(dump(datetime: [DateTime.new(2016, 1, 4, 20, 18)]))
|
42
51
|
.to eq('{"datetime":["2016-01-04T20:18:00+00:00"]}')
|
43
52
|
end
|
44
53
|
|
45
54
|
it 'serializes datetime objects in a nested hash as iso8601 format strings' do
|
46
|
-
expect(dump(x: { datetime: DateTime.new(2016, 1, 4, 20, 18) }))
|
55
|
+
expect(dump(x: { datetime: DateTime.new(2016, 1, 4, 20, 18) }))
|
47
56
|
.to eq('{"x":{"datetime":"2016-01-04T20:18:00+00:00"}}')
|
48
57
|
end
|
49
58
|
|
@@ -10,21 +10,17 @@ describe 'Concurrency' do
|
|
10
10
|
|
11
11
|
it 'supports multiple requests in #respond_to' do
|
12
12
|
freddy1.respond_to 'Concurrency1' do |_payload, msg_handler|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
msg_handler.error(e.response)
|
19
|
-
end
|
13
|
+
freddy1.deliver_with_response 'Concurrency2', msg: 'noop'
|
14
|
+
result2 = freddy1.deliver_with_response 'Concurrency3', msg: 'noop'
|
15
|
+
msg_handler.success(result2)
|
16
|
+
rescue Freddy::InvalidRequestError => e
|
17
|
+
msg_handler.error(e.response)
|
20
18
|
end
|
21
19
|
|
22
20
|
freddy2.respond_to 'Concurrency2' do |_payload, msg_handler|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
msg_handler.error(e.response)
|
27
|
-
end
|
21
|
+
msg_handler.success(from: 'Concurrency2')
|
22
|
+
rescue Freddy::InvalidRequestError => e
|
23
|
+
msg_handler.error(e.response)
|
28
24
|
end
|
29
25
|
|
30
26
|
freddy3.respond_to 'Concurrency3' do |_payload, msg_handler|
|
@@ -40,4 +40,19 @@ describe 'Tapping into with group identifier' do
|
|
40
40
|
wait_for { counter == 2 }
|
41
41
|
expect(counter).to eq(2)
|
42
42
|
end
|
43
|
+
|
44
|
+
it 'taps into multiple topics' do
|
45
|
+
destination2 = random_destination
|
46
|
+
counter = 0
|
47
|
+
|
48
|
+
responder1.tap_into([destination, destination2], group: arbitrary_id) do
|
49
|
+
counter += 1
|
50
|
+
end
|
51
|
+
|
52
|
+
deliverer.deliver(destination, {})
|
53
|
+
deliverer.deliver(destination2, {})
|
54
|
+
|
55
|
+
wait_for { counter == 2 }
|
56
|
+
expect(counter).to eq(2)
|
57
|
+
end
|
43
58
|
end
|
@@ -1,13 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'opentracing_test_tracer'
|
3
2
|
|
4
3
|
describe 'Tracing' do
|
5
|
-
let(:tracer) { OpenTracingTestTracer.build(logger: logger) }
|
6
4
|
let(:logger) { spy }
|
7
5
|
|
8
|
-
before { OpenTracing.global_tracer = tracer }
|
9
|
-
after { OpenTracing.global_tracer = nil }
|
10
|
-
|
11
6
|
context 'when receiving a traced request' do
|
12
7
|
let(:freddy) { Freddy.build(logger, config) }
|
13
8
|
let(:freddy2) { Freddy.build(logger, config) }
|
@@ -18,21 +13,13 @@ describe 'Tracing' do
|
|
18
13
|
before do
|
19
14
|
freddy.respond_to(destination) do |_payload, msg_handler|
|
20
15
|
msg_handler.success(
|
21
|
-
trace_initiator:
|
22
|
-
trace_id: active_span.context.trace_id,
|
23
|
-
parent_id: active_span.context.parent_id,
|
24
|
-
span_id: active_span.context.span_id
|
25
|
-
},
|
16
|
+
trace_initiator: current_span_attributes,
|
26
17
|
current_receiver: freddy.deliver_with_response(destination2, {})
|
27
18
|
)
|
28
19
|
end
|
29
20
|
|
30
21
|
freddy2.respond_to(destination2) do |_payload, msg_handler|
|
31
|
-
msg_handler.success(
|
32
|
-
trace_id: active_span.context.trace_id,
|
33
|
-
parent_id: active_span.context.parent_id,
|
34
|
-
span_id: active_span.context.span_id
|
35
|
-
)
|
22
|
+
msg_handler.success(current_span_attributes)
|
36
23
|
end
|
37
24
|
end
|
38
25
|
|
@@ -75,31 +62,19 @@ describe 'Tracing' do
|
|
75
62
|
before do
|
76
63
|
freddy.respond_to(destination) do |_payload, msg_handler|
|
77
64
|
msg_handler.success({
|
78
|
-
trace_initiator:
|
79
|
-
trace_id: active_span.context.trace_id,
|
80
|
-
parent_id: active_span.context.parent_id,
|
81
|
-
span_id: active_span.context.span_id
|
82
|
-
}
|
65
|
+
trace_initiator: current_span_attributes
|
83
66
|
}.merge(freddy.deliver_with_response(destination2, {})))
|
84
67
|
end
|
85
68
|
|
86
69
|
freddy2.respond_to(destination2) do |_payload, msg_handler|
|
87
70
|
msg_handler.success(
|
88
|
-
previous_receiver:
|
89
|
-
trace_id: active_span.context.trace_id,
|
90
|
-
parent_id: active_span.context.parent_id,
|
91
|
-
span_id: active_span.context.span_id
|
92
|
-
},
|
71
|
+
previous_receiver: current_span_attributes,
|
93
72
|
current_receiver: freddy2.deliver_with_response(destination3, {})
|
94
73
|
)
|
95
74
|
end
|
96
75
|
|
97
76
|
freddy3.respond_to(destination3) do |_payload, msg_handler|
|
98
|
-
msg_handler.success(
|
99
|
-
trace_id: active_span.context.trace_id,
|
100
|
-
parent_id: active_span.context.parent_id,
|
101
|
-
span_id: active_span.context.span_id
|
102
|
-
)
|
77
|
+
msg_handler.success(current_span_attributes)
|
103
78
|
end
|
104
79
|
end
|
105
80
|
|
@@ -131,7 +106,45 @@ describe 'Tracing' do
|
|
131
106
|
end
|
132
107
|
end
|
133
108
|
|
134
|
-
|
135
|
-
|
109
|
+
context 'when receiving a broadcast' do
|
110
|
+
let(:freddy) { Freddy.build(logger, config) }
|
111
|
+
let(:destination) { random_destination }
|
112
|
+
|
113
|
+
before do
|
114
|
+
freddy.tap_into(destination) do
|
115
|
+
@deliver_span = current_span_attributes
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
after do
|
120
|
+
freddy.close
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'creates a new trace and links it with the sender' do
|
124
|
+
initiator_span = nil
|
125
|
+
Freddy.tracer.in_span('test') do
|
126
|
+
initiator_span = current_span_attributes
|
127
|
+
freddy.deliver(destination, {})
|
128
|
+
end
|
129
|
+
wait_for { @deliver_span }
|
130
|
+
|
131
|
+
expect(@deliver_span.fetch(:trace_id)).not_to eq(initiator_span.fetch(:trace_id))
|
132
|
+
|
133
|
+
link = @deliver_span.fetch(:links)[0]
|
134
|
+
expect(link.span_context.trace_id).to eq(initiator_span.fetch(:trace_id))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def current_span_attributes
|
139
|
+
{
|
140
|
+
trace_id: current_span.context.trace_id,
|
141
|
+
parent_id: current_span.parent_span_id,
|
142
|
+
span_id: current_span.context.span_id,
|
143
|
+
links: current_span.links || []
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
def current_span
|
148
|
+
OpenTelemetry::Trace.current_span
|
136
149
|
end
|
137
150
|
end
|