logstash-input-rabbitmq 3.1.5 → 3.2.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: f93375b1eaa4816cd3abbef1aeaa4f6d4f80045f
4
- data.tar.gz: 4f06a024d67fcc9da7e5c8a4a1fb1ee2aa44ea2b
3
+ metadata.gz: 5caab21a9bb09f1d4af8660f26558c9bde6e9968
4
+ data.tar.gz: 522d2d66e0c768a06dc14f7dea3e38cc3e4419c0
5
5
  SHA512:
6
- metadata.gz: a0da602e8f34599aca96f97057a59bb9f2a12b67c5b37c72f5c08a5fdfcf9b5a7f2272840235acd31e4dd994ff1d673a247f339560782db5944a5b41221a8ff7
7
- data.tar.gz: 6cc9ab957d7e94ef0dd154a3e3d920d58d4e4dcb603a5589860a60c68f7850eee63ead3aa0a3d08c8cc9178863c64aba44f78d2577481d2141658f654f3c58ed
6
+ metadata.gz: 1d8a4404784a9de832e23c4b1c9272a87997fbeb4fc5984380c27e6c52a1e8f3661f170e8909eb59bfe7fad15c7af4303e327e44da23ce89632e65a737d1b47b
7
+ data.tar.gz: 5f01768900d95ddba5259bff24c01856297684c3a46642d78643c96b16d2f508eb37052cb309bc13d8829a6ca3154d97275d85ab96e0c4e22240be6fce2039d7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 3.2.0
2
+ - The properties and headers of the messages are now saved in the [@metadata][rabbitmq_headers] and [@metadata][rabbitmq_properties] fields.
3
+ - Logstash now shuts down if the server sends a basic.cancel method.
4
+ - Reinstating the overview documentation that was lost in 3.0.0 and updating it to be clearer.
5
+ - Internal: Various spec improvements that decrease flakiness and eliminate the queue littering of the integration test RabbitMQ instance.
6
+
1
7
  ## 3.1.5
2
8
  - Fix a bug where when reconnecting a duplicate consumer would be created
3
9
 
@@ -4,15 +4,101 @@ require 'logstash/inputs/threadable'
4
4
 
5
5
  module LogStash
6
6
  module Inputs
7
+ # Pull events from a http://www.rabbitmq.com/[RabbitMQ] queue.
8
+ #
9
+ # The default settings will create an entirely transient queue and listen for all messages by default.
10
+ # If you need durability or any other advanced settings, please set the appropriate options
11
+ #
12
+ # This plugin uses the http://rubymarchhare.info/[March Hare] library
13
+ # for interacting with the RabbitMQ server. Most configuration options
14
+ # map directly to standard RabbitMQ and AMQP concepts. The
15
+ # https://www.rabbitmq.com/amqp-0-9-1-reference.html[AMQP 0-9-1 reference guide]
16
+ # and other parts of the RabbitMQ documentation are useful for deeper
17
+ # understanding.
18
+ #
19
+ # The properties of messages received will be stored in the
20
+ # `[@metadata][rabbitmq_properties]` field. The following
21
+ # properties may be available (in most cases dependent on whether
22
+ # they were set by the sender):
23
+ #
24
+ # * app-id
25
+ # * cluster-id
26
+ # * consumer-tag
27
+ # * content-encoding
28
+ # * content-type
29
+ # * correlation-id
30
+ # * delivery-mode
31
+ # * exchange
32
+ # * expiration
33
+ # * message-id
34
+ # * priority
35
+ # * redeliver
36
+ # * reply-to
37
+ # * routing-key
38
+ # * timestamp
39
+ # * type
40
+ # * user-id
41
+ #
42
+ # For example, to get the RabbitMQ message's timestamp property
43
+ # into the Logstash event's `@timestamp` field, use the date
44
+ # filter to parse the `[@metadata][rabbitmq_properties][timestamp]`
45
+ # field:
46
+ # [source,ruby]
47
+ # filter {
48
+ # if [@metadata][rabbitmq_properties][timestamp] {
49
+ # date {
50
+ # match => ["[@metadata][rabbitmq_properties][timestamp]", "UNIX"]
51
+ # }
52
+ # }
53
+ # }
54
+ #
55
+ # Additionally, any message headers will be saved in the
56
+ # `[@metadata][rabbitmq_headers]` field.
7
57
  class RabbitMQ < LogStash::Inputs::Threadable
8
58
  include ::LogStash::PluginMixins::RabbitMQConnection
9
59
 
60
+ # The properties to extract from each message and store in a
61
+ # @metadata field.
62
+ #
63
+ # Technically the exchange, redeliver, and routing-key
64
+ # properties belong to the envelope and not the message but we
65
+ # ignore that distinction here. However, we extract the
66
+ # headers separately via get_headers even though the header
67
+ # table technically is a message property.
68
+ #
69
+ # Freezing all strings so that code modifying the event's
70
+ # @metadata field can't touch them.
71
+ #
72
+ # If updating this list, remember to update the documentation
73
+ # above too.
74
+ MESSAGE_PROPERTIES = [
75
+ "app-id",
76
+ "cluster-id",
77
+ "consumer-tag",
78
+ "content-encoding",
79
+ "content-type",
80
+ "correlation-id",
81
+ "delivery-mode",
82
+ "exchange",
83
+ "expiration",
84
+ "message-id",
85
+ "priority",
86
+ "redeliver",
87
+ "reply-to",
88
+ "routing-key",
89
+ "timestamp",
90
+ "type",
91
+ "user-id",
92
+ ].map { |s| s.freeze }.freeze
93
+
10
94
  config_name "rabbitmq"
11
95
 
12
96
  # The default codec for this plugin is JSON. You can override this to suit your particular needs however.
13
97
  default :codec, "json"
14
98
 
15
- # The name of the queue Logstash will consume events from.
99
+ # The name of the queue Logstash will consume events from. If
100
+ # left empty, a transient queue with an randomly chosen name
101
+ # will be created.
16
102
  config :queue, :validate => :string, :default => ""
17
103
 
18
104
  # Is this queue durable? (aka; Should it survive a broker restart?)
@@ -33,13 +119,24 @@ module LogStash
33
119
  # To make a RabbitMQ queue mirrored, use: `{"x-ha-policy" => "all"}`
34
120
  config :arguments, :validate => :array, :default => {}
35
121
 
36
- # Prefetch count. Number of messages to prefetch
122
+ # Prefetch count. If acknowledgements are enabled with the `ack`
123
+ # option, specifies the number of outstanding unacknowledged
124
+ # messages allowed. With acknowledgemnts disabled this setting
125
+ # has no effect.
37
126
  config :prefetch_count, :validate => :number, :default => 256
38
127
 
39
- # Enable message acknowledgement
128
+ # Enable message acknowledgements. With acknowledgements
129
+ # messages fetched by Logstash but not yet sent into the
130
+ # Logstash pipeline will be requeued by the server if Logstash
131
+ # shuts down. Acknowledgements will however hurt the message
132
+ # throughput.
40
133
  config :ack, :validate => :boolean, :default => true
41
134
 
42
- # Passive queue creation? Useful for checking queue existance without modifying server state
135
+ # If true the queue will be passively declared, meaning it must
136
+ # already exist on the server. To have Logstash create the queue
137
+ # if necessary leave this option as false. If actively declaring
138
+ # a queue that already exists, the queue options for this plugin
139
+ # (durable etc) must match those of the existing queue.
43
140
  config :passive, :validate => :boolean, :default => false
44
141
 
45
142
  # The name of the exchange to bind the queue to.
@@ -53,7 +150,7 @@ module LogStash
53
150
  config :key, :validate => :string, :default => "logstash"
54
151
 
55
152
  # Amount of time in seconds to wait after a failed subscription request
56
- # before retrying. Subscribes can fail if the server goes away and then comes back
153
+ # before retrying. Subscribes can fail if the server goes away and then comes back.
57
154
  config :subscription_retry_interval_seconds, :validate => :number, :required => true, :default => 5
58
155
 
59
156
  def register
