govuk_message_queue_consumer 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![A graph showing the message flow](docs/graph.png)
|
@@ -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:
|