govuk_message_queue_consumer 2.0.1 → 2.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 +4 -4
- data/README.md +27 -1
- data/lib/govuk_message_queue_consumer/consumer.rb +14 -2
- data/lib/govuk_message_queue_consumer/message.rb +5 -1
- data/lib/govuk_message_queue_consumer/null_statsd.rb +4 -0
- data/lib/govuk_message_queue_consumer/version.rb +1 -1
- data/spec/consumer_spec.rb +12 -11
- data/spec/consumer_with_statsd_spec.rb +54 -0
- data/spec/message_spec.rb +6 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ea5c6560ba396d9f32d22371cd8f94240bdfbcd
|
4
|
+
data.tar.gz: fdc543d6ee7d1290e3df4db3628f497aa3151e37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 760eb4893ce343d4e5dc439bc05a80ef49ceb8fc9c4538395079f6644b05ad555e435e32415be004bb0116dd1d9ec8b54bedf62819bf8eceaf7ac1caa3ce8451
|
7
|
+
data.tar.gz: 98f3ff7cb41cb8f20233114121e53967d180462ecd1118c07d0abf409456ca3c663127c6e2692f8a7cbadaa250bf2e36ba65e4865a45edf160a3d6f6d9e71374
|
data/README.md
CHANGED
@@ -9,6 +9,8 @@ the content-items it receives, so that applications such as
|
|
9
9
|
[email-alert-service](https://github.com/alphagov/email-alert-service) can be
|
10
10
|
notified of changes in content.
|
11
11
|
|
12
|
+
For detailed documentation, check out the [gem documentation on rubydoc.info](http://www.rubydoc.info/gems/govuk_message_queue_consumer/GovukMessageQueueConsumer/Consumer#initialize-instance_method).
|
13
|
+
|
12
14
|
## Nomenclature
|
13
15
|
|
14
16
|

|
@@ -51,7 +53,7 @@ Add the gem:
|
|
51
53
|
|
52
54
|
```ruby
|
53
55
|
# Gemfile
|
54
|
-
gem "govuk_message_queue_consumer", "~> 2.
|
56
|
+
gem "govuk_message_queue_consumer", "~> 2.1"
|
55
57
|
```
|
56
58
|
|
57
59
|
Add a rake task like the following example:
|
@@ -70,6 +72,8 @@ namespace :message_queue do
|
|
70
72
|
end
|
71
73
|
```
|
72
74
|
|
75
|
+
More options are [documented here](http://www.rubydoc.info/gems/govuk_message_queue_consumer/GovukMessageQueueConsumer/Consumer#initialize-instance_method).
|
76
|
+
|
73
77
|
The consumer expects a number of environment variables to be present. On GOV.UK,
|
74
78
|
these should be set up in puppet.
|
75
79
|
|
@@ -129,6 +133,28 @@ class MyProcessor
|
|
129
133
|
end
|
130
134
|
```
|
131
135
|
|
136
|
+
### Statsd integration
|
137
|
+
|
138
|
+
You can pass a `statsd_client` to the `GovukMessageQueueConsumer::Consumer` initializer. The consumer will emit counters to statsd with these keys:
|
139
|
+
|
140
|
+
- `your_queue_name.started` - message picked up from the your_queue_name
|
141
|
+
- `your_queue_name.retried` - message has been retried
|
142
|
+
- `your_queue_name.acked` - message has been processed and acked
|
143
|
+
- `your_queue_name.discarded` - message has been discarded
|
144
|
+
- `your_queue_name.uncaught_exception` - an uncaught exception occured during processing
|
145
|
+
|
146
|
+
Remember to use a namespace for the `Statsd` client:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
statsd_client = Statsd.new("localhost")
|
150
|
+
statsd_client.namespace = "govuk.app.my_app_name"
|
151
|
+
|
152
|
+
GovukMessageQueueConsumer::Consumer.new(
|
153
|
+
statsd_client: statsd_client
|
154
|
+
# ... other setup code omitted
|
155
|
+
).run
|
156
|
+
```
|
157
|
+
|
132
158
|
### Testing your processor
|
133
159
|
|
134
160
|
This gem provides a test helper for your processor.
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'bunny'
|
2
|
+
require_relative 'null_statsd'
|
2
3
|
|
3
4
|
module GovukMessageQueueConsumer
|
4
5
|
class Consumer
|
@@ -11,21 +12,32 @@ module GovukMessageQueueConsumer
|
|
11
12
|
# time to share the work evenly.
|
12
13
|
NUMBER_OF_MESSAGES_TO_PREFETCH = 1
|
13
14
|
|
14
|
-
|
15
|
+
# Create a new consumer
|
16
|
+
#
|
17
|
+
# @param queue_name [String] Your queue name. This is specific to your application.
|
18
|
+
# @param exchange_name [String] Name of the exchange to bind to, for example `published_documents`
|
19
|
+
# @param processor [Object] An object that responds to `process`
|
20
|
+
# @param routing_key [String] The RabbitMQ routing key to bind the queue to
|
21
|
+
# @param statsd_client [Statsd] An instance of the Statsd class
|
22
|
+
def initialize(queue_name:, exchange_name:, processor:, routing_key: '#', statsd_client: NullStatsd.new)
|
15
23
|
@queue_name = queue_name
|
16
24
|
@exchange_name = exchange_name
|
17
25
|
@processor = processor
|
18
26
|
@routing_key = routing_key
|
27
|
+
@statsd_client = statsd_client
|
19
28
|
end
|
20
29
|
|
21
30
|
def run
|
22
31
|
queue.subscribe(block: true, manual_ack: true) do |delivery_info, headers, payload|
|
23
32
|
begin
|
24
33
|
message = Message.new(payload, headers, delivery_info)
|
34
|
+
@statsd_client.increment("#{@queue_name}.started")
|
25
35
|
processor_chain.process(message)
|
36
|
+
@statsd_client.increment("#{@queue_name}.#{message.status}")
|
26
37
|
rescue Exception => e
|
38
|
+
@statsd_client.increment("#{@queue_name}.uncaught_exception")
|
27
39
|
Airbrake.notify_or_ignore(e) if defined?(Airbrake)
|
28
|
-
$stderr.puts "Uncaught exception in processor: \n\n #{e.class}: #{e.message}\n\n#{e.backtrace}"
|
40
|
+
$stderr.puts "Uncaught exception in processor: \n\n #{e.class}: #{e.message}\n\n#{e.backtrace.join("\n")}"
|
29
41
|
exit(1) # Ensure rabbitmq requeues outstanding messages
|
30
42
|
end
|
31
43
|
end
|
@@ -3,24 +3,28 @@ require 'json'
|
|
3
3
|
module GovukMessageQueueConsumer
|
4
4
|
# Client code will receive an instance of this
|
5
5
|
class Message
|
6
|
-
attr_accessor :delivery_info, :headers, :payload
|
6
|
+
attr_accessor :delivery_info, :headers, :payload, :status
|
7
7
|
|
8
8
|
def initialize(payload, headers, delivery_info)
|
9
9
|
@payload = payload
|
10
10
|
@headers = headers
|
11
11
|
@delivery_info = delivery_info
|
12
|
+
@status = :status
|
12
13
|
end
|
13
14
|
|
14
15
|
def ack
|
15
16
|
@delivery_info.channel.ack(@delivery_info.delivery_tag)
|
17
|
+
@status = :acked
|
16
18
|
end
|
17
19
|
|
18
20
|
def retry
|
19
21
|
@delivery_info.channel.reject(@delivery_info.delivery_tag, true)
|
22
|
+
@status = :retried
|
20
23
|
end
|
21
24
|
|
22
25
|
def discard
|
23
26
|
@delivery_info.channel.reject(@delivery_info.delivery_tag, false)
|
27
|
+
@status = :discarded
|
24
28
|
end
|
25
29
|
end
|
26
30
|
end
|
data/spec/consumer_spec.rb
CHANGED
@@ -1,33 +1,34 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
|
+
require_relative 'support/queue_helpers'
|
2
3
|
|
3
4
|
describe Consumer do
|
4
|
-
|
5
|
-
let(:channel) { instance_double('Bunny::Channel', queue: queue, prefetch: nil, topic: nil) }
|
6
|
-
let(:rabbitmq_connecton) { instance_double("Bunny::Session", start: nil, create_channel: channel) }
|
7
|
-
let(:client_processor) { instance_double('Client::Processor') }
|
5
|
+
include QueueHelpers
|
8
6
|
|
9
|
-
|
10
|
-
stub_environment_variables!
|
11
|
-
allow(Bunny).to receive(:new).and_return(rabbitmq_connecton)
|
12
|
-
end
|
7
|
+
let(:client_processor) { instance_double('Client::Processor') }
|
13
8
|
|
14
|
-
describe "
|
9
|
+
describe "#run" do
|
15
10
|
it "binds the queue to the all-routing key" do
|
11
|
+
queue = create_stubbed_queue
|
12
|
+
|
16
13
|
expect(queue).to receive(:bind).with(nil, { routing_key: "#" })
|
17
14
|
|
18
15
|
Consumer.new(queue_name: "some-queue", exchange_name: "my-exchange", processor: client_processor).run
|
19
16
|
end
|
20
17
|
|
21
18
|
it "binds the queue to a custom routing key" do
|
19
|
+
queue = create_stubbed_queue
|
20
|
+
|
22
21
|
expect(queue).to receive(:bind).with(nil, { routing_key: "*.major" })
|
23
22
|
|
24
23
|
Consumer.new(queue_name: "some-queue", exchange_name: "my-exchange", processor: client_processor, routing_key: "*.major").run
|
25
24
|
end
|
26
25
|
|
27
26
|
it "calls the heartbeat processor when subscribing to messages" do
|
27
|
+
queue = create_stubbed_queue
|
28
|
+
|
28
29
|
expect(queue).to receive(:subscribe).and_yield(:delivery_info_object, :headers, "payload")
|
29
|
-
|
30
|
-
expect_any_instance_of(HeartbeatProcessor).to receive(:process)
|
30
|
+
|
31
|
+
expect_any_instance_of(HeartbeatProcessor).to receive(:process).with(kind_of(Message))
|
31
32
|
|
32
33
|
Consumer.new(queue_name: "some-queue", exchange_name: "my-exchange", processor: client_processor).run
|
33
34
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'support/queue_helpers'
|
3
|
+
|
4
|
+
describe Consumer do
|
5
|
+
include QueueHelpers
|
6
|
+
|
7
|
+
describe "#run" do
|
8
|
+
it "increments the counters on the statsd client" do
|
9
|
+
statsd_client = StatsdClientMock.new
|
10
|
+
queue = create_stubbed_queue
|
11
|
+
|
12
|
+
expect(queue).to receive(:subscribe).and_yield(
|
13
|
+
double(:delivery_info, channel: double(:channel, reject: double), delivery_tag: double),
|
14
|
+
double(:headers, content_type: 'application/json'),
|
15
|
+
"message_payload"
|
16
|
+
)
|
17
|
+
|
18
|
+
Consumer.new(queue_name: "some-queue", exchange_name: "my-exchange", processor: double, statsd_client: statsd_client).run
|
19
|
+
|
20
|
+
expect(statsd_client.incremented_keys).to eql(['some-queue.started', 'some-queue.discarded'])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "increments the uncaught_exception counter for uncaught exceptions" do
|
24
|
+
statsd_client = StatsdClientMock.new
|
25
|
+
queue = create_stubbed_queue
|
26
|
+
|
27
|
+
expect(queue).to receive(:subscribe).and_yield(
|
28
|
+
double(:delivery_info, channel: double(:channel, reject: double), delivery_tag: double),
|
29
|
+
double(:headers, content_type: 'application/json'),
|
30
|
+
{}.to_json
|
31
|
+
)
|
32
|
+
|
33
|
+
processor = double
|
34
|
+
expect(processor).to receive(:process).and_raise("An exception")
|
35
|
+
|
36
|
+
expect do
|
37
|
+
Consumer.new(queue_name: "some-queue", exchange_name: "my-exchange", processor: processor, statsd_client: statsd_client).run
|
38
|
+
end.to raise_error(SystemExit)
|
39
|
+
|
40
|
+
expect(statsd_client.incremented_keys).to eql(['some-queue.started', 'some-queue.uncaught_exception'])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class StatsdClientMock
|
45
|
+
attr_reader :incremented_keys
|
46
|
+
def initialize
|
47
|
+
@incremented_keys = []
|
48
|
+
end
|
49
|
+
|
50
|
+
def increment(key)
|
51
|
+
@incremented_keys << key
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/message_spec.rb
CHANGED
@@ -10,17 +10,23 @@ describe Message do
|
|
10
10
|
expect(mock_channel).to receive(:ack).with("a_tag")
|
11
11
|
|
12
12
|
message.ack
|
13
|
+
|
14
|
+
expect(message.status).to eql(:acked)
|
13
15
|
end
|
14
16
|
|
15
17
|
it "retry sends a reject to the channel with requeue set" do
|
16
18
|
expect(mock_channel).to receive(:reject).with("a_tag", true)
|
17
19
|
|
18
20
|
message.retry
|
21
|
+
|
22
|
+
expect(message.status).to eql(:retried)
|
19
23
|
end
|
20
24
|
|
21
25
|
it "reject sends a reject to the channel without requeue set" do
|
22
26
|
expect(mock_channel).to receive(:reject).with("a_tag", false)
|
23
27
|
|
24
28
|
message.discard
|
29
|
+
|
30
|
+
expect(message.status).to eql(:discarded)
|
25
31
|
end
|
26
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: govuk_message_queue_consumer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GOV.UK Dev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 10.4.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
description: Avoid writing boilerplate code in order to consume messages from an AMQP
|
70
84
|
message queue. Plug in queue configuration, and how to process each message.
|
71
85
|
email:
|
@@ -81,6 +95,7 @@ files:
|
|
81
95
|
- lib/govuk_message_queue_consumer/heartbeat_processor.rb
|
82
96
|
- lib/govuk_message_queue_consumer/json_processor.rb
|
83
97
|
- lib/govuk_message_queue_consumer/message.rb
|
98
|
+
- lib/govuk_message_queue_consumer/null_statsd.rb
|
84
99
|
- lib/govuk_message_queue_consumer/rabbitmq_config.rb
|
85
100
|
- lib/govuk_message_queue_consumer/test_helpers.rb
|
86
101
|
- lib/govuk_message_queue_consumer/test_helpers/mock_message.rb
|
@@ -88,6 +103,7 @@ files:
|
|
88
103
|
- lib/govuk_message_queue_consumer/test_helpers/test_consumer.rb
|
89
104
|
- lib/govuk_message_queue_consumer/version.rb
|
90
105
|
- spec/consumer_spec.rb
|
106
|
+
- spec/consumer_with_statsd_spec.rb
|
91
107
|
- spec/heartbeat_processor_spec.rb
|
92
108
|
- spec/json_processor_spec.rb
|
93
109
|
- spec/message_spec.rb
|
@@ -122,6 +138,8 @@ test_files:
|
|
122
138
|
- spec/spec_helper.rb
|
123
139
|
- spec/message_spec.rb
|
124
140
|
- spec/consumer_spec.rb
|
141
|
+
- spec/consumer_with_statsd_spec.rb
|
125
142
|
- spec/mock_message_spec.rb
|
126
143
|
- spec/json_processor_spec.rb
|
127
144
|
- spec/rabbitmq_config_spec.rb
|
145
|
+
has_rdoc:
|