@@ -102,9 +199,12 @@ module LogStash
102
199
  # that we rely on MarchHare to do the reconnection for us with auto_reconnect.
103
200
  # Unfortunately, while MarchHare does the reconnection work it won't re-subscribe the consumer
104
201
  # hence the logic below.
105
- @consumer = @hare_info.queue.build_consumer() do |metadata, data|
202
+ @consumer = @hare_info.queue.build_consumer(:block => true,
203
+ :on_cancellation => Proc.new { on_cancellation }) do |metadata, data|
106
204
  @codec.decode(data) do |event|
107
205
  decorate(event)
206
+ event["@metadata"]["rabbitmq_headers"] = get_headers(metadata)
207
+ event["@metadata"]["rabbitmq_properties"] = get_properties(metadata)
108
208
  @output_queue << event if event
109
209
  end
110
210
  @hare_info.channel.ack(metadata.delivery_tag) if @ack
@@ -135,6 +235,59 @@ module LogStash
135
235
  @consumer.gracefully_shut_down
136
236
  end
137
237
 
238
+ def on_cancellation
239
+ @logger.info("Received basic.cancel from #{rabbitmq_settings[:host]}, shutting down.")
240
+ stop
241
+ end
242
+
243
+ private
244
+
245
+ # ByteArrayLongString is a private static inner class which
246
+ # can't be access via the regular Java::SomeNameSpace::Classname
247
+ # notation. See https://github.com/jruby/jruby/issues/3333.
248
+ ByteArrayLongString = JavaUtilities::get_proxy_class('com.rabbitmq.client.impl.LongStringHelper$ByteArrayLongString')
249
+
250
+ def get_header_value(value)
251
+ # Two kinds of values require exceptional treatment:
252
+ #
253
+ # String values are instances of
254
+ # com.rabbitmq.client.impl.LongStringHelper.ByteArrayLongString
255
+ # and we don't want to propagate those.
256
+ #
257
+ # List values are java.util.ArrayList objects and we need to
258
+ # recurse into them to convert any nested strings values.
259
+ if value.class == Java::JavaUtil::ArrayList
260
+ value.map{|item| get_header_value(item) }
261
+ elsif value.class == ByteArrayLongString
262
+ value.toString
263
+ else
264
+ value
265
+ end
266
+ end
267
+
268
+ private
269
+ def get_headers(metadata)
270
+ if !metadata.headers.nil?
271
+ Hash[metadata.headers.map {|k, v| [k, get_header_value(v)]}]
272
+ else
273
+ {}
274
+ end
275
+ end
276
+
277
+ private
278
+ def get_properties(metadata)
279
+ MESSAGE_PROPERTIES.reduce({}) do |acc, name|
280
+ # The method names obviously can't contain hyphens.
281
+ value = metadata.send(name.gsub("-", "_"))
282
+ if value
283
+ # The AMQP 0.9.1 timestamp field only has second resolution
284
+ # so storing milliseconds serves no purpose and might give
285
+ # the incorrect impression of a higher resolution.
286
+ acc[name] = name != "timestamp" ? value : value.getTime / 1000
287
+ end
288
+ acc
289
+ end
290
+ end
138
291
  end
139
292
  end
140
293
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-input-rabbitmq'
3
- s.version = '3.1.5'
3
+ s.version = '3.2.0'
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = "Pull events from a RabbitMQ exchange."
6
6
  s.description = "This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program"
@@ -112,7 +112,7 @@ end
112
112
 
113
113
  describe "with a live server", :integration => true do
114
114
  let(:klass) { LogStash::Inputs::RabbitMQ }
115
- let(:config) { {"host" => "127.0.0.1"} }
115
+ let(:config) { {"host" => "127.0.0.1", "auto_delete" => true, "codec" => "plain" } }
116
116
  let(:instance) { klass.new(config) }
117
117
  let(:hare_info) { instance.instance_variable_get(:@hare_info) }
118
118
  let(:output_queue) { Queue.new }
@@ -134,7 +134,7 @@ describe "with a live server", :integration => true do
134
134
  # Extra time to make sure the consumer can attach
135
135
  # Without this there's a chance the shutdown code will execute
