logstash-input-jms 3.1.0-java → 3.2.1-java
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 +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
|