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.
@@ -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
- # include_header => false
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, :default => true
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 subscriber settings.
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
- # This will read from the queue/topic until :timeout is breached, or messages are available whichever comes
233
- # first.
234
- subscriber.each({:timeout => @interval * 1000}) do |message|
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 = LogStash::Event.new
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
- unless msg.to_s.nil?
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 ||= LogStash::Event.new
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 @include_header
283
- msg.attributes && msg.attributes.each do |field, value|
284
- event.set(field.to_s, value) unless @skip_headers.include?(field.to_s)
285
- end
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
- msg.properties && msg.properties.each do |field, value|
290
- event.set(field.to_s, value) unless @skip_properties.include?(field.to_s)
291
- end
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
@@ -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.0'
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