136
136
  # before consumption begins. This is tricky to do more elegantly
137
- sleep 1
137
+ sleep 4
138
138
  end
139
139
 
140
140
  let(:test_connection) { MarchHare.connect(instance.send(:rabbitmq_settings)) }
@@ -166,21 +166,85 @@ describe "with a live server", :integration => true do
166
166
  end
167
167
 
168
168
  describe "receiving a message with a queue specified" do
169
- let(:queue_name) { "foo_queue" }
170
169
  let(:config) { super.merge("queue" => queue_name) }
170
+ let(:event) { output_queue.pop }
171
+ let(:queue) { test_channel.queue(queue_name, :auto_delete => true) }
172
+ let(:queue_name) { "logstash-input-rabbitmq-#{rand(0xFFFFFFFF)}" }
171
173
 
172
- it "should process the message" do
173
- message = "Foo Message"
174
- q = test_channel.queue(queue_name)
175
- q.publish(message)
174
+ context "when the message has a payload but no message headers" do
175
+ before do
176
+ queue.publish(message)
177
+ end
178
+
179
+ let(:message) { "Foo Message" }
180
+
181
+ it "should process the message and store the payload" do
182
+ expect(event["message"]).to eql(message)
183
+ end
176
184
 
177
- event = output_queue.pop
178
- expect(event["message"]).to eql(message)
185
+ it "should save an empty message header hash" do
186
+ expect(event).to include("@metadata")
187
+ expect(event["@metadata"]).to include("rabbitmq_headers")
188
+ expect(event["@metadata"]["rabbitmq_headers"]).to eq({})
189
+ end
190
+ end
191
+
192
+ context "when message properties are available" do
193
+ before do
194
+ # Don't test every single property but select a few with
195
+ # different characteristics to get sufficient coverage.
196
+ queue.publish("",
197
+ :properties => {
198
+ :app_id => app_id,
199
+ :timestamp => Java::JavaUtil::Date.new(epoch * 1000),
200
+ :priority => priority,
201
+ })
202
+ end
203
+
204
+ let(:app_id) { "myapplication" }
205
+ # Randomize the epoch we test with but limit its range to signed
206
+ # ints to not assume all protocols and libraries involved use
207
+ # unsigned ints for epoch values.
208
+ let(:epoch) { rand(0x7FFFFFFF) }
209
+ let(:priority) { 5 }
210
+
211
+ it "should save message properties into a @metadata field" do
212
+ expect(event).to include("@metadata")
213
+ expect(event["@metadata"]).to include("rabbitmq_properties")
214
+
215
+ props = event["@metadata"]["rabbitmq_properties"]
216
+ expect(props["app-id"]).to eq(app_id)
217
+ expect(props["delivery-mode"]).to eq(1)
218
+ expect(props["exchange"]).to eq("")
219
+ expect(props["priority"]).to eq(priority)
220
+ expect(props["routing-key"]).to eq(queue_name)
221
+ expect(props["timestamp"]).to eq(epoch)
222
+ end
223
+ end
224
+
225
+ context "when message headers are available" do
226
+ before do
227
+ queue.publish("", :properties => { :headers => headers })
228
+ end
229
+
230
+ let (:headers) {
231
+ {
232
+ "arrayvalue" => [true, 123, "foo"],
233
+ "boolvalue" => true,
234
+ "intvalue" => 123,
235
+ "stringvalue" => "foo",
236
+ }
237
+ }
238
+
239
+ it "should save message headers into a @metadata field" do
240
+ expect(event).to include("@metadata")
241
+ expect(event["@metadata"]).to include("rabbitmq_headers")
242
+ expect(event["@metadata"]["rabbitmq_headers"]).to include(headers)
243
+ end
179
244
  end
180
245
  end
181
246
 
182
247
  describe LogStash::Inputs::RabbitMQ do
183
- let(:config) { super.merge("queue" => "foo_queue") }
184
248
  it_behaves_like "an interruptible input plugin" do
185
249
 
186
250
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-rabbitmq
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.5
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-28 00:00:00.000000000 Z
11
+ date: 2016-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logstash-core