logstash-input-jms 3.1.0-java → 3.2.1-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/README.md +1 -1
- data/docs/index.asciidoc +143 -15
- data/lib/logstash/inputs/jms.rb +219 -28
- data/logstash-input-jms.gemspec +8 -5
- data/spec/inputs/integration/jms_spec.rb +153 -63
- data/spec/inputs/spec_helper.rb +37 -1
- data/spec/inputs/unit/jms_spec.rb +66 -10
- metadata +50 -9
data/lib/logstash/inputs/jms.rb
CHANGED
@@ -4,13 +4,18 @@ require "logstash/inputs/threadable"
|
|
4
4
|
require 'java'
|
5
5
|
require "logstash/namespace"
|
6
6
|
|
7
|
+
require 'logstash/plugin_mixins/ecs_compatibility_support'
|
8
|
+
require 'logstash/plugin_mixins/ecs_compatibility_support/target_check'
|
9
|
+
require 'logstash/plugin_mixins/event_support/event_factory_adapter'
|
10
|
+
require 'logstash/plugin_mixins/validator_support/field_reference_validation_adapter'
|
11
|
+
|
7
12
|
# Read events from a Jms Broker. Supports both Jms Queues and Topics.
|
8
13
|
#
|
9
14
|
# For more information about Jms, see <http://docs.oracle.com/javaee/6/tutorial/doc/bncdq.html>
|
10
15
|
# For more information about the Ruby Gem used, see <http://github.com/reidmorrison/jruby-jms>
|
11
16
|
# Here is a config example to pull from a queue:
|
12
17
|
# jms {
|
13
|
-
#
|
18
|
+
# include_headers => false
|
14
19
|
# include_properties => false
|
15
20
|
# include_body => true
|
16
21
|
# use_jms_timestamp => false
|
@@ -23,6 +28,12 @@ require "logstash/namespace"
|
|
23
28
|
#
|
24
29
|
#
|
25
30
|
class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
31
|
+
|
32
|
+
include LogStash::PluginMixins::ECSCompatibilitySupport(:disabled, :v1, :v8 => :v1)
|
33
|
+
include LogStash::PluginMixins::EventSupport::EventFactoryAdapter
|
34
|
+
|
35
|
+
extend LogStash::PluginMixins::ValidatorSupport::FieldReferenceValidationAdapter
|
36
|
+
|
26
37
|
config_name "jms"
|
27
38
|
|
28
39
|
# A JMS message has three parts :
|
@@ -32,7 +43,8 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
32
43
|
# You can tell the input plugin which parts should be included in the event produced by Logstash
|
33
44
|
#
|
34
45
|
# Include JMS Message Header Field values in the event
|
35
|
-
config :include_header, :validate => :boolean, :
|
46
|
+
config :include_header, :validate => :boolean, :deprecated => "Set 'include_headers => ...' instead"
|
47
|
+
config :include_headers, :validate => :boolean, :default => true
|
36
48
|
# Include JMS Message Properties Field values in the event
|
37
49
|
config :include_properties, :validate => :boolean, :default => true
|
38
50
|
|
@@ -48,12 +60,12 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
48
60
|
# If the JMS Message is a MapMessage, then all the key/value pairs will be added in the Hashmap of the event
|
49
61
|
# StreamMessage and ObjectMessage are not supported
|
50
62
|
|
51
|
-
# Receive Oracle AQ buffered messages.
|
63
|
+
# Receive Oracle AQ buffered messages.
|
52
64
|
# In this mode persistent Oracle AQ JMS messages will not be received.
|
53
65
|
config :oracle_aq_buffered_messages, :validate => :boolean, :default => false
|
54
66
|
|
55
67
|
config :include_body, :validate => :boolean, :default => true
|
56
|
-
|
68
|
+
|
57
69
|
# Convert the JMSTimestamp header field to the @timestamp value of the event
|
58
70
|
config :use_jms_timestamp, :validate => :boolean, :default => false
|
59
71
|
|
@@ -72,7 +84,7 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
72
84
|
# If pub-sub (topic) style should be used.
|
73
85
|
config :pub_sub, :validate => :boolean, :default => false
|
74
86
|
|
75
|
-
# Durable
|
87
|
+
# Durable message_consumer settings.
|
76
88
|
# By default the `durable_subscriber_name` will be set to the topic, and `durable_subscriber_client_id` will be set
|
77
89
|
# to 'Logstash'
|
78
90
|
config :durable_subscriber, :validate => :boolean, :default => false
|
@@ -122,6 +134,15 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
122
134
|
config :truststore, :validate => :path
|
123
135
|
config :truststore_password, :validate => :password
|
124
136
|
|
137
|
+
# Defines a target field for placing fields.
|
138
|
+
# If this setting is omitted, data gets stored at the root (top level) of the event.
|
139
|
+
# The target is only relevant while decoding data into a new event.
|
140
|
+
#
|
141
|
+
# NOTE: this is only relevant for map messages; byte[] and string use the codec!
|
142
|
+
config :target, :validate => :field_reference
|
143
|
+
|
144
|
+
config :headers_target, :validate => :field_reference # ECS default: [@metadata][input][jms][headers]
|
145
|
+
config :properties_target, :validate => :field_reference # ECS default: [@metadata][input][jms][properties]
|
125
146
|
|
126
147
|
# :yaml_file, :factory and :jndi_name are mutually exclusive, both cannot be supplied at the
|
127
148
|
# same time. The priority order is :yaml_file, then :jndi_name, then :factory
|
@@ -132,10 +153,25 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
132
153
|
# For some known examples, see: [Example jms.yml](https://github.com/reidmorrison/jruby-jms/blob/master/examples/jms.yml)
|
133
154
|
|
134
155
|
public
|
156
|
+
|
157
|
+
def initialize(*params)
|
158
|
+
super
|
159
|
+
|
160
|
+
if ecs_compatibility != :disabled # set ECS target defaults
|
161
|
+
@headers_target = '[@metadata][input][jms][headers]' unless original_params.include?('headers_target')
|
162
|
+
@properties_target = '[@metadata][input][jms][properties]' unless original_params.include?('properties_target')
|
163
|
+
end
|
164
|
+
|
165
|
+
@headers_setter = event_setter_for(@headers_target)
|
166
|
+
@properties_setter = event_setter_for(@properties_target)
|
167
|
+
|
168
|
+
@headers_mapper = ecs_compatibility == :disabled ? LegacyHeadersMapper::INSTANCE : HeadersMapper::INSTANCE
|
169
|
+
end
|
170
|
+
|
135
171
|
def register
|
136
172
|
require "jms"
|
137
173
|
|
138
|
-
check_config
|
174
|
+
check_config!
|
139
175
|
load_ssl_properties
|
140
176
|
load_system_properties if @system_properties
|
141
177
|
@jms_config = jms_config
|
@@ -208,7 +244,15 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
208
244
|
@system_properties.each { |k,v| java.lang.System.set_property(k,v.to_s) }
|
209
245
|
end
|
210
246
|
|
211
|
-
def check_config
|
247
|
+
def check_config!
|
248
|
+
if original_params.include?('include_header')
|
249
|
+
if original_params.include?('include_headers')
|
250
|
+
raise(LogStash::ConfigurationError, "Both `include_headers => #{include_headers}` and `include_header => #{include_header}`" +
|
251
|
+
" options are specified, please only set one")
|
252
|
+
end
|
253
|
+
@include_headers = include_header # only `include_header => ...` was set
|
254
|
+
end
|
255
|
+
|
212
256
|
check_durable_subscription_config
|
213
257
|
raise(LogStash::ConfigurationError, "Threads cannot be > 1 if pub_sub is set") if @threads > 1 && @pub_sub
|
214
258
|
end
|
@@ -229,9 +273,9 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
229
273
|
params = {:timeout => @timeout * 1000, :selector => @selector}
|
230
274
|
subscriber = subscriber(session, params)
|
231
275
|
until stop?
|
232
|
-
#
|
233
|
-
# first
|
234
|
-
subscriber
|
276
|
+
# read from the queue/topic until :timeout is reached, or a message is available
|
277
|
+
# (whichever comes first)
|
278
|
+
do_receive_message(subscriber, timeout: @interval * 1000) do |message|
|
235
279
|
queue_event(message, output_queue)
|
236
280
|
break if stop?
|
237
281
|
end
|
@@ -252,43 +296,35 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
252
296
|
end
|
253
297
|
end # def run_consumer
|
254
298
|
|
255
|
-
|
256
299
|
def queue_event(msg, output_queue)
|
257
300
|
begin
|
258
301
|
if @include_body
|
259
302
|
if msg.java_kind_of?(JMS::MapMessage)
|
260
|
-
event =
|
261
|
-
msg.data.each do |field, value|
|
262
|
-
event.set(field.to_s, value) # TODO(claveau): needs codec.decode or converter.convert ?
|
263
|
-
end
|
303
|
+
event = process_map_message(msg)
|
264
304
|
elsif msg.java_kind_of?(JMS::TextMessage) || msg.java_kind_of?(JMS::BytesMessage)
|
265
|
-
|
266
|
-
@codec.decode(msg.to_s) do |event_message|
|
267
|
-
event = event_message
|
268
|
-
end
|
269
|
-
end
|
305
|
+
event = decode_message(msg)
|
270
306
|
else
|
271
307
|
@logger.error( "Unsupported message type #{msg.data.class.to_s}" )
|
272
308
|
end
|
273
309
|
end
|
274
310
|
|
275
|
-
event ||=
|
311
|
+
event ||= event_factory.new_event
|
276
312
|
|
277
313
|
# Here, we can use the JMS Enqueue timestamp as the @timestamp
|
278
314
|
if @use_jms_timestamp && msg.jms_timestamp
|
279
315
|
event.set("@timestamp", LogStash::Timestamp.at(msg.jms_timestamp / 1000, (msg.jms_timestamp % 1000) * 1000))
|
280
316
|
end
|
281
317
|
|
282
|
-
if @
|
283
|
-
|
284
|
-
|
285
|
-
|
318
|
+
if @include_headers
|
319
|
+
headers = map_headers(msg)
|
320
|
+
@skip_headers.each { |key| headers.delete(key) }
|
321
|
+
@headers_setter.call(event, headers)
|
286
322
|
end
|
287
323
|
|
288
324
|
if @include_properties
|
289
|
-
|
290
|
-
|
291
|
-
|
325
|
+
properties = to_string_keyed_hash(msg.properties)
|
326
|
+
@skip_properties.each { |key| properties.delete(key) }
|
327
|
+
@properties_setter.call(event, properties)
|
292
328
|
end
|
293
329
|
|
294
330
|
decorate(event)
|
@@ -300,6 +336,63 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
300
336
|
end
|
301
337
|
end
|
302
338
|
|
339
|
+
# Loop through messages received and yield them.
|
340
|
+
#
|
341
|
+
# NOTE: a simplified replacement for JMS::MessageConsumer#each and #get (extensions).
|
342
|
+
#
|
343
|
+
# @param message_consumer [javax.jms.MessageConsumer]
|
344
|
+
# @param timeout in milliseconds
|
345
|
+
def do_receive_message(message_consumer, timeout: 0)
|
346
|
+
# Receive messages according to timeout
|
347
|
+
while true
|
348
|
+
case timeout
|
349
|
+
when 0 # Return immediately if no message is available
|
350
|
+
message = message_consumer.receiveNoWait()
|
351
|
+
when -1 # Wait forever
|
352
|
+
message = message_consumer.receive()
|
353
|
+
else # Wait for x milli-seconds for a message to be received from the broker
|
354
|
+
message = message_consumer.receive(timeout)
|
355
|
+
end
|
356
|
+
break unless message
|
357
|
+
yield message
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def to_string_keyed_hash(hash)
|
362
|
+
hash.inject({}) { |h, (key, val)| h[key.to_s] = val; h }
|
363
|
+
end
|
364
|
+
private :to_string_keyed_hash
|
365
|
+
|
366
|
+
# @param msg [JMS::MapMessage]
|
367
|
+
# @return [LogStash::Event]
|
368
|
+
def process_map_message(msg)
|
369
|
+
data = to_string_keyed_hash(msg.data)
|
370
|
+
do_target_check_once_and_get_event_factory.new_event(data)
|
371
|
+
end
|
372
|
+
|
373
|
+
def do_target_check_once_and_get_event_factory
|
374
|
+
@target_checked ||= begin
|
375
|
+
do_target_check
|
376
|
+
targeted_event_factory
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
TARGET_NOT_SET_MESSAGE = LogStash::PluginMixins::ECSCompatibilitySupport::TargetCheck::TARGET_NOT_SET_MESSAGE
|
381
|
+
|
382
|
+
def do_target_check
|
383
|
+
return true unless target.nil?
|
384
|
+
return nil if ecs_compatibility == :disabled
|
385
|
+
logger.info(TARGET_NOT_SET_MESSAGE) # target isn't set in ECS mode
|
386
|
+
end
|
387
|
+
|
388
|
+
# @param msg [JMS::TextMessage, JMS::BytesMessage]
|
389
|
+
# @return [LogStash::Event, nil]
|
390
|
+
def decode_message(msg)
|
391
|
+
text = msg.to_s # javax.jms.TextMessage#getText (e.g. JSON payload)
|
392
|
+
event = nil
|
393
|
+
@codec.decode(text) { |e| event = e } unless text.nil?
|
394
|
+
event
|
395
|
+
end
|
303
396
|
|
304
397
|
def subscriber(session, params)
|
305
398
|
destination_key = @pub_sub ? :topic_name : :queue_name
|
@@ -347,4 +440,102 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
|
|
347
440
|
end
|
348
441
|
{:exception => cause.class.name, :exception_message => cause.message }
|
349
442
|
end
|
443
|
+
|
444
|
+
private
|
445
|
+
|
446
|
+
def normalize_field_ref(target)
|
447
|
+
# so we can later event.set("#{target}[#{name}]", ...)
|
448
|
+
target.match?(/\A[^\[\]]+\z/) ? "[#{target}]" : target
|
449
|
+
end
|
450
|
+
|
451
|
+
def event_setter_for(target)
|
452
|
+
if target.nil? || target.empty?
|
453
|
+
TOP_LEVEL_EVENT_SETTER
|
454
|
+
else
|
455
|
+
TargetEventSetter.new normalize_field_ref(target)
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
TOP_LEVEL_EVENT_SETTER = lambda { |event, data| data.each { |key, val| event.set(key, val) } }
|
460
|
+
private_constant :TOP_LEVEL_EVENT_SETTER
|
461
|
+
|
462
|
+
class TargetEventSetter
|
463
|
+
|
464
|
+
def initialize(target)
|
465
|
+
@target = target
|
466
|
+
end
|
467
|
+
|
468
|
+
def call(event, data)
|
469
|
+
data.each { |key, val| event.set("#{@target}[#{key}]", val) }
|
470
|
+
end
|
471
|
+
|
472
|
+
end
|
473
|
+
private_constant :TargetEventSetter
|
474
|
+
|
475
|
+
def map_headers(msg)
|
476
|
+
@headers_mapper.call(msg)
|
477
|
+
end
|
478
|
+
|
479
|
+
# Maps JMS headers names
|
480
|
+
# (similar to JMS gem's Message#attributes extension)
|
481
|
+
class HeadersMapper
|
482
|
+
|
483
|
+
# @param msg [javax.jms.Message]
|
484
|
+
# @return [Hash]
|
485
|
+
def call(msg)
|
486
|
+
map = {
|
487
|
+
'jms_message_id' => msg.getJMSMessageID, # String
|
488
|
+
'jms_timestamp' => msg.getJMSTimestamp, # long
|
489
|
+
'jms_expiration' => msg.getJMSExpiration, # long
|
490
|
+
'jms_priority' => msg.getJMSPriority, # int (0-9)
|
491
|
+
'jms_type' => msg.getJMSType, # String
|
492
|
+
'jms_redelivered' => msg.getJMSRedelivered, # boolean
|
493
|
+
}
|
494
|
+
|
495
|
+
correlation_id = msg.getJMSCorrelationID # String
|
496
|
+
map['jms_correlation_id'] = correlation_id unless correlation_id.nil?
|
497
|
+
|
498
|
+
delivery_mode = jms_delivery_mode(msg)
|
499
|
+
map['jms_delivery_mode'] = delivery_mode unless delivery_mode.nil?
|
500
|
+
|
501
|
+
destination = msg.getJMSDestination # javax.jms.Destination
|
502
|
+
map['jms_destination'] = destination.to_string unless destination.nil?
|
503
|
+
|
504
|
+
reply_to = msg.getJMSReplyTo # javax.jms.Destination
|
505
|
+
map['jms_reply_to'] = reply_to.to_string unless reply_to.nil?
|
506
|
+
|
507
|
+
map
|
508
|
+
end
|
509
|
+
|
510
|
+
private
|
511
|
+
|
512
|
+
def jms_delivery_mode(msg)
|
513
|
+
case msg.getJMSDeliveryMode
|
514
|
+
when javax.jms.DeliveryMode::PERSISTENT
|
515
|
+
'persistent'
|
516
|
+
when javax.jms.DeliveryMode::NON_PERSISTENT
|
517
|
+
'non_persistent'
|
518
|
+
else
|
519
|
+
nil
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
INSTANCE = self.new
|
524
|
+
|
525
|
+
end
|
526
|
+
|
527
|
+
# For plugin compatibility due the use of JMS gem's Message#attributes.
|
528
|
+
class LegacyHeadersMapper < HeadersMapper
|
529
|
+
|
530
|
+
def call(msg)
|
531
|
+
map = super(msg)
|
532
|
+
delivery_mode = jms_delivery_mode(msg)
|
533
|
+
map['jms_delivery_mode_sym'] = delivery_mode ? delivery_mode.to_sym : nil
|
534
|
+
map
|
535
|
+
end
|
536
|
+
|
537
|
+
INSTANCE = self.new
|
538
|
+
|
539
|
+
end
|
540
|
+
|
350
541
|
end # class LogStash::Inputs::Jms
|
data/logstash-input-jms.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
|
3
3
|
s.name = 'logstash-input-jms'
|
4
|
-
s.version = '3.1
|
4
|
+
s.version = '3.2.1'
|
5
5
|
s.licenses = ['Apache License (2.0)']
|
6
6
|
s.summary = "Reads events from a Jms Broker"
|
7
7
|
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
|
@@ -19,15 +19,18 @@ Gem::Specification.new do |s|
|
|
19
19
|
# Special flag to let us know this is actually a logstash plugin
|
20
20
|
s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
|
21
21
|
|
22
|
+
s.platform = RUBY_PLATFORM
|
23
|
+
|
22
24
|
# Gem dependencies
|
23
25
|
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
24
26
|
s.add_runtime_dependency 'logstash-codec-json', '~> 3.0'
|
25
27
|
s.add_runtime_dependency 'logstash-codec-plain', '~> 3.0'
|
28
|
+
s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~> 1.3'
|
29
|
+
s.add_runtime_dependency "logstash-mixin-event_support", '~> 1.0'
|
30
|
+
s.add_runtime_dependency 'logstash-mixin-validator_support', '~> 1.0'
|
31
|
+
|
32
|
+
s.add_runtime_dependency "jruby-jms", ">= 1.2.0" #(Apache 2.0 license)
|
26
33
|
s.add_runtime_dependency 'semantic_logger', '< 4.0.0'
|
27
34
|
|
28
|
-
if RUBY_PLATFORM == 'java'
|
29
|
-
s.platform = RUBY_PLATFORM
|
30
|
-
s.add_runtime_dependency "jruby-jms", ">= 1.2.0" #(Apache 2.0 license)
|
31
|
-
end
|
32
35
|
s.add_development_dependency 'logstash-devutils'
|
33
36
|
end
|