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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 35f5b177e277ec11d5aea2811c4e1ef6e2dd4886
4
- data.tar.gz: 54050cbbc46013a6bcdaed24eb5c21966e99b8b8
3
+ metadata.gz: 6ea5c6560ba396d9f32d22371cd8f94240bdfbcd
4
+ data.tar.gz: fdc543d6ee7d1290e3df4db3628f497aa3151e37
5
5
  SHA512:
6
- metadata.gz: 00d5f07c06e4063bc7b06b9d52e7d96464cf59c024bd80fc7770d6780013a7b8bb587b98cc500edf0066e390055215f0eb2e55f36fb7e788d124216378d329df
7
- data.tar.gz: a93529d6ef53a6db31e199f946b51b0e7aa971291dc725915a351f3f4d09a3f788513594074beeee9ae4e2d914695713e48c3d6168ea71ee00675f47fd8e0fca
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.0.0"
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
- def initialize(queue_name:, exchange_name:, processor:, routing_key: '#')
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
@@ -0,0 +1,4 @@
1
+ class NullStatsd
2
+ def increment(_key)
3
+ end
4
+ end
@@ -1,3 +1,3 @@
1
1
  module GovukMessageQueueConsumer
2
- VERSION = '2.0.1'
2
+ VERSION = '2.1.0'
3
3
  end
@@ -1,33 +1,34 @@
1
1
  require_relative 'spec_helper'
2
+ require_relative 'support/queue_helpers'
2
3
 
3
4
  describe Consumer do
4
- let(:queue) { instance_double('Bunny::Queue', bind: nil, subscribe: '') }
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
- before do
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 "running the consumer" do
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
- expect(Message).to receive(:new).with("payload", :headers, :delivery_info_object)
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
@@ -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.1
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-02-15 00:00:00.000000000 Z
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: