ears 0.3.0 → 0.4.3
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/CHANGELOG.md +25 -0
- data/Gemfile.lock +5 -1
- data/README.md +140 -5
- data/Rakefile +3 -0
- data/ears.gemspec +8 -0
- data/lib/ears.rb +28 -5
- data/lib/ears/configuration.rb +2 -0
- data/lib/ears/consumer.rb +37 -11
- data/lib/ears/consumer_wrapper.rb +44 -0
- data/lib/ears/middleware.rb +15 -0
- data/lib/ears/middlewares/appsignal.rb +12 -3
- data/lib/ears/middlewares/json.rb +10 -3
- data/lib/ears/setup.rb +23 -4
- data/lib/ears/version.rb +1 -1
- data/package.json +1 -1
- metadata +36 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b46a87dfab0c7135fbb74534070d44ef48b693f7dfd848b3df9d68ceb915b29
|
4
|
+
data.tar.gz: 3880d424b10a408d49a51dfb1f6e22650ede251d23b642ee5983e42cfa4c0b82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7c3368085535d3c385778d0711b89755dbe4f5e2fd8f91fb986c573bf4beeb8a86253c00e8238be5cc38c78e5fbe727dc403500c16752985ac7e04e208d9075
|
7
|
+
data.tar.gz: ba1ee1434321eb492948027c31d7180d1254f89bcd756cf3edd11f64abdaae9c553841bacc8fef1607939ac6aa8341cb5c9751441c4b48f807bf9ba73e4a8cf4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,30 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.4.3 (2021-07-26)
|
4
|
+
|
5
|
+
### Changes
|
6
|
+
|
7
|
+
- Ears will not exit gracefully on unhandled errors anymore. You have to take care of proper flushing and cleanup yourself (see README for example).
|
8
|
+
|
9
|
+
## 0.3.3 (2021-06-07)
|
10
|
+
|
11
|
+
### Changes
|
12
|
+
|
13
|
+
- Ears will now exit gracefully on unhandled errors to give the application a chance to do flushing and cleanup work
|
14
|
+
|
15
|
+
## 0.3.2 (2021-05-21)
|
16
|
+
|
17
|
+
### Changes
|
18
|
+
|
19
|
+
- added YARD documentation and usage instructions to README
|
20
|
+
- introduced `Ears::Middleware` as an abstract base class for middlewares
|
21
|
+
|
22
|
+
## 0.3.1 (2021-05-07)
|
23
|
+
|
24
|
+
### Changes
|
25
|
+
|
26
|
+
- internally, user-defined `Ears::Consumer` instances are now wrapped by `Ears::ConsumerWrapper` to make the user-defined class smaller and easier to test
|
27
|
+
|
3
28
|
## 0.3.0 (2021-05-07)
|
4
29
|
|
5
30
|
### Breaking
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ears (0.3
|
4
|
+
ears (0.4.3)
|
5
5
|
bunny
|
6
6
|
multi_json
|
7
7
|
|
@@ -19,6 +19,7 @@ GEM
|
|
19
19
|
ast (~> 2.4.1)
|
20
20
|
rainbow (3.0.0)
|
21
21
|
rake (13.0.3)
|
22
|
+
redcarpet (3.5.1)
|
22
23
|
regexp_parser (2.1.1)
|
23
24
|
rexml (3.2.4)
|
24
25
|
rspec (3.10.0)
|
@@ -52,6 +53,7 @@ GEM
|
|
52
53
|
rubocop-ast (>= 1.1.0)
|
53
54
|
ruby-progressbar (1.11.0)
|
54
55
|
unicode-display_width (2.0.0)
|
56
|
+
yard (0.9.26)
|
55
57
|
|
56
58
|
PLATFORMS
|
57
59
|
x86_64-darwin-20
|
@@ -60,10 +62,12 @@ PLATFORMS
|
|
60
62
|
DEPENDENCIES
|
61
63
|
ears!
|
62
64
|
rake
|
65
|
+
redcarpet
|
63
66
|
rspec
|
64
67
|
rubocop
|
65
68
|
rubocop-rake
|
66
69
|
rubocop-rspec
|
70
|
+
yard
|
67
71
|
|
68
72
|
BUNDLED WITH
|
69
73
|
2.2.3
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Ears
|
2
2
|
|
3
|
-
|
3
|
+
`Ears` is a small, simple library for writing RabbitMQ consumers.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -20,16 +20,151 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
|
23
|
+
### Basic usage
|
24
|
+
|
25
|
+
First, you should configure where `Ears` should connect to.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'ears'
|
29
|
+
|
30
|
+
Ears.configure do |config|
|
31
|
+
config.rabbitmq_url = 'amqp://user:password@myrmq:5672'
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Next, define your exchanges, queues, and consumers by calling `Ears.setup`.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
Ears.setup do
|
39
|
+
# define a durable topic exchange
|
40
|
+
my_exchange = exchange('my_exchange', :topic, durable: true)
|
41
|
+
|
42
|
+
# define a queue
|
43
|
+
my_queue = queue('my_queue', durable: true)
|
44
|
+
|
45
|
+
# bind your queue to the exchange
|
46
|
+
my_queue.bind(my_exchange, routing_key: 'my.routing.key')
|
47
|
+
|
48
|
+
# define a consumer that handles messages for that queue
|
49
|
+
consumer(my_queue, MyConsumer)
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
Finally, you need to implement `MyConsumer` by subclassing `Ears::Consumer`.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class MyConsumer < Ears::Consumer
|
57
|
+
def work(delivery_info, metadata, payload)
|
58
|
+
message = JSON.parse(payload)
|
59
|
+
do_stuff(message)
|
60
|
+
|
61
|
+
ack
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
And, do not forget to run it. Be prepared that unhandled errors will be reraised. So, take care of cleanup work.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
begin
|
70
|
+
Ears.run!
|
71
|
+
ensure
|
72
|
+
# all your cleanup work goes here...
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
At the end of the `#work` method, you must always return `ack`, `reject`, or `requeue` to signal what should be done with the message.
|
77
|
+
|
78
|
+
### Middlewares
|
79
|
+
|
80
|
+
`Ears` supports middlewares that you can use for recurring tasks that you don't always want to reimplement. It comes with some built-in middlewares:
|
81
|
+
|
82
|
+
- `Ears::JSON` for automatically parsing JSON payloads
|
83
|
+
- `Ears::Appsignal` for automatically wrapping `#work` in an Appsignal transaction
|
84
|
+
|
85
|
+
You can use a middleware by just calling `use` with the middleware you want to register in your consumer.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
require 'ears/middlewares/json'
|
89
|
+
|
90
|
+
class MyConsumer < Ears::Consumer
|
91
|
+
# register the JSON middleware and don't symbolize keys (this can be omitted, the default is true)
|
92
|
+
use Ears::Middlewares::JSON, symbolize_keys: false
|
93
|
+
|
94
|
+
def work(delivery_info, metadata, payload)
|
95
|
+
return ack unless payload['data'].nil? # this now just works
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
If you want to implement your own middleware, just subclass `Ears::Middleware` and implement `#call` (and if you need it `#initialize`).
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
class MyMiddleware < Ears::Middleware
|
104
|
+
def initialize(opts = {})
|
105
|
+
@my_option = opts.fetch(:my_option, nil)
|
106
|
+
end
|
107
|
+
|
108
|
+
def call(delivery_info, metadata, payload, app)
|
109
|
+
do_stuff
|
110
|
+
|
111
|
+
# always call the next middleware in the chain or your consumer will never be called
|
112
|
+
app.call(delivery_info, metadata, payload)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
### Multiple threads
|
118
|
+
|
119
|
+
If you need to handle a lot of messages, you might want to have multiple instances of the same consumer all working on a dedicated thread. This is supported out of the box. You just have to define how many consumers you want when calling `consumer` in `Ears.setup`.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
Ears.setup do
|
123
|
+
my_exchange = exchange('my_exchange', :topic, durable: true)
|
124
|
+
my_queue = queue('my_queue', durable: true)
|
125
|
+
my_queue.bind(my_exchange, routing_key: 'my.routing.key')
|
126
|
+
|
127
|
+
# this will instantiate MyConsumer 10 times and run every instance on a dedicated thread
|
128
|
+
consumer(my_queue, MyConsumer, 10)
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
It may also be interesting for you to increase the prefetch amount. The default prefetch amount is 1, but if you have a lot of very small, fast to process messages, a higher prefetch is a good idea. Just set it when defining your consumer.
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
Ears.setup do
|
136
|
+
my_exchange = exchange('my_exchange', :topic, durable: true)
|
137
|
+
my_queue = queue('my_queue', durable: true)
|
138
|
+
my_queue.bind(my_exchange, routing_key: 'my.routing.key')
|
139
|
+
|
140
|
+
# this will instantiate one consumer but with a prefetch value of 10
|
141
|
+
consumer(my_queue, MyConsumer, 1, prefetch: 10)
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
### Setting arbitrary exchange/queue parameters
|
146
|
+
|
147
|
+
If you need some custom arguments on your exchange or queue, you can just pass these to `queue` or `exchange` inside `Ears.setup`. These are then just passed on to `Bunny::Queue` and `Bunny::Exchange`.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
Ears.setup do
|
151
|
+
my_queue =
|
152
|
+
queue('my_queue', durable: true, arguments: { 'x-message-ttl' => 10_000 })
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
## Documentation
|
157
|
+
|
158
|
+
If you need more in-depth information, look at [our API documentation](https://www.rubydoc.info/gems/ears).
|
24
159
|
|
25
160
|
## Contributing
|
26
161
|
|
27
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
162
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ivx/ears. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ivx/ears/blob/master/CODE_OF_CONDUCT.md).
|
28
163
|
|
29
164
|
## License
|
30
165
|
|
31
|
-
The gem is available as open
|
166
|
+
The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
32
167
|
|
33
168
|
## Code of Conduct
|
34
169
|
|
35
|
-
Everyone interacting in the Ears project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
170
|
+
Everyone interacting in the Ears project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/ivx/ears/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
data/ears.gemspec
CHANGED
@@ -19,6 +19,12 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.metadata['changelog_uri'] =
|
20
20
|
'https://github.com/ivx/ears/blob/master/CHANGELOG.md'
|
21
21
|
|
22
|
+
spec.post_install_message =
|
23
|
+
'
|
24
|
+
Ears: the new version changed the exit behaviour in case of uncaught exceptions.
|
25
|
+
You may want to have a look into the CHANGELOG!
|
26
|
+
'
|
27
|
+
|
22
28
|
spec.files =
|
23
29
|
Dir.chdir(File.expand_path('..', __FILE__)) do
|
24
30
|
`git ls-files -z`.split("\x0").reject do |f|
|
@@ -33,8 +39,10 @@ Gem::Specification.new do |spec|
|
|
33
39
|
spec.add_dependency 'multi_json'
|
34
40
|
|
35
41
|
spec.add_development_dependency 'rake'
|
42
|
+
spec.add_development_dependency 'redcarpet'
|
36
43
|
spec.add_development_dependency 'rspec'
|
37
44
|
spec.add_development_dependency 'rubocop'
|
38
45
|
spec.add_development_dependency 'rubocop-rake'
|
39
46
|
spec.add_development_dependency 'rubocop-rspec'
|
47
|
+
spec.add_development_dependency 'yard'
|
40
48
|
end
|
data/lib/ears.rb
CHANGED
@@ -1,47 +1,70 @@
|
|
1
1
|
require 'bunny'
|
2
2
|
require 'ears/configuration'
|
3
3
|
require 'ears/consumer'
|
4
|
+
require 'ears/middleware'
|
4
5
|
require 'ears/setup'
|
5
6
|
require 'ears/version'
|
6
7
|
|
7
8
|
module Ears
|
8
|
-
class Error < StandardError
|
9
|
-
end
|
10
|
-
|
11
9
|
class << self
|
10
|
+
# The global configuration for Ears.
|
11
|
+
#
|
12
|
+
# @return [Ears::Configuration]
|
12
13
|
def configuration
|
13
14
|
@configuration ||= Ears::Configuration.new
|
14
15
|
end
|
15
16
|
|
17
|
+
# Yields the global configuration instance so you can modify it.
|
18
|
+
# @yieldparam configuration [Ears::Configuration] The global configuration instance.
|
16
19
|
def configure
|
17
20
|
yield(configuration)
|
18
21
|
end
|
19
22
|
|
23
|
+
# The global RabbitMQ connection used by Ears.
|
24
|
+
#
|
25
|
+
# @return [Bunny::Session]
|
20
26
|
def connection
|
21
27
|
@connection ||= Bunny.new.tap { |conn| conn.start }
|
22
28
|
end
|
23
29
|
|
30
|
+
# The channel for the current thread.
|
31
|
+
#
|
32
|
+
# @return [Bunny::Channel]
|
24
33
|
def channel
|
25
34
|
Thread.current[:ears_channel] ||=
|
26
35
|
connection
|
27
36
|
.create_channel(nil, 1, true)
|
28
37
|
.tap do |channel|
|
29
38
|
channel.prefetch(1)
|
30
|
-
channel.on_uncaught_exception { |error|
|
39
|
+
channel.on_uncaught_exception { |error| Ears.error!(error) }
|
31
40
|
end
|
32
41
|
end
|
33
42
|
|
43
|
+
# Used to set up your exchanges, queues and consumers. See {Ears::Setup} for implementation details.
|
34
44
|
def setup(&block)
|
35
45
|
Ears::Setup.new.instance_eval(&block)
|
36
46
|
end
|
37
47
|
|
48
|
+
# Blocks the calling thread until +SIGTERM+ or +SIGINT+ is received.
|
49
|
+
# Used to keep the process alive while processing messages.
|
38
50
|
def run!
|
39
51
|
running = true
|
40
52
|
Signal.trap('INT') { running = false }
|
41
53
|
Signal.trap('TERM') { running = false }
|
42
|
-
sleep 1 while running
|
54
|
+
sleep 1 while running && !@uncaught_error_occurred
|
55
|
+
raise @error if @uncaught_error_occurred
|
56
|
+
end
|
57
|
+
|
58
|
+
# Signals that an uncaught error has occurred and the process should be stopped.
|
59
|
+
#
|
60
|
+
# @param [Exception] error The unhandled error that occurred.
|
61
|
+
def error!(error)
|
62
|
+
puts(error.full_message)
|
63
|
+
@uncaught_error_occurred = true
|
64
|
+
@error = error
|
43
65
|
end
|
44
66
|
|
67
|
+
# Used internally for testing.
|
45
68
|
def reset!
|
46
69
|
@connection = nil
|
47
70
|
Thread.current[:ears_channel] = nil
|
data/lib/ears/configuration.rb
CHANGED
data/lib/ears/consumer.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'bunny'
|
2
2
|
|
3
3
|
module Ears
|
4
|
-
class
|
4
|
+
# The abstract base class for consumers processing messages from queues.
|
5
|
+
# @abstract Subclass and override {#work} to implement.
|
6
|
+
class Consumer
|
7
|
+
# Error that is raised when an invalid value is returned from {#work}
|
5
8
|
class InvalidReturnError < StandardError
|
6
9
|
def initialize(value)
|
7
10
|
super(
|
@@ -10,18 +13,38 @@ module Ears
|
|
10
13
|
end
|
11
14
|
end
|
12
15
|
|
16
|
+
# List of registered middlewares. Register new middlewares with {.use}.
|
17
|
+
# @return [Array<Ears::Middleware>]
|
13
18
|
def self.middlewares
|
14
19
|
@middlewares ||= []
|
15
20
|
end
|
16
21
|
|
22
|
+
# Registers a new middleware by instantiating +middleware+ and passing it +opts+.
|
23
|
+
#
|
24
|
+
# @param [Class<Ears::Middleware>] middleware The middleware class to instantiate and register.
|
25
|
+
# @param [Hash] opts The options for instantiating the middleware.
|
17
26
|
def self.use(middleware, opts = {})
|
18
27
|
middlewares << middleware.new(opts)
|
19
28
|
end
|
20
29
|
|
30
|
+
# The method that is called when a message from the queue is received.
|
31
|
+
# Keep in mind that the parameters received can be altered by middlewares!
|
32
|
+
#
|
33
|
+
# @param [Bunny::DeliveryInfo] delivery_info The delivery info of the message.
|
34
|
+
# @param [Bunny::MessageProperties] metadata The metadata of the message.
|
35
|
+
# @param [String] payload The payload of the message.
|
36
|
+
#
|
37
|
+
# @return [:ack, :reject, :requeue] A symbol denoting what should be done with the message.
|
21
38
|
def work(delivery_info, metadata, payload)
|
22
39
|
raise NotImplementedError
|
23
40
|
end
|
24
41
|
|
42
|
+
# Wraps #work to add middlewares. This is being called by Ears when a message is received for the consumer.
|
43
|
+
#
|
44
|
+
# @param [Bunny::DeliveryInfo] delivery_info The delivery info of the received message.
|
45
|
+
# @param [Bunny::MessageProperties] metadata The metadata of the received message.
|
46
|
+
# @param [String] payload The payload of the received message.
|
47
|
+
# @raise [InvalidReturnError] if you return something other than +:ack+, +:reject+ or +:requeue+ from {#work}.
|
25
48
|
def process_delivery(delivery_info, metadata, payload)
|
26
49
|
self.class.middlewares.reverse.reduce(
|
27
50
|
work_proc,
|
@@ -32,14 +55,24 @@ module Ears
|
|
32
55
|
|
33
56
|
protected
|
34
57
|
|
58
|
+
# Helper method to ack a message.
|
59
|
+
#
|
60
|
+
# @return [:ack]
|
35
61
|
def ack
|
36
62
|
:ack
|
37
63
|
end
|
38
64
|
|
65
|
+
# Helper method to reject a message.
|
66
|
+
#
|
67
|
+
# @return [:reject]
|
68
|
+
#
|
39
69
|
def reject
|
40
70
|
:reject
|
41
71
|
end
|
42
72
|
|
73
|
+
# Helper method to requeue a message.
|
74
|
+
#
|
75
|
+
# @return [:requeue]
|
43
76
|
def requeue
|
44
77
|
:requeue
|
45
78
|
end
|
@@ -49,7 +82,7 @@ module Ears
|
|
49
82
|
def work_proc
|
50
83
|
->(delivery_info, metadata, payload) do
|
51
84
|
work(delivery_info, metadata, payload).tap do |result|
|
52
|
-
|
85
|
+
verify_result(result)
|
53
86
|
end
|
54
87
|
end
|
55
88
|
end
|
@@ -60,15 +93,8 @@ module Ears
|
|
60
93
|
end
|
61
94
|
end
|
62
95
|
|
63
|
-
def
|
64
|
-
|
65
|
-
when :ack
|
66
|
-
channel.ack(delivery_tag, false)
|
67
|
-
when :reject
|
68
|
-
channel.reject(delivery_tag)
|
69
|
-
when :requeue
|
70
|
-
channel.reject(delivery_tag, true)
|
71
|
-
else
|
96
|
+
def verify_result(result)
|
97
|
+
unless %i[ack reject requeue].include?(result)
|
72
98
|
raise InvalidReturnError, result
|
73
99
|
end
|
74
100
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
3
|
+
module Ears
|
4
|
+
# Wraps the user-defined consumer to provide the expected interface to Bunny.
|
5
|
+
class ConsumerWrapper < Bunny::Consumer
|
6
|
+
# @param [Ears::Consumer] consumer The user-defined consumer implementation derived from {Ears::Consumer}.
|
7
|
+
# @param [Bunny::Channel] channel The channel used for the consumer.
|
8
|
+
# @param [Bunny::Queue] queue The queue the consumer is subscribed to.
|
9
|
+
# @param [String] consumer_tag A string identifying the consumer instance.
|
10
|
+
# @param [Hash] arguments Arguments that are passed on to +Bunny::Consumer.new+.
|
11
|
+
def initialize(consumer, channel, queue, consumer_tag, arguments = {})
|
12
|
+
@consumer = consumer
|
13
|
+
super(channel, queue, consumer_tag, false, false, arguments)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Called when a message is received from the subscribed queue.
|
17
|
+
#
|
18
|
+
# @param [Bunny::DeliveryInfo] delivery_info The delivery info of the received message.
|
19
|
+
# @param [Bunny::MessageProperties] metadata The metadata of the received message.
|
20
|
+
# @param [String] payload The payload of the received message.
|
21
|
+
def process_delivery(delivery_info, metadata, payload)
|
22
|
+
consumer
|
23
|
+
.process_delivery(delivery_info, metadata, payload)
|
24
|
+
.tap { |result| process_result(result, delivery_info.delivery_tag) }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :consumer
|
30
|
+
|
31
|
+
def process_result(result, delivery_tag)
|
32
|
+
case result
|
33
|
+
when :ack
|
34
|
+
channel.ack(delivery_tag, false)
|
35
|
+
when :reject
|
36
|
+
channel.reject(delivery_tag)
|
37
|
+
when :requeue
|
38
|
+
channel.reject(delivery_tag, true)
|
39
|
+
else
|
40
|
+
raise InvalidReturnError, result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Ears
|
2
|
+
# The abstract base class for middlewares.
|
3
|
+
# @abstract Subclass and override {#call} (and maybe +#initialize+) to implement.
|
4
|
+
class Middleware
|
5
|
+
# Invokes the middleware.
|
6
|
+
#
|
7
|
+
# @param [Bunny::DeliveryInfo] delivery_info The delivery info of the received message.
|
8
|
+
# @param [Bunny::MessageProperties] metadata The metadata of the received message.
|
9
|
+
# @param [String] payload The payload of the received message.
|
10
|
+
# @param app The next middleware to call or the actual consumer instance.
|
11
|
+
def call(delivery_info, metadata, payload, app)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,9 +1,14 @@
|
|
1
|
+
require 'ears/middleware'
|
2
|
+
|
1
3
|
module Ears
|
2
4
|
module Middlewares
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
# A middleware that automatically wraps {Ears::Consumer#work} in an Appsignal transaction.
|
6
|
+
class Appsignal < Middleware
|
7
|
+
# @param [Hash] opts The options for the middleware.
|
8
|
+
# @option opts [String] :transaction_name The name of the Appsignal transaction.
|
9
|
+
# @option opts [String] :class_name The name of the class you want to monitor.
|
6
10
|
def initialize(opts)
|
11
|
+
super()
|
7
12
|
@transaction_name = opts.fetch(:transaction_name)
|
8
13
|
@class_name = opts.fetch(:class_name)
|
9
14
|
end
|
@@ -16,6 +21,10 @@ module Ears
|
|
16
21
|
queue_start: Time.now.utc,
|
17
22
|
) { app.call(delivery_info, metadata, payload) }
|
18
23
|
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :transaction_name, :class_name
|
19
28
|
end
|
20
29
|
end
|
21
30
|
end
|
@@ -1,11 +1,14 @@
|
|
1
|
+
require 'ears/middleware'
|
1
2
|
require 'multi_json'
|
2
3
|
|
3
4
|
module Ears
|
4
5
|
module Middlewares
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
# A middleware that automatically parses your JSON payload.
|
7
|
+
class JSON < Middleware
|
8
|
+
# @param [Hash] opts The options for the middleware.
|
9
|
+
# @option opts [Boolean] :symbolize_keys Whether to symbolize the keys of your payload.
|
8
10
|
def initialize(opts = {})
|
11
|
+
super()
|
9
12
|
@symbolize_keys = opts.fetch(:symbolize_keys, true)
|
10
13
|
end
|
11
14
|
|
@@ -13,6 +16,10 @@ module Ears
|
|
13
16
|
parsed_payload = MultiJson.load(payload, symbolize_keys: symbolize_keys)
|
14
17
|
app.call(delivery_info, metadata, parsed_payload)
|
15
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :symbolize_keys
|
16
23
|
end
|
17
24
|
end
|
18
25
|
end
|
data/lib/ears/setup.rb
CHANGED
@@ -1,16 +1,36 @@
|
|
1
1
|
require 'bunny'
|
2
2
|
require 'ears/consumer'
|
3
|
+
require 'ears/consumer_wrapper'
|
3
4
|
|
4
5
|
module Ears
|
6
|
+
# Contains methods used in {Ears.setup} to set up your exchanges, queues and consumers.
|
5
7
|
class Setup
|
8
|
+
# Creates a new exchange if it does not already exist.
|
9
|
+
#
|
10
|
+
# @param [String] name The name of the exchange.
|
11
|
+
# @param [Symbol] type The type of the exchange (:direct, :fanout, :topic or :headers).
|
12
|
+
# @param [Hash] opts The options for the exchange. These are passed on to +Bunny::Exchange.new+.
|
13
|
+
# @return [Bunny::Exchange] The exchange that was either newly created or was already there.
|
6
14
|
def exchange(name, type, opts = {})
|
7
15
|
Bunny::Exchange.new(Ears.channel, type, name, opts)
|
8
16
|
end
|
9
17
|
|
18
|
+
# Creates a new queue if it does not already exist.
|
19
|
+
#
|
20
|
+
# @param [String] name The name of the queue.
|
21
|
+
# @param [Hash] opts The options for the queue. These are passed on to +Bunny::Exchange.new+.
|
22
|
+
# @return [Bunny::Queue] The queue that was either newly created or was already there.
|
10
23
|
def queue(name, opts = {})
|
11
24
|
Bunny::Queue.new(Ears.channel, name, opts)
|
12
25
|
end
|
13
26
|
|
27
|
+
# Creates and starts one or many consumers bound to the given queue.
|
28
|
+
#
|
29
|
+
# @param [Bunny::Queue] queue The queue the consumers should be subscribed to.
|
30
|
+
# @param [Class<Ears::Consumer>] consumer_class A class implementing {Ears::Consumer} that holds the consumer behavior.
|
31
|
+
# @param [Integer] threads The number of threads that should be used to process messages from the queue.
|
32
|
+
# @param [Hash] args The arguments for the consumer. These are passed on to +Bunny::Consumer.new+.
|
33
|
+
# @option args [Integer] :prefetch (1) The prefetch count used for this consumer.
|
14
34
|
def consumer(queue, consumer_class, threads = 1, args = {})
|
15
35
|
threads.times do |n|
|
16
36
|
consumer_queue = create_consumer_queue(queue, args)
|
@@ -27,12 +47,11 @@ module Ears
|
|
27
47
|
private
|
28
48
|
|
29
49
|
def create_consumer(queue, consumer_class, args, number)
|
30
|
-
|
50
|
+
ConsumerWrapper.new(
|
51
|
+
consumer_class.new,
|
31
52
|
queue.channel,
|
32
53
|
queue,
|
33
54
|
"#{consumer_class.name}-#{number}",
|
34
|
-
false,
|
35
|
-
false,
|
36
55
|
args,
|
37
56
|
)
|
38
57
|
end
|
@@ -43,7 +62,7 @@ module Ears
|
|
43
62
|
.create_channel(nil, 1, true)
|
44
63
|
.tap do |channel|
|
45
64
|
channel.prefetch(args.fetch(:prefetch, 1))
|
46
|
-
channel.on_uncaught_exception { |error|
|
65
|
+
channel.on_uncaught_exception { |error| Ears.error!(error) }
|
47
66
|
end
|
48
67
|
end
|
49
68
|
|
data/lib/ears/version.rb
CHANGED
data/package.json
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ears
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mario Mainz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redcarpet
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rspec
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +122,20 @@ dependencies:
|
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
111
139
|
description: A gem for building RabbitMQ consumers.
|
112
140
|
email:
|
113
141
|
- mario.mainz@invision.de
|
@@ -131,6 +159,8 @@ files:
|
|
131
159
|
- lib/ears.rb
|
132
160
|
- lib/ears/configuration.rb
|
133
161
|
- lib/ears/consumer.rb
|
162
|
+
- lib/ears/consumer_wrapper.rb
|
163
|
+
- lib/ears/middleware.rb
|
134
164
|
- lib/ears/middlewares/appsignal.rb
|
135
165
|
- lib/ears/middlewares/json.rb
|
136
166
|
- lib/ears/setup.rb
|
@@ -145,7 +175,10 @@ metadata:
|
|
145
175
|
homepage_uri: https://github.com/ivx/ears
|
146
176
|
source_code_uri: https://github.com/ivx/ears
|
147
177
|
changelog_uri: https://github.com/ivx/ears/blob/master/CHANGELOG.md
|
148
|
-
post_install_message:
|
178
|
+
post_install_message: |2
|
179
|
+
|
180
|
+
Ears: the new version changed the exit behaviour in case of uncaught exceptions.
|
181
|
+
You may want to have a look into the CHANGELOG!
|
149
182
|
rdoc_options: []
|
150
183
|
require_paths:
|
151
184
|
- lib
|