logstash-input-jdbc 4.3.11 → 4.3.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9e1072279ad555d69710d66bc23f5d6af1d5492998c6a2a30edb8dea03bb402
4
- data.tar.gz: 03ca8379b772ef0a4b5d8693fbcee2adbe3c2226922f19e38c2f70e0b02d573e
3
+ metadata.gz: 68b7c5e096c835eea37700e2f7d68d14f61a6c67e5420e2ec3d1fd9a13cfb037
4
+ data.tar.gz: '0977958f3559d7a3e1e0f3c1b89c225e35f5b250fadcd2a5fba880423c6bd48c'
5
5
  SHA512:
6
- metadata.gz: 151670dbc5de220c1c68eef6b723a2c49a1fa93f19efe97a6286c0968309ea53bb57be19e0a46a359c094ce6ce8a69a3cc78213f9884f67fec6477622bc70c94
7
- data.tar.gz: 19929d6d094038af8405cb019de5032cc470ab312ffe3e495f47c193ee066f87fd9fdae7dcb79f8fd6b1a0fc27db1450e876bce8b0437c9e35706059d726b8dd
6
+ metadata.gz: d94b92c9ba96ed2ad5ab908af35bcc5d552da3adb6d6ac821cc65d2392495eb7fa9e8d4e4cf0700a7ba43d27aa4743a0c8894f8baa3679cd4bc33a833fffbfb2
7
+ data.tar.gz: 89170a09b67a8629cd1fb7e9ff7d74d26ecd3055d412eb93855e0f39981b3e04c783333b6a5c6848cd985a0d858216fd5295d0e5ad0386f3aafd091d827ac8ab
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 4.3.12
2
+ - Added check to prevent count sql syntax errors when debug logging [Issue #287](https://github.com/logstash-plugins/logstash-input-jdbc/issue/287) and [Pull Request #294](https://github.com/logstash-plugins/logstash-input-jdbc/pull/294)
3
+
1
4
  ## 4.3.11
2
5
  - Fixed crash that occurs when receiving string input that cannot be coerced to UTF-8 (such as BLOB data) [#291](https://github.com/logstash-plugins/logstash-input-jdbc/pull/291)
3
6
 
data/NOTICE.TXT CHANGED
@@ -1,5 +1,5 @@
1
1
  Elasticsearch
2
- Copyright 2012-2015 Elasticsearch
2
+ Copyright 2012-2018 Elasticsearch
3
3
 
4
4
  This product includes software developed by The Apache Software
5
5
  Foundation (http://www.apache.org/).
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/inputs/base"
3
3
  require "logstash/namespace"
4
- require "logstash/plugin_mixins/jdbc"
4
+ require "logstash/plugin_mixins/jdbc/jdbc"
5
5
 
6
6
 
7
7
  # This plugin was created as a way to ingest data from any database
@@ -123,8 +123,8 @@ require "logstash/plugin_mixins/jdbc"
123
123
  # }
124
124
  # ---------------------------------------------------------------------------------------------------
125
125
  #
126
- class LogStash::Inputs::Jdbc < LogStash::Inputs::Base
127
- include LogStash::PluginMixins::Jdbc
126
+ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
127
+ include LogStash::PluginMixins::Jdbc::Jdbc
128
128
  config_name "jdbc"
129
129
 
130
130
  # If undefined, Logstash will complain, even if codec is unused.
@@ -213,7 +213,8 @@ class LogStash::Inputs::Jdbc < LogStash::Inputs::Base
213
213
  end
214
214
  end
215
215
 
216
- @value_tracker = LogStash::PluginMixins::ValueTracking.build_last_value_tracker(self)
216
+ set_value_tracker(LogStash::PluginMixins::Jdbc::ValueTracking.build_last_value_tracker(self))
217
+ set_statement_logger(LogStash::PluginMixins::Jdbc::CheckedCountLogger.new(@logger))
217
218
 
218
219
  @enable_encoding = !@charset.nil? || !@columns_charset.empty?
219
220
 
@@ -221,13 +222,13 @@ class LogStash::Inputs::Jdbc < LogStash::Inputs::Base
221
222
  raise(LogStash::ConfigurationError, "Must set either :statement or :statement_filepath. Only one may be set at a time.")
222
223
  end
223
224
 
224
- @statement = File.read(@statement_filepath) if @statement_filepath
225
+ @statement = ::File.read(@statement_filepath) if @statement_filepath
225
226
 
226
227
  if (@jdbc_password_filepath and @jdbc_password)
227
228
  raise(LogStash::ConfigurationError, "Only one of :jdbc_password, :jdbc_password_filepath may be set at a time.")
228
229
  end
229
230
 
230
- @jdbc_password = LogStash::Util::Password.new(File.read(@jdbc_password_filepath).strip) if @jdbc_password_filepath
231
+ @jdbc_password = LogStash::Util::Password.new(::File.read(@jdbc_password_filepath).strip) if @jdbc_password_filepath
231
232
 
232
233
  if enable_encoding?
233
234
  encodings = @columns_charset.values
@@ -241,6 +242,15 @@ class LogStash::Inputs::Jdbc < LogStash::Inputs::Base
241
242
  end
242
243
  end # def register
243
244
 
245
+ # test injection points
246
+ def set_statement_logger(instance)
247
+ @statement_logger = instance
248
+ end
249
+
250
+ def set_value_tracker(instance)
251
+ @value_tracker = instance
252
+ end
253
+
244
254
  def run(queue)
245
255
  if @schedule
246
256
  @scheduler = Rufus::Scheduler.new(:max_work_threads => 1)
@@ -296,4 +306,4 @@ class LogStash::Inputs::Jdbc < LogStash::Inputs::Base
296
306
  value
297
307
  end
298
308
  end
299
- end # class LogStash::Inputs::Jdbc
309
+ end end end # class LogStash::Inputs::Jdbc
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ module LogStash module PluginMixins module Jdbc
4
+ class CheckedCountLogger
5
+ def initialize(logger)
6
+ @logger = logger
7
+ @needs_check = true
8
+ @count_is_supported = false
9
+ @in_debug = @logger.debug?
10
+ end
11
+
12
+ def log_statement_parameters(query, statement, parameters)
13
+ return unless @in_debug
14
+ check_count_query(query) if @needs_check
15
+ if @count_is_supported
16
+ @logger.debug("Executing JDBC query", :statement => statement, :parameters => parameters, :count => execute_count(query))
17
+ else
18
+ @logger.debug("Executing JDBC query", :statement => statement, :parameters => parameters)
19
+ end
20
+ end
21
+
22
+ def check_count_query(query)
23
+ @needs_check = false
24
+ begin
25
+ execute_count(query)
26
+ @count_is_supported = true
27
+ rescue Exception => e
28
+ @logger.warn("Attempting a count query raised an error, the generated count statement is most likely incorrect but check networking, authentication or your statement syntax", "exception" => e.message)
29
+ @logger.warn("Ongoing count statement generation is being prevented")
30
+ @count_is_supported = false
31
+ end
32
+ end
33
+
34
+ def execute_count(query)
35
+ query.count
36
+ end
37
+ end
38
+ end end end
@@ -0,0 +1,317 @@
1
+ # encoding: utf-8
2
+ # TAKEN FROM WIIBAA
3
+ require "logstash/config/mixin"
4
+ require "time"
5
+ require "date"
6
+ require_relative "value_tracking"
7
+ require_relative "checked_count_logger"
8
+
9
+ java_import java.util.concurrent.locks.ReentrantLock
10
+
11
+ # Tentative of abstracting JDBC logic to a mixin
12
+ # for potential reuse in other plugins (input/output)
13
+ module LogStash module PluginMixins module Jdbc
14
+ module Jdbc
15
+ # This method is called when someone includes this module
16
+ def self.included(base)
17
+ # Add these methods to the 'base' given.
18
+ base.extend(self)
19
+ base.setup_jdbc_config
20
+ end
21
+
22
+
23
+ public
24
+ def setup_jdbc_config
25
+ # JDBC driver library path to third party driver library. In case of multiple libraries being
26
+ # required you can pass them separated by a comma.
27
+ #
28
+ # If not provided, Plugin will look for the driver class in the Logstash Java classpath.
29
+ config :jdbc_driver_library, :validate => :string
30
+
31
+ # JDBC driver class to load, for exmaple, "org.apache.derby.jdbc.ClientDriver"
32
+ # NB per https://github.com/logstash-plugins/logstash-input-jdbc/issues/43 if you are using
33
+ # the Oracle JDBC driver (ojdbc6.jar) the correct `jdbc_driver_class` is `"Java::oracle.jdbc.driver.OracleDriver"`
34
+ config :jdbc_driver_class, :validate => :string, :required => true
35
+
36
+ # JDBC connection string
37
+ config :jdbc_connection_string, :validate => :string, :required => true
38
+
39
+ # JDBC user
40
+ config :jdbc_user, :validate => :string, :required => true
41
+
42
+ # JDBC password
43
+ config :jdbc_password, :validate => :password
44
+
45
+ # JDBC password filename
46
+ config :jdbc_password_filepath, :validate => :path
47
+
48
+ # JDBC enable paging
49
+ #
50
+ # This will cause a sql statement to be broken up into multiple queries.
51
+ # Each query will use limits and offsets to collectively retrieve the full
52
+ # result-set. The limit size is set with `jdbc_page_size`.
53
+ #
54
+ # Be aware that ordering is not guaranteed between queries.
55
+ config :jdbc_paging_enabled, :validate => :boolean, :default => false
56
+
57
+ # JDBC page size
58
+ config :jdbc_page_size, :validate => :number, :default => 100000
59
+
60
+ # JDBC fetch size. if not provided, respective driver's default will be used
61
+ config :jdbc_fetch_size, :validate => :number
62
+
63
+ # Connection pool configuration.
64
+ # Validate connection before use.
65
+ config :jdbc_validate_connection, :validate => :boolean, :default => false
66
+
67
+ # Connection pool configuration.
68
+ # How often to validate a connection (in seconds)
69
+ config :jdbc_validation_timeout, :validate => :number, :default => 3600
70
+
71
+ # Connection pool configuration.
72
+ # The amount of seconds to wait to acquire a connection before raising a PoolTimeoutError (default 5)
73
+ config :jdbc_pool_timeout, :validate => :number, :default => 5
74
+
75
+ # Timezone conversion.
76
+ # SQL does not allow for timezone data in timestamp fields. This plugin will automatically
77
+ # convert your SQL timestamp fields to Logstash timestamps, in relative UTC time in ISO8601 format.
78
+ #
79
+ # Using this setting will manually assign a specified timezone offset, instead
80
+ # of using the timezone setting of the local machine. You must use a canonical
81
+ # timezone, *America/Denver*, for example.
82
+ config :jdbc_default_timezone, :validate => :string
83
+
84
+ # General/Vendor-specific Sequel configuration options.
85
+ #
86
+ # An example of an optional connection pool configuration
87
+ # max_connections - The maximum number of connections the connection pool
88
+ #
89
+ # examples of vendor-specific options can be found in this
90
+ # documentation page: https://github.com/jeremyevans/sequel/blob/master/doc/opening_databases.rdoc
91
+ config :sequel_opts, :validate => :hash, :default => {}
92
+
93
+ # Log level at which to log SQL queries, the accepted values are the common ones fatal, error, warn,
94
+ # info and debug. The default value is info.
95
+ config :sql_log_level, :validate => [ "fatal", "error", "warn", "info", "debug" ], :default => "info"
96
+
97
+ # Maximum number of times to try connecting to database
98
+ config :connection_retry_attempts, :validate => :number, :default => 1
99
+ # Number of seconds to sleep between connection attempts
100
+ config :connection_retry_attempts_wait_time, :validate => :number, :default => 0.5
101
+ end
102
+
103
+ private
104
+ def jdbc_connect
105
+ opts = {
106
+ :user => @jdbc_user,
107
+ :password => @jdbc_password.nil? ? nil : @jdbc_password.value,
108
+ :pool_timeout => @jdbc_pool_timeout,
109
+ :keep_reference => false
110
+ }.merge(@sequel_opts)
111
+ retry_attempts = @connection_retry_attempts
112
+ loop do
113
+ retry_attempts -= 1
114
+ begin
115
+ return Sequel.connect(@jdbc_connection_string, opts=opts)
116
+ rescue Sequel::PoolTimeout => e
117
+ if retry_attempts <= 0
118
+ @logger.error("Failed to connect to database. #{@jdbc_pool_timeout} second timeout exceeded. Tried #{@connection_retry_attempts} times.")
119
+ raise e
120
+ else
121
+ @logger.error("Failed to connect to database. #{@jdbc_pool_timeout} second timeout exceeded. Trying again.")
122
+ end
123
+ rescue Sequel::Error => e
124
+ if retry_attempts <= 0
125
+ @logger.error("Unable to connect to database. Tried #{@connection_retry_attempts} times", :error_message => e.message, )
126
+ raise e
127
+ else
128
+ @logger.error("Unable to connect to database. Trying again", :error_message => e.message)
129
+ end
130
+ end
131
+ sleep(@connection_retry_attempts_wait_time)
132
+ end
133
+ end
134
+
135
+ private
136
+ def load_drivers(drivers)
137
+ drivers.each do |driver|
138
+ begin
139
+ class_loader = java.lang.ClassLoader.getSystemClassLoader().to_java(java.net.URLClassLoader)
140
+ class_loader.add_url(java.io.File.new(driver).toURI().toURL())
141
+ rescue => e
142
+ @logger.error("Failed to load #{driver}", :exception => e)
143
+ end
144
+ end
145
+ end
146
+
147
+ private
148
+ def open_jdbc_connection
149
+ require "java"
150
+ require "sequel"
151
+ require "sequel/adapters/jdbc"
152
+ load_drivers(@jdbc_driver_library.split(",")) if @jdbc_driver_library
153
+
154
+ begin
155
+ Sequel::JDBC.load_driver(@jdbc_driver_class)
156
+ rescue Sequel::AdapterNotFound => e
157
+ message = if @jdbc_driver_library.nil?
158
+ ":jdbc_driver_library is not set, are you sure you included
159
+ the proper driver client libraries in your classpath?"
160
+ else
161
+ "Are you sure you've included the correct jdbc driver in :jdbc_driver_library?"
162
+ end
163
+ raise LogStash::ConfigurationError, "#{e}. #{message}"
164
+ end
165
+ @database = jdbc_connect()
166
+ @database.extension(:pagination)
167
+ if @jdbc_default_timezone
168
+ @database.extension(:named_timezones)
169
+ @database.timezone = @jdbc_default_timezone
170
+ end
171
+ if @jdbc_validate_connection
172
+ @database.extension(:connection_validator)
173
+ @database.pool.connection_validation_timeout = @jdbc_validation_timeout
174
+ end
175
+ @database.fetch_size = @jdbc_fetch_size unless @jdbc_fetch_size.nil?
176
+ begin
177
+ @database.test_connection
178
+ rescue Sequel::DatabaseConnectionError => e
179
+ @logger.warn("Failed test_connection.", :exception => e)
180
+ close_jdbc_connection
181
+
182
+ #TODO return false and let the plugin raise a LogStash::ConfigurationError
183
+ raise e
184
+ end
185
+
186
+ @database.sql_log_level = @sql_log_level.to_sym
187
+ @database.logger = @logger
188
+
189
+ @database.extension :identifier_mangling
190
+
191
+ if @lowercase_column_names
192
+ @database.identifier_output_method = :downcase
193
+ else
194
+ @database.identifier_output_method = :to_s
195
+ end
196
+ end
197
+
198
+ public
199
+ def prepare_jdbc_connection
200
+ @connection_lock = ReentrantLock.new
201
+ end
202
+
203
+ public
204
+ def close_jdbc_connection
205
+ begin
206
+ # pipeline restarts can also close the jdbc connection, block until the current executing statement is finished to avoid leaking connections
207
+ # connections in use won't really get closed
208
+ @connection_lock.lock
209
+ @database.disconnect if @database
210
+ rescue => e
211
+ @logger.warn("Failed to close connection", :exception => e)
212
+ ensure
213
+ @connection_lock.unlock
214
+ end
215
+ end
216
+
217
+ public
218
+ def execute_statement(statement, parameters)
219
+ success = false
220
+ @connection_lock.lock
221
+ open_jdbc_connection
222
+ begin
223
+ params = symbolized_params(parameters)
224
+ query = @database[statement, params]
225
+
226
+ sql_last_value = @use_column_value ? @value_tracker.value : Time.now.utc
227
+ @tracking_column_warning_sent = false
228
+ @statement_logger.log_statement_parameters(query, statement, params)
229
+ perform_query(query) do |row|
230
+ sql_last_value = get_column_value(row) if @use_column_value
231
+ yield extract_values_from(row)
232
+ end
233
+ success = true
234
+ rescue Sequel::DatabaseConnectionError, Sequel::DatabaseError => e
235
+ @logger.warn("Exception when executing JDBC query", :exception => e)
236
+ else
237
+ @value_tracker.set_value(sql_last_value)
238
+ ensure
239
+ close_jdbc_connection
240
+ @connection_lock.unlock
241
+ end
242
+ return success
243
+ end
244
+
245
+ # Performs the query, respecting our pagination settings, yielding once per row of data
246
+ # @param query [Sequel::Dataset]
247
+ # @yieldparam row [Hash{Symbol=>Object}]
248
+ private
249
+ def perform_query(query)
250
+ if @jdbc_paging_enabled
251
+ query.each_page(@jdbc_page_size) do |paged_dataset|
252
+ paged_dataset.each do |row|
253
+ yield row
254
+ end
255
+ end
256
+ else
257
+ query.each do |row|
258
+ yield row
259
+ end
260
+ end
261
+ end
262
+
263
+ public
264
+ def get_column_value(row)
265
+ if !row.has_key?(@tracking_column.to_sym)
266
+ if !@tracking_column_warning_sent
267
+ @logger.warn("tracking_column not found in dataset.", :tracking_column => @tracking_column)
268
+ @tracking_column_warning_sent = true
269
+ end
270
+ # If we can't find the tracking column, return the current value in the ivar
271
+ @sql_last_value
272
+ else
273
+ # Otherwise send the updated tracking column
274
+ row[@tracking_column.to_sym]
275
+ end
276
+ end
277
+
278
+ # Symbolize parameters keys to use with Sequel
279
+ private
280
+ def symbolized_params(parameters)
281
+ parameters.inject({}) do |hash,(k,v)|
282
+ case v
283
+ when LogStash::Timestamp
284
+ hash[k.to_sym] = v.time
285
+ else
286
+ hash[k.to_sym] = v
287
+ end
288
+ hash
289
+ end
290
+ end
291
+
292
+ private
293
+ #Stringify row keys and decorate values when necessary
294
+ def extract_values_from(row)
295
+ Hash[row.map { |k, v| [k.to_s, decorate_value(v)] }]
296
+ end
297
+
298
+ private
299
+ def decorate_value(value)
300
+ if value.is_a?(Time)
301
+ # transform it to LogStash::Timestamp as required by LS
302
+ LogStash::Timestamp.new(value)
303
+ elsif value.is_a?(Date)
304
+ LogStash::Timestamp.new(value.to_time)
305
+ elsif value.is_a?(DateTime)
306
+ # Manual timezone conversion detected.
307
+ # This is slower, so we put it in as a conditional case.
308
+ LogStash::Timestamp.new(Time.parse(value.to_s))
309
+ else
310
+ value
311
+ end
312
+ end
313
+
314
+ end
315
+ end end end
316
+
317
+
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
  require "yaml" # persistence
3
3
 
4
- module LogStash module PluginMixins
4
+ module LogStash module PluginMixins module Jdbc
5
5
  class ValueTracking
6
6
 
7
7
  def self.build_last_value_tracker(plugin)
@@ -125,4 +125,4 @@ module LogStash module PluginMixins
125
125
  def write(value)
126
126
  end
127
127
  end
128
- end end
128
+ end end end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-input-jdbc'
3
- s.version = '4.3.11'
3
+ s.version = '4.3.12'
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = "Creates events from JDBC data"
6
6
  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,36 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/inputs/jdbc"
3
+
4
+ # This test requires: Firebird installed to Mac OSX, it uses the built-in example database `employee`
5
+
6
+ describe LogStash::Inputs::Jdbc, :integration => true do
7
+ # This is a necessary change test-wide to guarantee that no local timezone
8
+ # is picked up. It could be arbitrarily set to any timezone, but then the test
9
+ # would have to compensate differently. That's why UTC is chosen.
10
+ ENV["TZ"] = "Etc/UTC"
11
+ let(:mixin_settings) do
12
+ { "jdbc_user" => "SYSDBA", "jdbc_driver_class" => "org.firebirdsql.jdbc.FBDriver", "jdbc_driver_library" => "/elastic/tmp/jaybird-full-3.0.4.jar",
13
+ "jdbc_connection_string" => "jdbc:firebirdsql://localhost:3050//Library/Frameworks/Firebird.framework/Versions/A/Resources/examples/empbuild/employee.fdb", "jdbc_password" => "masterkey"}
14
+ end
15
+ let(:settings) { {"statement" => "SELECT FIRST_NAME, LAST_NAME FROM EMPLOYEE WHERE EMP_NO > 144"} }
16
+ let(:plugin) { LogStash::Inputs::Jdbc.new(mixin_settings.merge(settings)) }
17
+ let(:queue) { Queue.new }
18
+
19
+ context "when passing no parameters" do
20
+ before do
21
+ plugin.register
22
+ end
23
+
24
+ after do
25
+ plugin.stop
26
+ end
27
+
28
+ it "should retrieve params correctly from Event" do
29
+ plugin.run(queue)
30
+ event = queue.pop
31
+ expect(event.get('first_name')).to eq("Mark")
32
+ expect(event.get('last_name')).to eq("Guckenheimer")
33
+ end
34
+ end
35
+ end
36
+
@@ -1267,4 +1267,54 @@ describe LogStash::Inputs::Jdbc do
1267
1267
  expect(event.get("ranking").to_f).to eq(95.67)
1268
1268
  end
1269
1269
  end
1270
+
1271
+ context "when debug logging and a count query raises a count related error" do
1272
+ let(:settings) do
1273
+ { "statement" => "SELECT * from types_table" }
1274
+ end
1275
+ let(:logger) { double("logger", :debug? => true) }
1276
+ let(:statement_logger) { LogStash::PluginMixins::Jdbc::CheckedCountLogger.new(logger) }
1277
+ let(:value_tracker) { double("value tracker", :set_value => nil, :write => nil) }
1278
+ let(:msg) { 'Java::JavaSql::SQLSyntaxErrorException: Dynamic SQL Error; SQL error code = -104; Token unknown - line 1, column 105; LIMIT [SQLState:42000, ISC error code:335544634]' }
1279
+ let(:error_args) do
1280
+ {"exception" => msg}
1281
+ end
1282
+
1283
+ before do
1284
+ db << "INSERT INTO types_table (num, string, started_at, custom_time, ranking) VALUES (1, 'A test', '1999-12-31', '1999-12-31 23:59:59', 95.67)"
1285
+ plugin.register
1286
+ plugin.set_statement_logger(statement_logger)
1287
+ plugin.set_value_tracker(value_tracker)
1288
+ allow(value_tracker).to receive(:value).and_return("bar")
1289
+ allow(statement_logger).to receive(:execute_count).once.and_raise(StandardError.new(msg))
1290
+ end
1291
+
1292
+ after do
1293
+ plugin.stop
1294
+ end
1295
+
1296
+ context "if the count query raises an error" do
1297
+ it "should log a debug line without a count key as its unknown whether a count works at this stage" do
1298
+ expect(logger).to receive(:warn).once.with("Attempting a count query raised an error, the generated count statement is most likely incorrect but check networking, authentication or your statement syntax", error_args)
1299
+ expect(logger).to receive(:warn).once.with("Ongoing count statement generation is being prevented")
1300
+ expect(logger).to receive(:debug).once.with("Executing JDBC query", :statement => settings["statement"], :parameters => {:sql_last_value=>"bar"})
1301
+ plugin.run(queue)
1302
+ queue.pop
1303
+ end
1304
+
1305
+ it "should create an event normally" do
1306
+ allow(logger).to receive(:warn)
1307
+ allow(logger).to receive(:debug)
1308
+ plugin.run(queue)
1309
+ event = queue.pop
1310
+ expect(event.get("num")).to eq(1)
1311
+ expect(event.get("string")).to eq("A test")
1312
+ expect(event.get("started_at")).to be_a(LogStash::Timestamp)
1313
+ expect(event.get("started_at").to_s).to eq("1999-12-31T00:00:00.000Z")
1314
+ expect(event.get("custom_time")).to be_a(LogStash::Timestamp)
1315
+ expect(event.get("custom_time").to_s).to eq("1999-12-31T23:59:59.000Z")
1316
+ expect(event.get("ranking").to_f).to eq(95.67)
1317
+ end
1318
+ end
1319
+ end
1270
1320
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-jdbc
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.11
4
+ version: 4.3.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-23 00:00:00.000000000 Z
11
+ date: 2018-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -171,9 +171,11 @@ files:
171
171
  - README.md
172
172
  - docs/index.asciidoc
173
173
  - lib/logstash/inputs/jdbc.rb
174
- - lib/logstash/plugin_mixins/jdbc.rb
175
- - lib/logstash/plugin_mixins/value_tracking.rb
174
+ - lib/logstash/plugin_mixins/jdbc/checked_count_logger.rb
175
+ - lib/logstash/plugin_mixins/jdbc/jdbc.rb
176
+ - lib/logstash/plugin_mixins/jdbc/value_tracking.rb
176
177
  - logstash-input-jdbc.gemspec
178
+ - spec/inputs/integ_spec.rb
177
179
  - spec/inputs/jdbc_spec.rb
178
180
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
179
181
  licenses:
@@ -202,4 +204,5 @@ signing_key:
202
204
  specification_version: 4
203
205
  summary: Creates events from JDBC data
204
206
  test_files:
207
+ - spec/inputs/integ_spec.rb
205
208
  - spec/inputs/jdbc_spec.rb
@@ -1,313 +0,0 @@
1
- # encoding: utf-8
2
- # TAKEN FROM WIIBAA
3
- require "logstash/config/mixin"
4
- require "time"
5
- require "date"
6
- require "logstash/plugin_mixins/value_tracking"
7
-
8
- java_import java.util.concurrent.locks.ReentrantLock
9
-
10
- # Tentative of abstracting JDBC logic to a mixin
11
- # for potential reuse in other plugins (input/output)
12
- module LogStash::PluginMixins::Jdbc
13
-
14
- # This method is called when someone includes this module
15
- def self.included(base)
16
- # Add these methods to the 'base' given.
17
- base.extend(self)
18
- base.setup_jdbc_config
19
- end
20
-
21
-
22
- public
23
- def setup_jdbc_config
24
- # JDBC driver library path to third party driver library. In case of multiple libraries being
25
- # required you can pass them separated by a comma.
26
- #
27
- # If not provided, Plugin will look for the driver class in the Logstash Java classpath.
28
- config :jdbc_driver_library, :validate => :string
29
-
30
- # JDBC driver class to load, for exmaple, "org.apache.derby.jdbc.ClientDriver"
31
- # NB per https://github.com/logstash-plugins/logstash-input-jdbc/issues/43 if you are using
32
- # the Oracle JDBC driver (ojdbc6.jar) the correct `jdbc_driver_class` is `"Java::oracle.jdbc.driver.OracleDriver"`
33
- config :jdbc_driver_class, :validate => :string, :required => true
34
-
35
- # JDBC connection string
36
- config :jdbc_connection_string, :validate => :string, :required => true
37
-
38
- # JDBC user
39
- config :jdbc_user, :validate => :string, :required => true
40
-
41
- # JDBC password
42
- config :jdbc_password, :validate => :password
43
-
44
- # JDBC password filename
45
- config :jdbc_password_filepath, :validate => :path
46
-
47
- # JDBC enable paging
48
- #
49
- # This will cause a sql statement to be broken up into multiple queries.
50
- # Each query will use limits and offsets to collectively retrieve the full
51
- # result-set. The limit size is set with `jdbc_page_size`.
52
- #
53
- # Be aware that ordering is not guaranteed between queries.
54
- config :jdbc_paging_enabled, :validate => :boolean, :default => false
55
-
56
- # JDBC page size
57
- config :jdbc_page_size, :validate => :number, :default => 100000
58
-
59
- # JDBC fetch size. if not provided, respective driver's default will be used
60
- config :jdbc_fetch_size, :validate => :number
61
-
62
- # Connection pool configuration.
63
- # Validate connection before use.
64
- config :jdbc_validate_connection, :validate => :boolean, :default => false
65
-
66
- # Connection pool configuration.
67
- # How often to validate a connection (in seconds)
68
- config :jdbc_validation_timeout, :validate => :number, :default => 3600
69
-
70
- # Connection pool configuration.
71
- # The amount of seconds to wait to acquire a connection before raising a PoolTimeoutError (default 5)
72
- config :jdbc_pool_timeout, :validate => :number, :default => 5
73
-
74
- # Timezone conversion.
75
- # SQL does not allow for timezone data in timestamp fields. This plugin will automatically
76
- # convert your SQL timestamp fields to Logstash timestamps, in relative UTC time in ISO8601 format.
77
- #
78
- # Using this setting will manually assign a specified timezone offset, instead
79
- # of using the timezone setting of the local machine. You must use a canonical
80
- # timezone, *America/Denver*, for example.
81
- config :jdbc_default_timezone, :validate => :string
82
-
83
- # General/Vendor-specific Sequel configuration options.
84
- #
85
- # An example of an optional connection pool configuration
86
- # max_connections - The maximum number of connections the connection pool
87
- #
88
- # examples of vendor-specific options can be found in this
89
- # documentation page: https://github.com/jeremyevans/sequel/blob/master/doc/opening_databases.rdoc
90
- config :sequel_opts, :validate => :hash, :default => {}
91
-
92
- # Log level at which to log SQL queries, the accepted values are the common ones fatal, error, warn,
93
- # info and debug. The default value is info.
94
- config :sql_log_level, :validate => [ "fatal", "error", "warn", "info", "debug" ], :default => "info"
95
-
96
- # Maximum number of times to try connecting to database
97
- config :connection_retry_attempts, :validate => :number, :default => 1
98
- # Number of seconds to sleep between connection attempts
99
- config :connection_retry_attempts_wait_time, :validate => :number, :default => 0.5
100
- end
101
-
102
- private
103
- def jdbc_connect
104
- opts = {
105
- :user => @jdbc_user,
106
- :password => @jdbc_password.nil? ? nil : @jdbc_password.value,
107
- :pool_timeout => @jdbc_pool_timeout,
108
- :keep_reference => false
109
- }.merge(@sequel_opts)
110
- retry_attempts = @connection_retry_attempts
111
- loop do
112
- retry_attempts -= 1
113
- begin
114
- return Sequel.connect(@jdbc_connection_string, opts=opts)
115
- rescue Sequel::PoolTimeout => e
116
- if retry_attempts <= 0
117
- @logger.error("Failed to connect to database. #{@jdbc_pool_timeout} second timeout exceeded. Tried #{@connection_retry_attempts} times.")
118
- raise e
119
- else
120
- @logger.error("Failed to connect to database. #{@jdbc_pool_timeout} second timeout exceeded. Trying again.")
121
- end
122
- rescue Sequel::Error => e
123
- if retry_attempts <= 0
124
- @logger.error("Unable to connect to database. Tried #{@connection_retry_attempts} times", :error_message => e.message, )
125
- raise e
126
- else
127
- @logger.error("Unable to connect to database. Trying again", :error_message => e.message)
128
- end
129
- end
130
- sleep(@connection_retry_attempts_wait_time)
131
- end
132
- end
133
-
134
- private
135
- def load_drivers(drivers)
136
- drivers.each do |driver|
137
- begin
138
- class_loader = java.lang.ClassLoader.getSystemClassLoader().to_java(java.net.URLClassLoader)
139
- class_loader.add_url(java.io.File.new(driver).toURI().toURL())
140
- rescue => e
141
- @logger.error("Failed to load #{driver}", :exception => e)
142
- end
143
- end
144
- end
145
-
146
- private
147
- def open_jdbc_connection
148
- require "java"
149
- require "sequel"
150
- require "sequel/adapters/jdbc"
151
- load_drivers(@jdbc_driver_library.split(",")) if @jdbc_driver_library
152
-
153
- begin
154
- Sequel::JDBC.load_driver(@jdbc_driver_class)
155
- rescue Sequel::AdapterNotFound => e
156
- message = if @jdbc_driver_library.nil?
157
- ":jdbc_driver_library is not set, are you sure you included
158
- the proper driver client libraries in your classpath?"
159
- else
160
- "Are you sure you've included the correct jdbc driver in :jdbc_driver_library?"
161
- end
162
- raise LogStash::ConfigurationError, "#{e}. #{message}"
163
- end
164
- @database = jdbc_connect()
165
- @database.extension(:pagination)
166
- if @jdbc_default_timezone
167
- @database.extension(:named_timezones)
168
- @database.timezone = @jdbc_default_timezone
169
- end
170
- if @jdbc_validate_connection
171
- @database.extension(:connection_validator)
172
- @database.pool.connection_validation_timeout = @jdbc_validation_timeout
173
- end
174
- @database.fetch_size = @jdbc_fetch_size unless @jdbc_fetch_size.nil?
175
- begin
176
- @database.test_connection
177
- rescue Sequel::DatabaseConnectionError => e
178
- @logger.warn("Failed test_connection.", :exception => e)
179
- close_jdbc_connection
180
-
181
- #TODO return false and let the plugin raise a LogStash::ConfigurationError
182
- raise e
183
- end
184
-
185
- @database.sql_log_level = @sql_log_level.to_sym
186
- @database.logger = @logger
187
-
188
- @database.extension :identifier_mangling
189
-
190
- if @lowercase_column_names
191
- @database.identifier_output_method = :downcase
192
- else
193
- @database.identifier_output_method = :to_s
194
- end
195
- end
196
-
197
- public
198
- def prepare_jdbc_connection
199
- @connection_lock = ReentrantLock.new
200
- end
201
-
202
- public
203
- def close_jdbc_connection
204
- begin
205
- # pipeline restarts can also close the jdbc connection, block until the current executing statement is finished to avoid leaking connections
206
- # connections in use won't really get closed
207
- @connection_lock.lock
208
- @database.disconnect if @database
209
- rescue => e
210
- @logger.warn("Failed to close connection", :exception => e)
211
- ensure
212
- @connection_lock.unlock
213
- end
214
- end
215
-
216
- public
217
- def execute_statement(statement, parameters)
218
- success = false
219
- @connection_lock.lock
220
- open_jdbc_connection
221
- begin
222
- parameters = symbolized_params(parameters)
223
- query = @database[statement, parameters]
224
-
225
- sql_last_value = @use_column_value ? @value_tracker.value : Time.now.utc
226
- @tracking_column_warning_sent = false
227
- @logger.debug? and @logger.debug("Executing JDBC query", :statement => statement, :parameters => parameters, :count => query.count)
228
-
229
- perform_query(query) do |row|
230
- sql_last_value = get_column_value(row) if @use_column_value
231
- yield extract_values_from(row)
232
- end
233
- success = true
234
- rescue Sequel::DatabaseConnectionError, Sequel::DatabaseError => e
235
- @logger.warn("Exception when executing JDBC query", :exception => e)
236
- else
237
- @value_tracker.set_value(sql_last_value)
238
- ensure
239
- close_jdbc_connection
240
- @connection_lock.unlock
241
- end
242
- return success
243
- end
244
-
245
- # Performs the query, respecting our pagination settings, yielding once per row of data
246
- # @param query [Sequel::Dataset]
247
- # @yieldparam row [Hash{Symbol=>Object}]
248
- private
249
- def perform_query(query)
250
- if @jdbc_paging_enabled
251
- query.each_page(@jdbc_page_size) do |paged_dataset|
252
- paged_dataset.each do |row|
253
- yield row
254
- end
255
- end
256
- else
257
- query.each do |row|
258
- yield row
259
- end
260
- end
261
- end
262
-
263
- public
264
- def get_column_value(row)
265
- if !row.has_key?(@tracking_column.to_sym)
266
- if !@tracking_column_warning_sent
267
- @logger.warn("tracking_column not found in dataset.", :tracking_column => @tracking_column)
268
- @tracking_column_warning_sent = true
269
- end
270
- # If we can't find the tracking column, return the current value in the ivar
271
- @sql_last_value
272
- else
273
- # Otherwise send the updated tracking column
274
- row[@tracking_column.to_sym]
275
- end
276
- end
277
-
278
- # Symbolize parameters keys to use with Sequel
279
- private
280
- def symbolized_params(parameters)
281
- parameters.inject({}) do |hash,(k,v)|
282
- case v
283
- when LogStash::Timestamp
284
- hash[k.to_sym] = v.time
285
- else
286
- hash[k.to_sym] = v
287
- end
288
- hash
289
- end
290
- end
291
-
292
- private
293
- #Stringify row keys and decorate values when necessary
294
- def extract_values_from(row)
295
- Hash[row.map { |k, v| [k.to_s, decorate_value(v)] }]
296
- end
297
-
298
- private
299
- def decorate_value(value)
300
- if value.is_a?(Time)
301
- # transform it to LogStash::Timestamp as required by LS
302
- LogStash::Timestamp.new(value)
303
- elsif value.is_a?(Date)
304
- LogStash::Timestamp.new(value.to_time)
305
- elsif value.is_a?(DateTime)
306
- # Manual timezone conversion detected.
307
- # This is slower, so we put it in as a conditional case.
308
- LogStash::Timestamp.new(Time.parse(value.to_s))
309
- else
310
- value
311
- end
312
- end
313
- end