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 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: