logstash-input-jms 3.0.6-java → 3.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/inputs/base"
3
3
  require "logstash/inputs/threadable"
4
+ require 'java'
4
5
  require "logstash/namespace"
5
6
 
6
7
  # Read events from a Jms Broker. Supports both Jms Queues and Topics.
@@ -34,6 +35,13 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
34
35
  config :include_header, :validate => :boolean, :default => true
35
36
  # Include JMS Message Properties Field values in the event
36
37
  config :include_properties, :validate => :boolean, :default => true
38
+
39
+ # List of headers to skip from the event if headers are included
40
+ config :skip_headers, :validate => :array, :default => []
41
+
42
+ # List of properties to skip from the event if properties are included
43
+ config :skip_properties, :validate => :array, :default => []
44
+
37
45
  # Include JMS Message Body in the event
38
46
  # Supports TextMessage, MapMessage and ByteMessage
39
47
  # If the JMS Message is a TextMessage or ByteMessage, then the value will be in the "message" field of the event
@@ -50,7 +58,7 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
50
58
  config :use_jms_timestamp, :validate => :boolean, :default => false
51
59
 
52
60
  # Choose an implementation of the run block. Value can be either consumer, async or thread
53
- config :runner, :validate => [ "consumer", "async", "thread" ], :default => "consumer"
61
+ config :runner, :deprecated => true
54
62
 
55
63
  # Set the selector to use to get messages off the queue or topic
56
64
  config :selector, :validate => :string
@@ -59,13 +67,18 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
59
67
  config :timeout, :validate => :number, :default => 60
60
68
 
61
69
  # Polling interval in seconds.
62
- # This is the time sleeping between asks to a consumed Queue.
63
- # This parameter has non influence in the case of a subcribed Topic.
64
70
  config :interval, :validate => :number, :default => 10
65
71
 
66
72
  # If pub-sub (topic) style should be used.
67
73
  config :pub_sub, :validate => :boolean, :default => false
68
74
 
75
+ # Durable subscriber settings.
76
+ # By default the `durable_subscriber_name` will be set to the topic, and `durable_subscriber_client_id` will be set
77
+ # to 'Logstash'
78
+ config :durable_subscriber, :validate => :boolean, :default => false
79
+ config :durable_subscriber_client_id, :validate => :string, :required => false
80
+ config :durable_subscriber_name, :validate => :string, :required => false
81
+
69
82
  # Name of the destination queue or topic to use.
70
83
  config :destination, :validate => :string, :required => true
71
84
 
@@ -88,7 +101,7 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
88
101
  # Username to connect to JMS provider with
89
102
  config :username, :validate => :string
90
103
  # Password to use when connecting to the JMS provider
91
- config :password, :validate => :string
104
+ config :password, :validate => :password
92
105
  # Url to use when connecting to the JMS provider
93
106
  config :broker_url, :validate => :string
94
107
 
@@ -98,6 +111,18 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
98
111
  # contains details on how to connect to JNDI server
99
112
  config :jndi_context, :validate => :hash
100
113
 
114
+ # System properties
115
+ config :system_properties, :validate => :hash
116
+
117
+ # Factory settings
118
+ config :factory_settings, :validate => :hash
119
+
120
+ config :keystore, :validate => :path
121
+ config :keystore_password, :validate => :password
122
+ config :truststore, :validate => :path
123
+ config :truststore_password, :validate => :password
124
+
125
+
101
126
  # :yaml_file, :factory and :jndi_name are mutually exclusive, both cannot be supplied at the
102
127
  # same time. The priority order is :yaml_file, then :jndi_name, then :factory
103
128
  #
@@ -109,34 +134,125 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
109
134
  public
110
135
  def register
111
136
  require "jms"
112
- @connection = nil
113
137
 
114
- if @yaml_file
115
- @jms_config = YAML.load_file(@yaml_file)[@yaml_section]
138
+ check_config
139
+ load_ssl_properties
140
+ load_system_properties if @system_properties
141
+ @jms_config = jms_config
142
+
143
+ @logger.debug("JMS Config being used ", :context => obfuscate_jms_config(@jms_config))
144
+ end # def register
145
+
146
+ def obfuscate_jms_config(config)
147
+ config.each_with_object({}) { |(k, v), h| h[k] = obfuscatable?(k) ? 'xxxxx' : v }
148
+ end
149
+
150
+ def obfuscatable?(setting)
151
+ [:password, :keystore_password, :truststore_password].include?(setting)
152
+ end
153
+
154
+ def jms_config
155
+ return jms_config_from_yaml(@yaml_file, @yaml_section) if @yaml_file
156
+ return jms_config_from_jndi if @jndi_name
157
+ jms_config_from_configuration
158
+ end
116
159
 
117
- elsif @jndi_name
118
- @jms_config = {
119
- :require_jars => @require_jars,
120
- :jndi_name => @jndi_name,
121
- :jndi_context => @jndi_context}
122
160
 
123
- elsif @factory
124
- @jms_config = {
161
+ def jms_config_from_configuration
162
+ config = {
125
163
  :require_jars => @require_jars,
126
164
  :factory => @factory,
127
165
  :username => @username,
128
- :password => @password,
129
166
  :broker_url => @broker_url,
130
167
  :url => @broker_url # "broker_url" is named "url" with Oracle AQ
131
- }
168
+ }
169
+
170
+ config[:password] = @password.value unless @password.nil?
171
+ correct_factory_hash(config, @factory_settings) unless @factory_settings.nil?
172
+ config
173
+ end
174
+
175
+ def correct_factory_hash(original, value)
176
+ if hash.is_a?(String)
177
+ return true if value.downcase == "true"
178
+ return false if value.downcase == "false"
132
179
  end
133
180
 
134
- @logger.debug("JMS Config being used", :context => @jms_config)
181
+ if value.is_a?(Hash)
182
+ value.each { |key, value| original[key.to_sym] = correct_factory_hash({}, value) }
183
+ return original
184
+ end
185
+ value
186
+ end
135
187
 
136
- end # def register
188
+ def jms_config_from_jndi
189
+ {
190
+ :require_jars => @require_jars,
191
+ :jndi_name => @jndi_name,
192
+ :jndi_context => @jndi_context
193
+ }
194
+ end
195
+
196
+ def jms_config_from_yaml(file, section)
197
+ YAML.load_file(file)[section]
198
+ end
199
+
200
+ def load_ssl_properties
201
+ java.lang.System.setProperty("javax.net.ssl.keyStore", @keystore) if @keystore
202
+ java.lang.System.setProperty("javax.net.ssl.keyStorePassword", @keystore_password.value) if @keystore_password
203
+ java.lang.System.setProperty("javax.net.ssl.trustStore", @truststore) if @truststore
204
+ java.lang.System.setProperty("javax.net.ssl.trustStorePassword", @truststore_password.value) if @truststore_password
205
+ end
206
+
207
+ def load_system_properties
208
+ @system_properties.each { |k,v| java.lang.System.set_property(k,v.to_s) }
209
+ end
210
+
211
+ def check_config
212
+ check_durable_subscription_config
213
+ raise(LogStash::ConfigurationError, "Threads cannot be > 1 if pub_sub is set") if @threads > 1 && @pub_sub
214
+ end
215
+
216
+ def check_durable_subscription_config
217
+ return unless @durable_subscriber
218
+ raise(LogStash::ConfigurationError, "pub_sub must be true if durable_subscriber is set") unless @pub_sub
219
+ @durable_subscriber_client_id ||= 'Logstash'
220
+ @durable_subscriber_name ||= destination
221
+ end
222
+
223
+ def run(output_queue)
224
+ begin
225
+ connection = JMS::Connection.new(@jms_config)
226
+ connection.client_id = @durable_subscriber_client_id if @durable_subscriber_client_id
227
+ session = connection.create_session(@jms_config)
228
+ connection.start
229
+ params = {:timeout => @timeout * 1000, :selector => @selector}
230
+ subscriber = subscriber(session, params)
231
+ 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|
235
+ queue_event(message, output_queue)
236
+ break if stop?
237
+ end
238
+ end
239
+ rescue => e
240
+ logger.warn("JMS Consumer Died", error_hash(e))
241
+ unless stop?
242
+ sleep(5)
243
+ subscriber && subscriber.close
244
+ session && session.close
245
+ connection && connection.close
246
+ retry
247
+ end
248
+ ensure
249
+ subscriber && subscriber.close
250
+ session && session.close
251
+ connection && connection.close
252
+ end
253
+ end # def run_consumer
137
254
 
