logstash-integration-jdbc 5.2.6 → 5.4.0
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 +8 -0
- data/docs/filter-jdbc_streaming.asciidoc +1 -1
- data/docs/input-jdbc.asciidoc +15 -3
- data/lib/logstash/filters/jdbc_static.rb +4 -9
- data/lib/logstash/inputs/jdbc.rb +6 -12
- data/lib/logstash/plugin_mixins/jdbc/jdbc.rb +8 -3
- data/lib/logstash/plugin_mixins/jdbc/timezone_proxy.rb +61 -0
- data/lib/logstash/plugin_mixins/jdbc/value_tracking.rb +1 -1
- data/logstash-integration-jdbc.gemspec +3 -4
- data/spec/inputs/jdbc_spec.rb +45 -14
- data/spec/plugin_mixins/jdbc/timezone_proxy_spec.rb +68 -0
- metadata +15 -15
- data/lib/logstash/plugin_mixins/jdbc/scheduler.rb +0 -175
- data/spec/plugin_mixins/jdbc/scheduler_spec.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28cba197157488c1839fc9a948c3f27916b26af07f28c592f537434bd84722a6
|
4
|
+
data.tar.gz: fe2263ea7ed36abdeb9fd3d9597958724be77a8653950e9d554a212c1e3dd106
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76543ead6834631efaca25d154abe7ee7594943dff0c27d90e7588ed3aa651de427fc3372cffac348f6f810f11cfe13864d8f26af7d09fd34470491a6a4c66b3
|
7
|
+
data.tar.gz: de71ae5f8c54dfa08d67e1e848e59fc1779477c41246cee120debf633301829e931b7103546de0bef482002eb7b5296b748d6ad6caf0bb5457abe7205db79b6e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 5.4.0
|
2
|
+
- Ambiguous Timestamp Support [#92](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/92)
|
3
|
+
- FIX: when encountering an ambiguous timestamp, the JDBC Input no longer crashes
|
4
|
+
- Added support for disambiguating timestamps in daylight saving time (DST) overlap periods
|
5
|
+
|
6
|
+
## 5.3.0
|
7
|
+
- Refactor: start using scheduler mixin [#110](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/110)
|
8
|
+
|
1
9
|
## 5.2.6
|
2
10
|
- Fix: change default path of 'last_run_metadata_path' to be rooted in the LS data.path folder and not in $HOME [#106](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/106)
|
3
11
|
|
@@ -21,7 +21,7 @@ include::{include_path}/plugin_header-integration.asciidoc[]
|
|
21
21
|
|
22
22
|
==== Description
|
23
23
|
|
24
|
-
This filter executes a SQL query and
|
24
|
+
This filter executes a SQL query and stores the result set in the field
|
25
25
|
specified as `target`.
|
26
26
|
It will cache the results locally in an LRU cache with expiry.
|
27
27
|
|
data/docs/input-jdbc.asciidoc
CHANGED
@@ -286,10 +286,14 @@ JDBC connection string
|
|
286
286
|
===== `jdbc_default_timezone`
|
287
287
|
|
288
288
|
* Value type is <<string,string>>
|
289
|
+
** Value should be a canonical timezone or offset, such as `Europe/Paris` or `Etc/GMT+3`
|
290
|
+
** Value _may_ include square-bracketed extensions, such as `America/Denver[dst_enabled_on_overlap:true]`
|
289
291
|
* There is no default value for this setting.
|
290
292
|
|
291
|
-
|
292
|
-
|
293
|
+
[id="plugins-{type}s-{plugin}-jdbc_timezone_conv"]
|
294
|
+
====== Timezone conversion
|
295
|
+
|
296
|
+
Logstash and Elasticsearch expect timestamps to be expressed in UTC terms.
|
293
297
|
If your database has recorded timestamps that are relative to another timezone,
|
294
298
|
the database timezone if you will, then set this setting to be the timezone that
|
295
299
|
the database is using. However, as SQL does not allow for timezone data in
|
@@ -299,7 +303,15 @@ in relative UTC time in ISO8601 format.
|
|
299
303
|
|
300
304
|
Using this setting will manually assign a specified timezone offset, instead
|
301
305
|
of using the timezone setting of the local machine. You must use a canonical
|
302
|
-
timezone,
|
306
|
+
timezone, `America/Denver`, for example.
|
307
|
+
|
308
|
+
[id="plugins-{type}s-{plugin}-jdbc_ambiguous_timestamps"]
|
309
|
+
===== Ambiguous timestamps
|
310
|
+
|
311
|
+
While it is common to store local times in SQL's timestamp column type, many timezones change their offset during the course of a calendar year and therefore cannot be used with SQL's timestamp type to represent an ordered, continuous timeline.
|
312
|
+
For example in the `America/Chicago` zone when daylight saving time (DST) ends in the autumn, the clock rolls from `01:59:59` back to `01:00:00`, making any timestamp in the 2-hour period between `01:00:00CDT` and `02:00:00CST` on that day ambiguous.
|
313
|
+
|
314
|
+
When encountering an ambiguous timestamp caused by a DST transition, the query will fail unless the timezone specified here includes a square-bracketed instruction for how to handle overlapping periods (such as: `America/Chicago[dst_enabled_on_overlap:true]` or `Australia/Melbourne[dst_enabled_on_overlap:false]`).
|
303
315
|
|
304
316
|
[id="plugins-{type}s-{plugin}-plugin_timezone"]
|
305
317
|
===== `plugin_timezone`
|
@@ -3,7 +3,7 @@ require "logstash-integration-jdbc_jars"
|
|
3
3
|
require "logstash/filters/base"
|
4
4
|
require "logstash/namespace"
|
5
5
|
require "logstash/plugin_mixins/ecs_compatibility_support"
|
6
|
-
require "logstash/plugin_mixins/
|
6
|
+
require "logstash/plugin_mixins/scheduler"
|
7
7
|
require_relative "jdbc/loader"
|
8
8
|
require_relative "jdbc/loader_schedule"
|
9
9
|
require_relative "jdbc/repeating_load_runner"
|
@@ -19,6 +19,8 @@ module LogStash module Filters class JdbcStatic < LogStash::Filters::Base
|
|
19
19
|
# adds ecs_compatibility config which could be :disabled or :v1
|
20
20
|
include LogStash::PluginMixins::ECSCompatibilitySupport(:disabled, :v1, :v8 => :v1)
|
21
21
|
|
22
|
+
include LogStash::PluginMixins::Scheduler
|
23
|
+
|
22
24
|
config_name "jdbc_static"
|
23
25
|
|
24
26
|
# Define the loaders, an Array of Hashes, to fetch remote data and create local tables.
|
@@ -162,7 +164,6 @@ module LogStash module Filters class JdbcStatic < LogStash::Filters::Base
|
|
162
164
|
end
|
163
165
|
|
164
166
|
def close
|
165
|
-
@scheduler.stop if @scheduler
|
166
167
|
@parsed_loaders.each(&:close)
|
167
168
|
@processor.close
|
168
169
|
end
|
@@ -208,13 +209,7 @@ module LogStash module Filters class JdbcStatic < LogStash::Filters::Base
|
|
208
209
|
if @loader_schedule
|
209
210
|
@loader_runner = Jdbc::RepeatingLoadRunner.new(*runner_args)
|
210
211
|
@loader_runner.initial_load
|
211
|
-
@
|
212
|
-
start_cron_scheduler(@loader_schedule, thread_name: "[#{id}]-jdbc_static__scheduler") { @loader_runner.repeated_load }
|
213
|
-
cron_job = @scheduler.cron_jobs.first
|
214
|
-
if cron_job
|
215
|
-
frequency = cron_job.respond_to?(:rough_frequency) ? cron_job.rough_frequency : cron_job.frequency
|
216
|
-
logger.info("Loaders will execute every #{frequency} seconds", loader_schedule: @loader_schedule)
|
217
|
-
end
|
212
|
+
scheduler.cron(@loader_schedule) { @loader_runner.repeated_load }
|
218
213
|
else
|
219
214
|
@loader_runner = Jdbc::SingleLoadRunner.new(*runner_args)
|
220
215
|
@loader_runner.initial_load
|
data/lib/logstash/inputs/jdbc.rb
CHANGED
@@ -3,12 +3,11 @@ require "logstash/inputs/base"
|
|
3
3
|
require "logstash/namespace"
|
4
4
|
require "logstash/plugin_mixins/jdbc/common"
|
5
5
|
require "logstash/plugin_mixins/jdbc/jdbc"
|
6
|
-
require "logstash/plugin_mixins/jdbc/scheduler"
|
7
6
|
require "logstash/plugin_mixins/ecs_compatibility_support"
|
8
7
|
require "logstash/plugin_mixins/ecs_compatibility_support/target_check"
|
9
8
|
require "logstash/plugin_mixins/validator_support/field_reference_validation_adapter"
|
10
|
-
|
11
9
|
require "logstash/plugin_mixins/event_support/event_factory_adapter"
|
10
|
+
require "logstash/plugin_mixins/scheduler"
|
12
11
|
require "fileutils"
|
13
12
|
|
14
13
|
# this require_relative returns early unless the JRuby version is between 9.2.0.0 and 9.2.8.0
|
@@ -147,6 +146,8 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
|
|
147
146
|
# adds :field_reference validator adapter
|
148
147
|
extend LogStash::PluginMixins::ValidatorSupport::FieldReferenceValidationAdapter
|
149
148
|
|
149
|
+
include LogStash::PluginMixins::Scheduler
|
150
|
+
|
150
151
|
config_name "jdbc"
|
151
152
|
|
152
153
|
# If undefined, Logstash will complain, even if codec is unused.
|
@@ -260,7 +261,6 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
|
|
260
261
|
end
|
261
262
|
end
|
262
263
|
|
263
|
-
require "rufus/scheduler"
|
264
264
|
prepare_jdbc_connection
|
265
265
|
|
266
266
|
if @use_column_value
|
@@ -319,22 +319,16 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
|
|
319
319
|
def run(queue)
|
320
320
|
load_driver
|
321
321
|
if @schedule
|
322
|
-
# input thread
|
323
|
-
@
|
324
|
-
|
325
|
-
@scheduler.join
|
322
|
+
# scheduler input thread name example: "[my-oracle]|input|jdbc|scheduler"
|
323
|
+
scheduler.cron(@schedule) { execute_query(queue) }
|
324
|
+
scheduler.join
|
326
325
|
else
|
327
326
|
execute_query(queue)
|
328
327
|
end
|
329
328
|
end # def run
|
330
329
|
|
331
|
-
def close
|
332
|
-
@scheduler.shutdown if @scheduler
|
333
|
-
end
|
334
|
-
|
335
330
|
def stop
|
336
331
|
close_jdbc_connection
|
337
|
-
@scheduler.shutdown(:wait) if @scheduler
|
338
332
|
end
|
339
333
|
|
340
334
|
private
|
@@ -4,6 +4,7 @@ require "logstash/config/mixin"
|
|
4
4
|
require "time"
|
5
5
|
require "date"
|
6
6
|
require_relative "value_tracking"
|
7
|
+
require_relative "timezone_proxy"
|
7
8
|
require_relative "statement_handler"
|
8
9
|
|
9
10
|
java_import java.util.concurrent.locks.ReentrantLock
|
@@ -82,7 +83,8 @@ module LogStash module PluginMixins module Jdbc
|
|
82
83
|
# Using this setting will manually assign a specified timezone offset, instead
|
83
84
|
# of using the timezone setting of the local machine. You must use a canonical
|
84
85
|
# timezone, *America/Denver*, for example.
|
85
|
-
config :jdbc_default_timezone, :validate => :
|
86
|
+
config :jdbc_default_timezone, :validate => :jdbc_timezone_spec
|
87
|
+
extend TimezoneProxy::JDBCTimezoneSpecValidator
|
86
88
|
|
87
89
|
# General/Vendor-specific Sequel configuration options.
|
88
90
|
#
|
@@ -157,7 +159,7 @@ module LogStash module PluginMixins module Jdbc
|
|
157
159
|
@database.extension(:pagination)
|
158
160
|
if @jdbc_default_timezone
|
159
161
|
@database.extension(:named_timezones)
|
160
|
-
@database.timezone = @jdbc_default_timezone
|
162
|
+
@database.timezone = TimezoneProxy.load(@jdbc_default_timezone)
|
161
163
|
end
|
162
164
|
if @jdbc_validate_connection
|
163
165
|
@database.extension(:connection_validator)
|
@@ -218,7 +220,10 @@ module LogStash module PluginMixins module Jdbc
|
|
218
220
|
yield extract_values_from(row)
|
219
221
|
end
|
220
222
|
success = true
|
221
|
-
rescue Sequel::DatabaseConnectionError,
|
223
|
+
rescue Sequel::DatabaseConnectionError,
|
224
|
+
Sequel::DatabaseError,
|
225
|
+
Sequel::InvalidValue,
|
226
|
+
Java::JavaSql::SQLException => e
|
222
227
|
details = { exception: e.class, message: e.message }
|
223
228
|
details[:cause] = e.cause.inspect if e.cause
|
224
229
|
details[:backtrace] = e.backtrace if @logger.debug?
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tzinfo'
|
4
|
+
|
5
|
+
module LogStash module PluginMixins module Jdbc
|
6
|
+
##
|
7
|
+
# This `TimezoneProxy` allows timezone specs to include extensions indicating preference for ambiguous handling.
|
8
|
+
# @see TimezoneProxy::parse
|
9
|
+
module TimezoneProxy
|
10
|
+
##
|
11
|
+
# @param timezone_spec [String]: a timezone spec, consisting of any valid timezone identifier
|
12
|
+
# followed by square-bracketed extensions. Currently-supported
|
13
|
+
# extensions are:
|
14
|
+
# `dst_enabled_on_overlap:(true|false)`: when encountering an ambiguous time
|
15
|
+
# due to daylight-savings transition,
|
16
|
+
# assume DST to be either enabled or
|
17
|
+
# disabled instead of raising an
|
18
|
+
# AmbiguousTime exception
|
19
|
+
# @return [TZInfo::Timezone]
|
20
|
+
def self.load(timezone_spec)
|
21
|
+
# re-load pass-through
|
22
|
+
return timezone_spec if timezone_spec.kind_of?(::TZInfo::Timezone)
|
23
|
+
|
24
|
+
parsed_spec = /\A(?<name>[^\[]+)(\[(?<extensions>[^\]]*)\])?\z/.match(timezone_spec)
|
25
|
+
|
26
|
+
timezone = ::TZInfo::Timezone.get(parsed_spec[:name])
|
27
|
+
return timezone unless parsed_spec[:extensions]
|
28
|
+
|
29
|
+
parsed_spec[:extensions].split(';').each do |extension_spec|
|
30
|
+
timezone = case extension_spec
|
31
|
+
when 'dst_enabled_on_overlap:true' then timezone.dup.extend(PeriodForLocalWithDSTPreference::ON)
|
32
|
+
when 'dst_enabled_on_overlap:false' then timezone.dup.extend(PeriodForLocalWithDSTPreference::OFF)
|
33
|
+
else fail(ArgumentError, "Invalid timezone extension `#{extension_spec}`")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
timezone
|
38
|
+
end
|
39
|
+
|
40
|
+
module JDBCTimezoneSpecValidator
|
41
|
+
def validate_value(value, validator_name)
|
42
|
+
return super(value, validator_name) unless validator_name == :jdbc_timezone_spec
|
43
|
+
|
44
|
+
[true, TimezoneProxy.load(value)] rescue [false, $!.message]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# @api private
|
50
|
+
class PeriodForLocalWithDSTPreference < Module
|
51
|
+
def initialize(default_dst_enabled_on_overlap)
|
52
|
+
define_method(:period_for_local) do |localtime, dst_enabled_on_overlap=nil, &dismabiguation_block|
|
53
|
+
super(localtime, dst_enabled_on_overlap.nil? ? default_dst_enabled_on_overlap : dst_enabled_on_overlap, &dismabiguation_block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ON = new(true)
|
58
|
+
OFF = new(false)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end; end; end
|
@@ -14,7 +14,7 @@ module LogStash module PluginMixins module Jdbc
|
|
14
14
|
# use this irrespective of the jdbc_default_timezone setting
|
15
15
|
NumericValueTracker.new(handler)
|
16
16
|
else
|
17
|
-
if plugin.jdbc_default_timezone.nil?
|
17
|
+
if plugin.jdbc_default_timezone.nil?
|
18
18
|
# no TZ stuff for Sequel, use Time
|
19
19
|
TimeValueTracker.new(handler)
|
20
20
|
else
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'logstash-integration-jdbc'
|
3
|
-
s.version = '5.
|
3
|
+
s.version = '5.4.0'
|
4
4
|
s.licenses = ['Apache License (2.0)']
|
5
5
|
s.summary = "Integration with JDBC - input and filter plugins"
|
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"
|
@@ -34,12 +34,11 @@ Gem::Specification.new do |s|
|
|
34
34
|
|
35
35
|
s.add_runtime_dependency 'tzinfo'
|
36
36
|
s.add_runtime_dependency 'tzinfo-data'
|
37
|
-
|
38
|
-
# but works with newer rufus-scheduler >= 3.5 as well
|
39
|
-
s.add_runtime_dependency 'rufus-scheduler'
|
37
|
+
|
40
38
|
s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~>1.3'
|
41
39
|
s.add_runtime_dependency "logstash-mixin-validator_support", '~> 1.0'
|
42
40
|
s.add_runtime_dependency "logstash-mixin-event_support", '~> 1.0'
|
41
|
+
s.add_runtime_dependency "logstash-mixin-scheduler", '~> 1.0'
|
43
42
|
|
44
43
|
s.add_development_dependency "childprocess"
|
45
44
|
s.add_development_dependency 'logstash-devutils', '>= 2.3'
|
data/spec/inputs/jdbc_spec.rb
CHANGED
@@ -251,18 +251,6 @@ describe LogStash::Inputs::Jdbc do
|
|
251
251
|
Timecop.return
|
252
252
|
end
|
253
253
|
|
254
|
-
it "cleans up scheduler resources on close" do
|
255
|
-
runner = Thread.new do
|
256
|
-
plugin.run(queue)
|
257
|
-
end
|
258
|
-
sleep 1
|
259
|
-
plugin.do_close
|
260
|
-
|
261
|
-
scheduler = plugin.instance_variable_get(:@scheduler)
|
262
|
-
expect(scheduler).to_not be_nil
|
263
|
-
expect(scheduler.down?).to be_truthy
|
264
|
-
end
|
265
|
-
|
266
254
|
end
|
267
255
|
|
268
256
|
context "when scheduling and previous runs are to be preserved" do
|
@@ -708,7 +696,7 @@ describe LogStash::Inputs::Jdbc do
|
|
708
696
|
"last_run_metadata_path" => Stud::Temporary.pathname }
|
709
697
|
end
|
710
698
|
|
711
|
-
let(:nums) { [BigDecimal
|
699
|
+
let(:nums) { [BigDecimal(10), BigDecimal(20), BigDecimal(30), BigDecimal(40), BigDecimal(50)] }
|
712
700
|
|
713
701
|
before do
|
714
702
|
plugin.register
|
@@ -1516,6 +1504,49 @@ describe LogStash::Inputs::Jdbc do
|
|
1516
1504
|
end
|
1517
1505
|
end
|
1518
1506
|
|
1507
|
+
context "when retrieving records with ambiguous timestamps" do
|
1508
|
+
|
1509
|
+
let(:settings) do
|
1510
|
+
{
|
1511
|
+
"statement" => "SELECT * from types_table",
|
1512
|
+
"jdbc_default_timezone" => jdbc_default_timezone
|
1513
|
+
}
|
1514
|
+
end
|
1515
|
+
|
1516
|
+
before(:each) do
|
1517
|
+
db << "INSERT INTO types_table (num, string, started_at, custom_time, ranking) VALUES (1, 'A test', '1999-12-31', '2021-11-07 01:23:45', 95.67)"
|
1518
|
+
plugin.register
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
context "when initialized with a preference for DST being enabled" do
|
1522
|
+
let(:jdbc_default_timezone) { 'America/Chicago[dst_enabled_on_overlap:true]' }
|
1523
|
+
|
1524
|
+
it 'treats the timestamp column as if DST was enabled' do
|
1525
|
+
plugin.run(queue)
|
1526
|
+
event = queue.pop
|
1527
|
+
expect(event.get("custom_time")).to be_a_logstash_timestamp_equivalent_to("2021-11-07T06:23:45Z")
|
1528
|
+
end
|
1529
|
+
end
|
1530
|
+
context "when initialized with a preference for DST being disabled" do
|
1531
|
+
let(:jdbc_default_timezone) { 'America/Chicago[dst_enabled_on_overlap:false]' }
|
1532
|
+
|
1533
|
+
it 'treats the timestamp column as if DST was disabled' do
|
1534
|
+
plugin.run(queue)
|
1535
|
+
event = queue.pop
|
1536
|
+
expect(event.get("custom_time")).to be_a_logstash_timestamp_equivalent_to("2021-11-07T07:23:45Z")
|
1537
|
+
end
|
1538
|
+
end
|
1539
|
+
context "when initialized without a preference for DST being enabled or disabled" do
|
1540
|
+
before(:each) { allow(plugin.logger).to receive(:warn) }
|
1541
|
+
let(:jdbc_default_timezone) { 'America/Chicago' }
|
1542
|
+
|
1543
|
+
it 'the error results in helpful log warning' do
|
1544
|
+
plugin.run(queue)
|
1545
|
+
expect(plugin.logger).to have_received(:warn).with(a_string_including("Exception when executing JDBC query"), a_hash_including(:message => a_string_including("2021-11-07 01:23:45 is an ambiguous local time")))
|
1546
|
+
end
|
1547
|
+
end
|
1548
|
+
end
|
1549
|
+
|
1519
1550
|
context "when an unreadable jdbc_driver_path entry is present" do
|
1520
1551
|
let(:driver_jar_path) do
|
1521
1552
|
jar_file = $CLASSPATH.find { |name| name.index(Jdbc::Derby.driver_jar) }
|
@@ -1717,7 +1748,7 @@ describe LogStash::Inputs::Jdbc do
|
|
1717
1748
|
let(:jdbc_driver_class) { "org.apache.NonExistentDriver" }
|
1718
1749
|
it "raises a loading error" do
|
1719
1750
|
expect { plugin.send(:load_driver) }.to raise_error LogStash::PluginLoadingError,
|
1720
|
-
/
|
1751
|
+
/ClassNotFoundException: org.apache.NonExistentDriver/
|
1721
1752
|
end
|
1722
1753
|
end
|
1723
1754
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/plugin_mixins/jdbc/timezone_proxy"
|
4
|
+
|
5
|
+
describe LogStash::PluginMixins::Jdbc::TimezoneProxy do
|
6
|
+
subject(:timezone) { described_class.load(timezone_spec) }
|
7
|
+
|
8
|
+
context 'when handling a daylight-savings ambiguous time' do
|
9
|
+
context 'without extensions' do
|
10
|
+
let(:timezone_spec) { 'America/Los_Angeles[]' }
|
11
|
+
it 'raises an AmbiguousTime error' do
|
12
|
+
expect { timezone.local_time(2021,11,7,1,17) }.to raise_error(::TZInfo::AmbiguousTime)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
context 'with extension `dst_enabled_on_overlap:true`' do
|
16
|
+
let(:timezone_spec) { 'America/Los_Angeles[dst_enabled_on_overlap:true]' }
|
17
|
+
it 'resolves as if DST were enabled' do
|
18
|
+
timestamp = timezone.local_time(2021,11,7,1,17)
|
19
|
+
aggregate_failures do
|
20
|
+
expect(timestamp.dst?).to be true
|
21
|
+
expect(timestamp.zone).to eq('PDT') # Pacific Daylight Time
|
22
|
+
expect(timestamp.getutc).to eq(Time.utc(2021,11,7,8,17))
|
23
|
+
expect(timestamp.utc_offset).to eq( -7 * 3600 )
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
context 'with extension `dst_enabled_on_overlap:false`' do
|
28
|
+
let(:timezone_spec) { 'America/Los_Angeles[dst_enabled_on_overlap:false]' }
|
29
|
+
it 'resolves as if DST were disabled' do
|
30
|
+
timestamp = timezone.local_time(2021,11,7,1,17)
|
31
|
+
aggregate_failures do
|
32
|
+
expect(timestamp.dst?).to be false
|
33
|
+
expect(timestamp.zone).to eq('PST') # Pacific Standard Time
|
34
|
+
expect(timestamp.getutc).to eq(Time.utc(2021,11,7,9,17))
|
35
|
+
expect(timestamp.utc_offset).to eq( -8 * 3600 )
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context '#load' do
|
42
|
+
context 'when spec is a normal timezone instance' do
|
43
|
+
let(:timezone_spec) { ::TZInfo::Timezone.get('America/Los_Angeles') }
|
44
|
+
it 'returns that instance' do
|
45
|
+
expect(timezone).to be(timezone_spec)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context 'when spec is a valid unextended timezone spec' do
|
49
|
+
let(:timezone_spec) { 'America/Los_Angeles' }
|
50
|
+
it 'returns the canonical timezone' do
|
51
|
+
expect(timezone).to eq(::TZInfo::Timezone.get('America/Los_Angeles'))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
context 'when spec is an invalid timezone spec' do
|
55
|
+
let(:timezone_spec) { 'NotAValidTimezoneIdentifier' }
|
56
|
+
|
57
|
+
it 'propagates the TZInfo exception' do
|
58
|
+
expect { timezone }.to raise_exception(::TZInfo::InvalidTimezoneIdentifier)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
context 'with invalid extension' do
|
62
|
+
let(:timezone_spec) { 'America/Los_Angeles[dst_enabled_on_overlap:false;nope:wrong]' }
|
63
|
+
it 'raises an exception with a helpful message' do
|
64
|
+
expect { timezone }.to raise_exception(ArgumentError, a_string_including("Invalid timezone extension `nope:wrong`"))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-integration-jdbc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -131,38 +131,38 @@ dependencies:
|
|
131
131
|
- !ruby/object:Gem::Dependency
|
132
132
|
requirement: !ruby/object:Gem::Requirement
|
133
133
|
requirements:
|
134
|
-
- - "
|
134
|
+
- - "~>"
|
135
135
|
- !ruby/object:Gem::Version
|
136
|
-
version: '
|
137
|
-
name:
|
136
|
+
version: '1.3'
|
137
|
+
name: logstash-mixin-ecs_compatibility_support
|
138
138
|
prerelease: false
|
139
139
|
type: :runtime
|
140
140
|
version_requirements: !ruby/object:Gem::Requirement
|
141
141
|
requirements:
|
142
|
-
- - "
|
142
|
+
- - "~>"
|
143
143
|
- !ruby/object:Gem::Version
|
144
|
-
version: '
|
144
|
+
version: '1.3'
|
145
145
|
- !ruby/object:Gem::Dependency
|
146
146
|
requirement: !ruby/object:Gem::Requirement
|
147
147
|
requirements:
|
148
148
|
- - "~>"
|
149
149
|
- !ruby/object:Gem::Version
|
150
|
-
version: '1.
|
151
|
-
name: logstash-mixin-
|
150
|
+
version: '1.0'
|
151
|
+
name: logstash-mixin-validator_support
|
152
152
|
prerelease: false
|
153
153
|
type: :runtime
|
154
154
|
version_requirements: !ruby/object:Gem::Requirement
|
155
155
|
requirements:
|
156
156
|
- - "~>"
|
157
157
|
- !ruby/object:Gem::Version
|
158
|
-
version: '1.
|
158
|
+
version: '1.0'
|
159
159
|
- !ruby/object:Gem::Dependency
|
160
160
|
requirement: !ruby/object:Gem::Requirement
|
161
161
|
requirements:
|
162
162
|
- - "~>"
|
163
163
|
- !ruby/object:Gem::Version
|
164
164
|
version: '1.0'
|
165
|
-
name: logstash-mixin-
|
165
|
+
name: logstash-mixin-event_support
|
166
166
|
prerelease: false
|
167
167
|
type: :runtime
|
168
168
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -176,7 +176,7 @@ dependencies:
|
|
176
176
|
- - "~>"
|
177
177
|
- !ruby/object:Gem::Version
|
178
178
|
version: '1.0'
|
179
|
-
name: logstash-mixin-
|
179
|
+
name: logstash-mixin-scheduler
|
180
180
|
prerelease: false
|
181
181
|
type: :runtime
|
182
182
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -277,8 +277,8 @@ files:
|
|
277
277
|
- lib/logstash/inputs/tzinfo_jruby_patch.rb
|
278
278
|
- lib/logstash/plugin_mixins/jdbc/common.rb
|
279
279
|
- lib/logstash/plugin_mixins/jdbc/jdbc.rb
|
280
|
-
- lib/logstash/plugin_mixins/jdbc/scheduler.rb
|
281
280
|
- lib/logstash/plugin_mixins/jdbc/statement_handler.rb
|
281
|
+
- lib/logstash/plugin_mixins/jdbc/timezone_proxy.rb
|
282
282
|
- lib/logstash/plugin_mixins/jdbc/value_tracking.rb
|
283
283
|
- lib/logstash/plugin_mixins/jdbc_streaming.rb
|
284
284
|
- lib/logstash/plugin_mixins/jdbc_streaming/cache_payload.rb
|
@@ -306,7 +306,7 @@ files:
|
|
306
306
|
- spec/helpers/derbyrun.jar
|
307
307
|
- spec/inputs/integration/integ_spec.rb
|
308
308
|
- spec/inputs/jdbc_spec.rb
|
309
|
-
- spec/plugin_mixins/jdbc/
|
309
|
+
- spec/plugin_mixins/jdbc/timezone_proxy_spec.rb
|
310
310
|
- spec/plugin_mixins/jdbc_streaming/parameter_handler_spec.rb
|
311
311
|
- vendor/jar-dependencies/org/apache/derby/derby/10.14.1.0/derby-10.14.1.0.jar
|
312
312
|
- vendor/jar-dependencies/org/apache/derby/derbyclient/10.14.1.0/derbyclient-10.14.1.0.jar
|
@@ -359,5 +359,5 @@ test_files:
|
|
359
359
|
- spec/helpers/derbyrun.jar
|
360
360
|
- spec/inputs/integration/integ_spec.rb
|
361
361
|
- spec/inputs/jdbc_spec.rb
|
362
|
-
- spec/plugin_mixins/jdbc/
|
362
|
+
- spec/plugin_mixins/jdbc/timezone_proxy_spec.rb
|
363
363
|
- spec/plugin_mixins/jdbc_streaming/parameter_handler_spec.rb
|
@@ -1,175 +0,0 @@
|
|
1
|
-
require 'rufus/scheduler'
|
2
|
-
|
3
|
-
require 'logstash/util/loggable'
|
4
|
-
|
5
|
-
module LogStash module PluginMixins module Jdbc
|
6
|
-
class Scheduler < Rufus::Scheduler
|
7
|
-
|
8
|
-
include LogStash::Util::Loggable
|
9
|
-
|
10
|
-
# Rufus::Scheduler >= 3.4 moved the Time impl into a gem EoTime = ::EtOrbi::EoTime`
|
11
|
-
# Rufus::Scheduler 3.1 - 3.3 using it's own Time impl `Rufus::Scheduler::ZoTime`
|
12
|
-
TimeImpl = defined?(Rufus::Scheduler::EoTime) ? Rufus::Scheduler::EoTime :
|
13
|
-
(defined?(Rufus::Scheduler::ZoTime) ? Rufus::Scheduler::ZoTime : ::Time)
|
14
|
-
|
15
|
-
# @param cron [String] cron-line
|
16
|
-
# @param opts [Hash] scheduler options
|
17
|
-
# @return scheduler instance
|
18
|
-
def self.start_cron_scheduler(cron, opts = {}, &block)
|
19
|
-
unless block_given?
|
20
|
-
raise ArgumentError, 'missing (cron scheduler) block - worker task to execute'
|
21
|
-
end
|
22
|
-
scheduler = new_scheduler(opts)
|
23
|
-
scheduler.schedule_cron(cron, &block)
|
24
|
-
scheduler
|
25
|
-
end
|
26
|
-
|
27
|
-
# @param opts [Hash] scheduler options
|
28
|
-
# @return scheduler instance
|
29
|
-
def self.new_scheduler(opts)
|
30
|
-
unless opts.key?(:thread_name)
|
31
|
-
raise ArgumentError, 'thread_name: option is required to be able to distinguish multiple scheduler threads'
|
32
|
-
end
|
33
|
-
opts[:max_work_threads] ||= 1
|
34
|
-
# amount the scheduler thread sleeps between checking whether jobs
|
35
|
-
# should trigger, default is 0.3 which is a bit too often ...
|
36
|
-
# in theory the cron expression '* * * * * *' supports running jobs
|
37
|
-
# every second but this is very rare, we could potentially go higher
|
38
|
-
opts[:frequency] ||= 1.0
|
39
|
-
|
40
|
-
new(opts)
|
41
|
-
end
|
42
|
-
|
43
|
-
# @overload
|
44
|
-
def timeout_jobs
|
45
|
-
# Rufus relies on `Thread.list` which is a blocking operation and with many schedulers
|
46
|
-
# (and threads) within LS will have a negative impact on performance as scheduler
|
47
|
-
# threads will end up waiting to obtain the `Thread.list` lock.
|
48
|
-
#
|
49
|
-
# However, this isn't necessary we can easily detect whether there are any jobs
|
50
|
-
# that might need to timeout: only when `@opts[:timeout]` is set causes worker thread(s)
|
51
|
-
# to have a `Thread.current[:rufus_scheduler_timeout]` that is not nil
|
52
|
-
return unless @opts[:timeout]
|
53
|
-
super
|
54
|
-
end
|
55
|
-
|
56
|
-
# @overload
|
57
|
-
def work_threads(query = :all)
|
58
|
-
if query == :__all_no_cache__ # special case from JobDecorator#start_work_thread
|
59
|
-
@_work_threads = nil # when a new worker thread is being added reset
|
60
|
-
return super(:all)
|
61
|
-
end
|
62
|
-
|
63
|
-
# Gets executed every time a job is triggered, we're going to cache the
|
64
|
-
# worker threads for this scheduler (to avoid `Thread.list`) - they only
|
65
|
-
# change when a new thread is being started from #start_work_thread ...
|
66
|
-
work_threads = @_work_threads
|
67
|
-
if work_threads.nil?
|
68
|
-
work_threads = threads.select { |t| t[:rufus_scheduler_work_thread] }
|
69
|
-
@_work_threads = work_threads
|
70
|
-
end
|
71
|
-
|
72
|
-
case query
|
73
|
-
when :active then work_threads.select { |t| t[:rufus_scheduler_job] }
|
74
|
-
when :vacant then work_threads.reject { |t| t[:rufus_scheduler_job] }
|
75
|
-
else work_threads
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
# @overload
|
80
|
-
def on_error(job, err)
|
81
|
-
details = { exception: err.class, message: err.message, backtrace: err.backtrace }
|
82
|
-
details[:cause] = err.cause if err.cause
|
83
|
-
|
84
|
-
details[:now] = debug_format_time(TimeImpl.now)
|
85
|
-
details[:last_time] = (debug_format_time(job.last_time) rescue nil)
|
86
|
-
details[:next_time] = (debug_format_time(job.next_time) rescue nil)
|
87
|
-
details[:job] = job
|
88
|
-
|
89
|
-
details[:opts] = @opts
|
90
|
-
details[:started_at] = started_at
|
91
|
-
details[:thread] = thread.inspect
|
92
|
-
details[:jobs_size] = @jobs.size
|
93
|
-
details[:work_threads_size] = work_threads.size
|
94
|
-
details[:work_queue_size] = work_queue.size
|
95
|
-
|
96
|
-
logger.error("Scheduler intercepted an error:", details)
|
97
|
-
|
98
|
-
rescue => e
|
99
|
-
logger.error("Scheduler failed in #on_error #{e.inspect}")
|
100
|
-
end
|
101
|
-
|
102
|
-
def debug_format_time(time)
|
103
|
-
# EtOrbi::EoTime used by (newer) Rufus::Scheduler has to_debug_s https://git.io/JyiPj
|
104
|
-
time.respond_to?(:to_debug_s) ? time.to_debug_s : time.strftime("%Y-%m-%dT%H:%M:%S.%L")
|
105
|
-
end
|
106
|
-
private :debug_format_time
|
107
|
-
|
108
|
-
# @private helper used by JobDecorator
|
109
|
-
def work_thread_name_prefix
|
110
|
-
( @opts[:thread_name] || "#{@thread_key}_scheduler" ) + '_worker-'
|
111
|
-
end
|
112
|
-
|
113
|
-
protected
|
114
|
-
|
115
|
-
# @overload
|
116
|
-
def start
|
117
|
-
ret = super() # @thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
|
118
|
-
|
119
|
-
# at least set thread.name for easier thread dump analysis
|
120
|
-
if @thread.is_a?(Thread) && @thread.respond_to?(:name=)
|
121
|
-
@thread.name = @thread[:name] if @thread[:name]
|
122
|
-
end
|
123
|
-
|
124
|
-
ret
|
125
|
-
end
|
126
|
-
|
127
|
-
# @overload
|
128
|
-
def do_schedule(job_type, t, callable, opts, return_job_instance, block)
|
129
|
-
job_or_id = super
|
130
|
-
|
131
|
-
job_or_id.extend JobDecorator if return_job_instance
|
132
|
-
|
133
|
-
job_or_id
|
134
|
-
end
|
135
|
-
|
136
|
-
module JobDecorator
|
137
|
-
|
138
|
-
def start_work_thread
|
139
|
-
prev_thread_count = @scheduler.work_threads.size
|
140
|
-
|
141
|
-
ret = super() # does not return Thread instance in 3.0
|
142
|
-
|
143
|
-
work_threads = @scheduler.work_threads(:__all_no_cache__)
|
144
|
-
while prev_thread_count == work_threads.size # very unlikely
|
145
|
-
Thread.pass
|
146
|
-
work_threads = @scheduler.work_threads(:__all_no_cache__)
|
147
|
-
end
|
148
|
-
|
149
|
-
work_thread_name_prefix = @scheduler.work_thread_name_prefix
|
150
|
-
|
151
|
-
work_threads.sort! do |t1, t2|
|
152
|
-
if t1[:name].nil?
|
153
|
-
t2[:name].nil? ? 0 : +1 # nils at the end
|
154
|
-
elsif t2[:name].nil?
|
155
|
-
t1[:name].nil? ? 0 : -1
|
156
|
-
else
|
157
|
-
t1[:name] <=> t2[:name]
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
work_threads.each_with_index do |thread, i|
|
162
|
-
unless thread[:name]
|
163
|
-
thread[:name] = "#{work_thread_name_prefix}#{sprintf('%02i', i)}"
|
164
|
-
thread.name = thread[:name] if thread.respond_to?(:name=)
|
165
|
-
# e.g. "[oracle]<jdbc_scheduler_worker-00"
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
ret
|
170
|
-
end
|
171
|
-
|
172
|
-
end
|
173
|
-
|
174
|
-
end
|
175
|
-
end end end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require "logstash/devutils/rspec/spec_helper"
|
3
|
-
require "logstash/plugin_mixins/jdbc/scheduler"
|
4
|
-
|
5
|
-
describe LogStash::PluginMixins::Jdbc::Scheduler do
|
6
|
-
|
7
|
-
let(:thread_name) { '[test]<jdbc_scheduler' }
|
8
|
-
|
9
|
-
let(:opts) do
|
10
|
-
{ :max_work_threads => 2, :thread_name => thread_name }
|
11
|
-
end
|
12
|
-
|
13
|
-
subject(:scheduler) { LogStash::PluginMixins::Jdbc::Scheduler.new(opts) }
|
14
|
-
|
15
|
-
after { scheduler.stop(:wait) }
|
16
|
-
|
17
|
-
it "sets scheduler thread name" do
|
18
|
-
expect( scheduler.thread.name ).to include thread_name
|
19
|
-
end
|
20
|
-
|
21
|
-
context 'cron schedule' do
|
22
|
-
|
23
|
-
before do
|
24
|
-
scheduler.schedule_cron('* * * * * *') { sleep 1.25 } # every second
|
25
|
-
end
|
26
|
-
|
27
|
-
it "sets worker thread names" do
|
28
|
-
sleep 3.0
|
29
|
-
threads = scheduler.work_threads
|
30
|
-
threads.sort! { |t1, t2| (t1.name || '') <=> (t2.name || '') }
|
31
|
-
|
32
|
-
expect( threads.size ).to eql 2
|
33
|
-
expect( threads.first.name ).to eql "#{thread_name}_worker-00"
|
34
|
-
expect( threads.last.name ).to eql "#{thread_name}_worker-01"
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
context 'every 1s' do
|
40
|
-
|
41
|
-
before do
|
42
|
-
scheduler.schedule_in('1s') { raise 'TEST' } # every second
|
43
|
-
end
|
44
|
-
|
45
|
-
it "logs errors handled" do
|
46
|
-
expect( scheduler.logger ).to receive(:error).with /Scheduler intercepted an error/, hash_including(:message => 'TEST')
|
47
|
-
sleep 1.5
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
context 'work threads' do
|
53
|
-
|
54
|
-
let(:opts) { super().merge :max_work_threads => 3 }
|
55
|
-
|
56
|
-
let(:counter) { java.util.concurrent.atomic.AtomicLong.new(0) }
|
57
|
-
|
58
|
-
before do
|
59
|
-
scheduler.schedule_cron('* * * * * *') { counter.increment_and_get; sleep 3.25 } # every second
|
60
|
-
end
|
61
|
-
|
62
|
-
it "are working" do
|
63
|
-
sleep(0.05) while counter.get == 0
|
64
|
-
expect( scheduler.work_threads.size ).to eql 1
|
65
|
-
sleep(0.05) while counter.get == 1
|
66
|
-
expect( scheduler.work_threads.size ).to eql 2
|
67
|
-
sleep(0.05) while counter.get == 2
|
68
|
-
expect( scheduler.work_threads.size ).to eql 3
|
69
|
-
|
70
|
-
sleep 1.25
|
71
|
-
expect( scheduler.work_threads.size ).to eql 3
|
72
|
-
sleep 1.25
|
73
|
-
expect( scheduler.work_threads.size ).to eql 3
|
74
|
-
end
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|