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.
@@ -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