138
255
 
139
- private
140
256
  def queue_event(msg, output_queue)
141
257
  begin
142
258
  if @include_body
@@ -146,13 +262,13 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
146
262
  event.set(field.to_s, value) # TODO(claveau): needs codec.decode or converter.convert ?
147
263
  end
148
264
  elsif msg.java_kind_of?(JMS::TextMessage) || msg.java_kind_of?(JMS::BytesMessage)
149
- if !msg.to_s.nil?
265
+ unless msg.to_s.nil?
150
266
  @codec.decode(msg.to_s) do |event_message|
151
267
  event = event_message
152
268
  end
153
269
  end
154
270
  else
155
- @logger.error( "Unknown data type #{msg.data.class.to_s} in Message" )
271
+ @logger.error( "Unsupported message type #{msg.data.class.to_s}" )
156
272
  end
157
273
  end
158
274
 
@@ -164,14 +280,14 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
164
280
  end
165
281
 
166
282
  if @include_header
167
- msg.attributes.each do |field, value|
168
- event.set(field.to_s, value)
283
+ msg.attributes && msg.attributes.each do |field, value|
284
+ event.set(field.to_s, value) unless @skip_headers.include?(field.to_s)
169
285
  end
170
286
  end
171
287
 
172
288
  if @include_properties
173
- msg.properties.each do |field, value|
174
- event.set(field.to_s, value)
289
+ msg.properties && msg.properties.each do |field, value|
290
+ event.set(field.to_s, value) unless @skip_properties.include?(field.to_s)
175
291
  end
176
292
  end
177
293
 
@@ -180,102 +296,55 @@ class LogStash::Inputs::Jms < LogStash::Inputs::Threadable
180
296
 
181
297
  rescue => e # parse or event creation error
182
298
  @logger.error("Failed to create event", :message => msg, :exception => e,
183
- :backtrace => e.backtrace);
299
+ :backtrace => e.backtrace)
184
300
  end
185
301
  end
186
302
 
187
- # Consume all available messages on the queue
188
- # sleeps some time, then consume again
189
- private
190
- def run_consumer(output_queue)
191
- JMS::Connection.session(@jms_config) do |session|
192
- destination_key = @pub_sub ? :topic_name : :queue_name
193
- while !stop?
194
- session.consume(destination_key => @destination, :timeout=>@timeout, :selector => @selector, :buffered_message => @oracle_aq_buffered_messages) do |message|
195
- queue_event message, output_queue
196
- break if stop?
197
- end
198
- sleep @interval
199
- end
200
- end
201
- rescue => e
202
- @logger.warn("JMS Consumer died", :exception => e, :backtrace => e.backtrace)
203
- sleep(10)
204
- retry unless stop?
205
- end # def run_consumer
206
-
207
- # Consume all available messages on the queue through a listener
208
- private
209
- def run_thread(output_queue)
210
- connection = JMS::Connection.new(@jms_config)
211
- connection.on_exception do |jms_exception|
212
- @logger.warn("JMS Exception has occurred: #{jms_exception}")
213
- end
214
303
 
304
+ def subscriber(session, params)
215
305
  destination_key = @pub_sub ? :topic_name : :queue_name
216
- connection.on_message(destination_key => @destination, :selector => @selector) do |message|
217
- queue_event message, output_queue
218
- end
219
- connection.start
220
- while !stop?
221
- @logger.debug("JMS Thread sleeping ...")
222
- sleep @interval
223
- end
224
- rescue => e
225
- @logger.warn("JMS Consumer died", :exception => e, :backtrace => e.backtrace)
226
- sleep(10)
227
- retry unless stop?
228
- end # def run_thread
229
-
230
- # Consume all available messages on the queue through a listener
231
- private
232
- def run_async(output_queue)
233
- JMS::Connection.start(@jms_config) do |connection|
234
- # Define exception listener
235
- # The problem here is that we do not handle any exception
236
- connection.on_exception do |jms_exception|
237
- @logger.warn("JMS Exception has occurred: #{jms_exception}")
238
- raise jms_exception
239
- end
240
- # Define Asynchronous code block to be called every time a message is received
241
- destination_key = @pub_sub ? :topic_name : :queue_name
242
- connection.on_message(destination_key => @destination, :selector => @selector) do |message|
243
- queue_event message, output_queue
244
- end
245
- # Since the on_message handler above is in a separate thread the thread needs
246
- # to do some other work. It will just sleep for 10 seconds.
247
- while !stop?
248
- @logger.debug("JMS Thread sleeping ...")
249
- sleep @interval
250
- end
251
- end
252
- rescue => e
253
- @logger.warn("JMS Consumer died", :exception => e, :backtrace => e.backtrace)
254
- sleep(10)
255
- retry unless stop?
256
- end # def run_async
306
+ params[destination_key] = @destination
307
+ queue_or_topic = session.create_destination(params)
308
+ @durable_subscriber ? durable_subscriber(session, queue_or_topic, params) :
309
+ regular_subscriber(session, queue_or_topic, params)
310
+ end
257
311
 
258
- public
259
- def run(output_queue)
260
- case @runner
261
- when "consumer" then
262
- run_consumer(output_queue)
263
- when "async" then
264
- run_async(output_queue)
265
- when "thread" then
266
- run_thread(output_queue)
267
- end
268
- end # def run
269
312
 
270
- public
271
- def close
272
- @logger.info("Closing JMS connection")
273
- @connection.close rescue nil
274
- end # def close
313
+ def durable_subscriber(session, queue_or_topic, params)
314
+ params[:selector] ? session.create_durable_subscriber(queue_or_topic, @durable_subscriber_name, params[:selector], false) :
315
+ session.create_durable_subscriber(queue_or_topic, @durable_subscriber_name)
316
+ end
275
317
 
276
- public
277
- def stop
278
- @logger.info("Stopping JMS consumer")
279
- @connection.stop rescue nil
280
- end # def stop
318
+ def regular_subscriber(session, queue_or_topic, params)
319
+ params[:selector] ? session.create_consumer(queue_or_topic, params[:selector]) :
320
+ session.create_consumer(queue_or_topic)
321
+ end
322
+
323
+ def error_hash(e)
324
+ error_hash = {:exception => e.class.name, :exception_message => e.message, :backtrace => e.backtrace}
325
+ root_cause = get_root_cause(e)
326
+ error_hash[:root_cause] = root_cause unless root_cause.nil?
327
+ error_hash
328
+ end
329
+
330
+ # JMS Exceptions can contain chains of Exceptions, making it difficult to determine the root cause of an error
331
+ # without knowing the actual root cause behind the problem.
332
+ # This method protects against Java Exceptions where the cause methods loop. If there is a cause loop, the last
333
+ # cause exception before the loop is detected will be returned, along with an entry in the root_cause hash indicating
334
+ # that an exception loop was detected. This will mean that the root cause may not be the actual root cause of the
335
+ # problem, and further investigation is required
336
+ def get_root_cause(e)
337
+ return nil unless e.respond_to?(:get_cause) && !e.get_cause.nil?
338
+ cause = e
339
+ slow_pointer = e
340
+ # Use a slow pointer to avoid cause loops in Java Exceptions
341
+ move_slow = false
342
+ until (next_cause = cause.get_cause).nil?
343
+ cause = next_cause
344
+ return {:exception => cause.class.name, :exception_message => cause.message, :exception_loop => true } if cause == slow_pointer
345
+ slow_pointer = slow_pointer.cause if move_slow
346
+ move_slow = !move_slow
347
+ end
348
+ {:exception => cause.class.name, :exception_message => cause.message }
349
+ end
281
350
  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.0.6'
4
+ s.version = '3.1.0'
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"
@@ -0,0 +1,132 @@
1
+ <!--
2
+ Licensed to the Apache Software Foundation (ASF) under one or more
3
+ contributor license agreements. See the NOTICE file distributed with
4
+ this work for additional information regarding copyright ownership.
5
+ The ASF licenses this file to You under the Apache License, Version 2.0
6
+ (the "License"); you may not use this file except in compliance with
7
+ the License. You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ -->
17
+ <!-- START SNIPPET: example -->
18
+ <beans
19
+ xmlns="http://www.springframework.org/schema/beans"
20
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
22
+ http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
23
+
24
+ <!-- Allows us to use system properties as variables in this configuration file -->
25
+ <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
26
+ <property name="locations">
27
+ <value>file:${activemq.conf}/credentials.properties</value>
28
+ </property>
29
+ </bean>
30
+
31
+ <!-- Allows accessing the server log -->
32
+ <bean id="logQuery" class="io.fabric8.insight.log.log4j.Log4jLogQuery"
33
+ lazy-init="false" scope="singleton"
34
+ init-method="start" destroy-method="stop">
35
+ </bean>
36
+
37
+ <!--
38
+ The <broker> element is used to configure the ActiveMQ broker.
39
+ -->
40
+ <broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}">
41
+
42
+ <destinationPolicy>
43
+ <policyMap>
44
+ <policyEntries>
45
+ <policyEntry topic=">" >
46
+ <!-- The constantPendingMessageLimitStrategy is used to prevent
47
+ slow topic consumers to block producers and affect other consumers
48
+ by limiting the number of messages that are retained
49
+ For more information, see:
50
+
51
+ http://activemq.apache.org/slow-consumer-handling.html
52
+
53
+ -->
54
+ <pendingMessageLimitStrategy>
55
+ <constantPendingMessageLimitStrategy limit="1000"/>
56
+ </pendingMessageLimitStrategy>
57
+ </policyEntry>
58
+ </policyEntries>
59
+ </policyMap>
60
+ </destinationPolicy>
61
+
62
+
63
+ <!--
64
+ The managementContext is used to configure how ActiveMQ is exposed in
65
+ JMX. By default, ActiveMQ uses the MBean server that is started by
66
+ the JVM. For more information, see:
67
+
68
+ http://activemq.apache.org/jmx.html
69
+ -->
70
+ <managementContext>
71
+ <managementContext createConnector="false"/>
72
+ </managementContext>
73
+
74
+ <!--
75
+ Configure message persistence for the broker. The default persistence
76
+ mechanism is the KahaDB store (identified by the kahaDB tag).
77
+ For more information, see:
78
+
79
+ http://activemq.apache.org/persistence.html
80
+ -->
81
+ <persistenceAdapter>
82
+ <kahaDB directory="${activemq.data}/kahadb"/>
83
+ </persistenceAdapter>
84
+
85
+
86
+ <!--
87
+ The systemUsage controls the maximum amount of space the broker will
88
+ use before disabling caching and/or slowing down producers. For more information, see:
89
+ http://activemq.apache.org/producer-flow-control.html
90
+ -->
91
+ <systemUsage>
92
+ <systemUsage>
93
+ <memoryUsage>
94
+ <memoryUsage percentOfJvmHeap="70" />
95
+ </memoryUsage>
96
+ <storeUsage>
97
+ <storeUsage limit="100 gb"/>
98
+ </storeUsage>
99
+ <tempUsage>
100
+ <tempUsage limit="50 gb"/>
101
+ </tempUsage>
102
+ </systemUsage>
103
+ </systemUsage>
104
+
105
+ <!--
106
+ The transport connectors expose ActiveMQ over a given protocol to
107
+ clients and other brokers. For more information, see:
108
+
109
+ http://activemq.apache.org/configuring-transports.html
110
+ -->
111
+ <transportConnectors>
112
+ <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
113
+ <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
114
+ </transportConnectors>
115
+
116
+ <!-- destroy the spring context on shutdown to stop jetty -->
117
+ <shutdownHooks>
118
+ <bean xmlns="http://www.springframework.org/schema/beans" class="org.apache.activemq.hooks.SpringContextHook" />
119
+ </shutdownHooks>
120
+
121
+ </broker>
122
+
123
+ <!--
124
+ Enable web consoles, REST and Ajax APIs and demos
125
+ The web consoles requires by default login, you can disable this in the jetty.xml file
126
+
127
+ Take a look at ${ACTIVEMQ_HOME}/conf/jetty.xml for more details
128
+ -->
129
+ <import resource="jetty.xml"/>
130
+
131
+ </beans>
132
+ <!-- END SNIPPET: example -->