fluent-plugin-sql 0.6.1 → 2.1.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 +5 -5
- data/.travis.yml +5 -10
- data/ChangeLog +20 -0
- data/README.md +20 -2
- data/VERSION +1 -1
- data/fluent-plugin-sql.gemspec +4 -5
- data/lib/fluent/plugin/in_sql.rb +51 -48
- data/lib/fluent/plugin/out_sql.rb +61 -61
- data/test/fixtures/schema.rb +7 -0
- data/test/plugin/test_in_sql.rb +16 -8
- data/test/plugin/test_in_sql_with_custom_time.rb +114 -0
- data/test/plugin/test_out_sql.rb +20 -13
- metadata +15 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '00987a628d7afc94546e68d5374331e82c65eca49aa8e3aad33b8f5ffb5c8f59'
|
4
|
+
data.tar.gz: e809d69583ab25571878d9bc4adb64a26841e14347e9deb973e1a659262bbf47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5cf74d0668a3e7b26220d2b56179c3668a99e2a51e8d99b7468d797bdc3c7e0fe81ca813ede95713fc3a2dc6484929505953c0be7239129343f138798ad7fe4
|
7
|
+
data.tar.gz: c45922cd85d83e3ed89629c55232cd0fe83106b4961f1d2dfaafd8564d8db900b4740f78e2f12b7596ee6ce025a43efaf4cb409ca9ed418434dcc3ed447a4456
|
data/.travis.yml
CHANGED
@@ -5,19 +5,17 @@ addons:
|
|
5
5
|
postgresql: "9.4"
|
6
6
|
|
7
7
|
rvm:
|
8
|
-
- 2.
|
9
|
-
- 2.
|
10
|
-
- 2.
|
11
|
-
- 2.
|
12
|
-
- 2.4.1
|
8
|
+
- 2.4.10
|
9
|
+
- 2.5.8
|
10
|
+
- 2.6.6
|
11
|
+
- 2.7.1
|
13
12
|
- ruby-head
|
14
13
|
|
15
14
|
gemfile:
|
16
15
|
- Gemfile
|
17
|
-
- Gemfile.v0.12
|
18
16
|
|
19
17
|
before_install:
|
20
|
-
- gem
|
18
|
+
- gem update bundler
|
21
19
|
|
22
20
|
before_script:
|
23
21
|
- psql -U postgres -c "CREATE ROLE fluentd WITH LOGIN ENCRYPTED PASSWORD 'fluentd';"
|
@@ -28,6 +26,3 @@ script: bundle exec rake test
|
|
28
26
|
matrix:
|
29
27
|
allow_failures:
|
30
28
|
- rvm: ruby-head
|
31
|
-
exclude:
|
32
|
-
- rvm: 2.0
|
33
|
-
gemfile: Gemfile
|
data/ChangeLog
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Release 2.1.0 - 2020/09/09
|
2
|
+
|
3
|
+
* in_sql: Use Fluent::EventTime instead of Integer to preserve millisecond precision
|
4
|
+
|
5
|
+
Release 2.0.0 - 2020/04/22
|
6
|
+
|
7
|
+
* out_sql: Remove v0.12 API code and use full v1 API. Change buffer format.
|
8
|
+
|
9
|
+
Release 1.1.1 - 2019/05/10
|
10
|
+
|
11
|
+
* out_sql: Support schema_search_path option of PostgreSQL
|
12
|
+
|
13
|
+
Release 1.1.0 - 2018/10/04
|
14
|
+
|
15
|
+
* Upgrade ActiveRecord to 5.1
|
16
|
+
|
17
|
+
Release 1.0.0 - 2018/04/06
|
18
|
+
|
19
|
+
* Support v1 API
|
20
|
+
|
data/README.md
CHANGED
@@ -5,7 +5,16 @@
|
|
5
5
|
This SQL plugin has two parts:
|
6
6
|
|
7
7
|
1. SQL **input** plugin reads records from RDBMSes periodically. An example use case would be getting "diffs" of a table (based on the "updated_at" field).
|
8
|
-
2. SQL **output** plugin that writes records into
|
8
|
+
2. SQL **output** plugin that writes records into RDBMSes. An example use case would be aggregating server/app/sensor logs into RDBMS systems.
|
9
|
+
|
10
|
+
## Requirements
|
11
|
+
|
12
|
+
| fluent-plugin-sql | fluentd | ruby |
|
13
|
+
|-------------------|------------|--------|
|
14
|
+
| >= 1.0.0 | >= v0.14.4 | >= 2.1 |
|
15
|
+
| < 1.0.0 | < v0.14.0 | >= 1.9 |
|
16
|
+
|
17
|
+
NOTE: fluent-plugin-sql v2's buffer format is different from v1. If you update the plugin to v2, don't reuse v1's buffer.
|
9
18
|
|
10
19
|
## Installation
|
11
20
|
|
@@ -16,6 +25,13 @@ You should install actual RDBMS driver gem together. `pg` gem for postgresql ada
|
|
16
25
|
|
17
26
|
We recommend that mysql2 gem is higher than `0.3.12` and pg gem is higher than `0.16.0`.
|
18
27
|
|
28
|
+
If you use ruby 2.1, use pg gem 0.21.0 (< 1.0.0) because ActiveRecord 5.1.4 or earlier doesn't support Ruby 2.1.
|
29
|
+
|
30
|
+
### Resolve tzinfo version conflict
|
31
|
+
|
32
|
+
If you want to use fluent-plugin-sql with recent fluentd/td-agent, you need to downgrade tzinfo to v1.x manually.
|
33
|
+
See also this comment: https://github.com/fluent/fluent-plugin-sql/issues/87#issuecomment-614552292
|
34
|
+
|
19
35
|
## Input: How It Works
|
20
36
|
|
21
37
|
This plugin runs following SQL periodically:
|
@@ -139,10 +155,12 @@ This plugin takes advantage of ActiveRecord underneath. For `host`, `port`, `dat
|
|
139
155
|
* **username** RDBMS login user name
|
140
156
|
* **password** RDBMS login password
|
141
157
|
* **socket** RDBMS socket path
|
158
|
+
* **pool** A connection pool synchronizes thread access to a limited number of database connections
|
159
|
+
* **timeout** RDBMS connection timeout
|
142
160
|
* **remove_tag_prefix** remove the given prefix from the events. See "tag_prefix" in "Input: Configuration". (optional)
|
143
161
|
|
144
162
|
\<table\> sections:
|
145
163
|
|
146
164
|
* **table** RDBM table name
|
147
165
|
* **column_mapping**: [Required] Record to table schema mapping. The format is consists of `from:to` or `key` values are separated by `,`. For example, if set 'item_id:id,item_text:data,updated_at' to **column_mapping**, `item_id` field of record is stored into `id` column and `updated_at` field of record is stored into `updated_at` column.
|
148
|
-
*
|
166
|
+
* **\<table pattern\>**: the pattern to which the incoming event's tag (after it goes through `remove_tag_prefix`, if given). The patterns should follow the same syntax as [that of \<match\>](https://docs.fluentd.org/configuration/config-file#how-match-patterns-work). **Exactly one \<table\> element must NOT have this parameter so that it becomes the default table to store data**.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.1.0
|
data/fluent-plugin-sql.gemspec
CHANGED
@@ -4,12 +4,11 @@ $:.push File.expand_path('../lib', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.name = "fluent-plugin-sql"
|
6
6
|
gem.description = "SQL input/output plugin for Fluentd event collector"
|
7
|
-
gem.homepage = "https://github.com/
|
7
|
+
gem.homepage = "https://github.com/fluent/fluent-plugin-sql"
|
8
8
|
gem.summary = gem.description
|
9
9
|
gem.version = File.read("VERSION").strip
|
10
10
|
gem.authors = ["Sadayuki Furuhashi"]
|
11
11
|
gem.email = "frsyuki@gmail.com"
|
12
|
-
gem.has_rdoc = false
|
13
12
|
#gem.platform = Gem::Platform::RUBY
|
14
13
|
gem.files = `git ls-files`.split("\n")
|
15
14
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -18,11 +17,11 @@ Gem::Specification.new do |gem|
|
|
18
17
|
gem.license = "Apache-2.0"
|
19
18
|
|
20
19
|
gem.add_dependency "fluentd", [">= 0.12.17", "< 2"]
|
21
|
-
gem.add_dependency 'activerecord', "~>
|
20
|
+
gem.add_dependency 'activerecord', "~> 5.1"
|
22
21
|
gem.add_dependency 'activerecord-import', "~> 0.7"
|
23
22
|
gem.add_development_dependency "rake", ">= 0.9.2"
|
24
|
-
gem.add_development_dependency "test-unit", "
|
23
|
+
gem.add_development_dependency "test-unit", "> 3.1.0"
|
25
24
|
gem.add_development_dependency "test-unit-rr"
|
26
25
|
gem.add_development_dependency "test-unit-notify"
|
27
|
-
gem.add_development_dependency "pg"
|
26
|
+
gem.add_development_dependency "pg", '~> 1.0'
|
28
27
|
end
|
data/lib/fluent/plugin/in_sql.rb
CHANGED
@@ -16,67 +16,60 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
|
19
|
-
require "fluent/input"
|
19
|
+
require "fluent/plugin/input"
|
20
20
|
|
21
|
-
module Fluent
|
21
|
+
module Fluent::Plugin
|
22
22
|
|
23
23
|
require 'active_record'
|
24
24
|
|
25
25
|
class SQLInput < Input
|
26
|
-
Plugin.register_input('sql', self)
|
27
|
-
|
28
|
-
# For fluentd v0.12.16 or earlier
|
29
|
-
class << self
|
30
|
-
unless method_defined?(:desc)
|
31
|
-
def desc(description)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
26
|
+
Fluent::Plugin.register_input('sql', self)
|
35
27
|
|
36
28
|
desc 'RDBMS host'
|
37
29
|
config_param :host, :string
|
38
30
|
desc 'RDBMS port'
|
39
|
-
config_param :port, :integer, :
|
31
|
+
config_param :port, :integer, default: nil
|
40
32
|
desc 'RDBMS driver name.'
|
41
33
|
config_param :adapter, :string
|
42
34
|
desc 'RDBMS database name'
|
43
35
|
config_param :database, :string
|
44
36
|
desc 'RDBMS login user name'
|
45
|
-
config_param :username, :string, :
|
37
|
+
config_param :username, :string, default: nil
|
46
38
|
desc 'RDBMS login password'
|
47
|
-
config_param :password, :string, :
|
39
|
+
config_param :password, :string, default: nil, secret: true
|
48
40
|
desc 'RDBMS socket path'
|
49
|
-
config_param :socket, :string, :
|
41
|
+
config_param :socket, :string, default: nil
|
42
|
+
desc 'PostgreSQL schema search path'
|
43
|
+
config_param :schema_search_path, :string, default: nil
|
50
44
|
|
51
45
|
desc 'path to a file to store last rows'
|
52
|
-
config_param :state_file, :string, :
|
46
|
+
config_param :state_file, :string, default: nil
|
53
47
|
desc 'prefix of tags of events. actual tag will be this_tag_prefix.tables_tag (optional)'
|
54
|
-
config_param :tag_prefix, :string, :
|
48
|
+
config_param :tag_prefix, :string, default: nil
|
55
49
|
desc 'interval to run SQLs (optional)'
|
56
|
-
config_param :select_interval, :time, :
|
50
|
+
config_param :select_interval, :time, default: 60
|
57
51
|
desc 'limit of number of rows for each SQL(optional)'
|
58
|
-
config_param :select_limit, :time, :
|
59
|
-
|
60
|
-
unless method_defined?(:log)
|
61
|
-
define_method(:log) { $log }
|
62
|
-
end
|
52
|
+
config_param :select_limit, :time, default: 500
|
63
53
|
|
64
54
|
class TableElement
|
65
|
-
include Configurable
|
55
|
+
include Fluent::Configurable
|
66
56
|
|
67
57
|
config_param :table, :string
|
68
|
-
config_param :tag, :string, :
|
69
|
-
config_param :update_column, :string, :
|
70
|
-
config_param :time_column, :string, :
|
71
|
-
config_param :primary_key, :string, :
|
58
|
+
config_param :tag, :string, default: nil
|
59
|
+
config_param :update_column, :string, default: nil
|
60
|
+
config_param :time_column, :string, default: nil
|
61
|
+
config_param :primary_key, :string, default: nil
|
62
|
+
|
63
|
+
attr_reader :log
|
72
64
|
|
73
65
|
def configure(conf)
|
74
66
|
super
|
75
67
|
end
|
76
68
|
|
77
|
-
def init(tag_prefix, base_model, router)
|
69
|
+
def init(tag_prefix, base_model, router, log)
|
78
70
|
@router = router
|
79
71
|
@tag = "#{tag_prefix}.#{@tag}" if tag_prefix
|
72
|
+
@log = log
|
80
73
|
|
81
74
|
# creates a model for this table
|
82
75
|
table_name = @table
|
@@ -118,6 +111,17 @@ module Fluent
|
|
118
111
|
end
|
119
112
|
end
|
120
113
|
|
114
|
+
# Make sure we always have a Fluent::EventTime object regardless of what comes in
|
115
|
+
def normalized_time(tv, now)
|
116
|
+
return Fluent::EventTime.from_time(tv) if tv.is_a?(Time)
|
117
|
+
begin
|
118
|
+
Fluent::EventTime.parse(tv.to_s)
|
119
|
+
rescue
|
120
|
+
log.warn "Message contains invalid timestamp, using current time instead (#{now.inspect})"
|
121
|
+
now
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
121
125
|
# emits next records and returns the last record of emitted records
|
122
126
|
def emit_next_records(last_record, limit)
|
123
127
|
relation = @model
|
@@ -127,21 +131,19 @@ module Fluent
|
|
127
131
|
relation = relation.order("#{@update_column} ASC")
|
128
132
|
relation = relation.limit(limit) if limit > 0
|
129
133
|
|
130
|
-
now = Engine.now
|
134
|
+
now = Fluent::Engine.now
|
131
135
|
|
132
|
-
me = MultiEventStream.new
|
136
|
+
me = Fluent::MultiEventStream.new
|
133
137
|
relation.each do |obj|
|
134
138
|
record = obj.serializable_hash rescue nil
|
135
139
|
if record
|
136
|
-
|
137
|
-
if tv.
|
138
|
-
|
140
|
+
time =
|
141
|
+
if @time_column && (tv = obj.read_attribute(@time_column))
|
142
|
+
normalized_time(tv, now)
|
139
143
|
else
|
140
|
-
|
144
|
+
now
|
141
145
|
end
|
142
|
-
|
143
|
-
time = now
|
144
|
-
end
|
146
|
+
|
145
147
|
me.add(time, record)
|
146
148
|
last_record = record
|
147
149
|
end
|
@@ -181,13 +183,14 @@ module Fluent
|
|
181
183
|
@state_store = @state_file.nil? ? MemoryStateStore.new : StateStore.new(@state_file)
|
182
184
|
|
183
185
|
config = {
|
184
|
-
:
|
185
|
-
:
|
186
|
-
:
|
187
|
-
:
|
188
|
-
:
|
189
|
-
:
|
190
|
-
:
|
186
|
+
adapter: @adapter,
|
187
|
+
host: @host,
|
188
|
+
port: @port,
|
189
|
+
database: @database,
|
190
|
+
username: @username,
|
191
|
+
password: @password,
|
192
|
+
socket: @socket,
|
193
|
+
schema_search_path: @schema_search_path,
|
191
194
|
}
|
192
195
|
|
193
196
|
# creates subclass of ActiveRecord::Base so that it can have different
|
@@ -226,11 +229,11 @@ module Fluent
|
|
226
229
|
# ignore tables if TableElement#init failed
|
227
230
|
@tables.reject! do |te|
|
228
231
|
begin
|
229
|
-
te.init(@tag_prefix, @base_model, router)
|
232
|
+
te.init(@tag_prefix, @base_model, router, log)
|
230
233
|
log.info "Selecting '#{te.table}' table"
|
231
234
|
false
|
232
235
|
rescue => e
|
233
|
-
log.warn "Can't handle '#{te.table}' table. Ignoring.",
|
236
|
+
log.warn "Can't handle '#{te.table}' table. Ignoring.", error: e
|
234
237
|
log.warn_backtrace e.backtrace
|
235
238
|
true
|
236
239
|
end
|
@@ -264,7 +267,7 @@ module Fluent
|
|
264
267
|
@state_store.last_records[t.table] = t.emit_next_records(last_record, @select_limit)
|
265
268
|
@state_store.update!
|
266
269
|
rescue => e
|
267
|
-
log.error "unexpected error",
|
270
|
+
log.error "unexpected error", error: e
|
268
271
|
log.error_backtrace e.backtrace
|
269
272
|
end
|
270
273
|
end
|
@@ -1,59 +1,59 @@
|
|
1
|
-
require "fluent/output"
|
1
|
+
require "fluent/plugin/output"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Plugin.register_output('sql', self)
|
3
|
+
require 'active_record'
|
4
|
+
require 'activerecord-import'
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
module Fluent::Plugin
|
7
|
+
class SQLOutput < Output
|
8
|
+
Fluent::Plugin.register_output('sql', self)
|
9
9
|
|
10
|
-
|
11
|
-
class << self
|
12
|
-
unless method_defined?(:desc)
|
13
|
-
def desc(description)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
10
|
+
helpers :inject, :compat_parameters, :event_emitter
|
17
11
|
|
18
12
|
desc 'RDBMS host'
|
19
13
|
config_param :host, :string
|
20
14
|
desc 'RDBMS port'
|
21
|
-
config_param :port, :integer, :
|
15
|
+
config_param :port, :integer, default: nil
|
22
16
|
desc 'RDBMS driver name.'
|
23
17
|
config_param :adapter, :string
|
24
18
|
desc 'RDBMS login user name'
|
25
|
-
config_param :username, :string, :
|
19
|
+
config_param :username, :string, default: nil
|
26
20
|
desc 'RDBMS login password'
|
27
|
-
config_param :password, :string, :
|
21
|
+
config_param :password, :string, default: nil, secret: true
|
28
22
|
desc 'RDBMS database name'
|
29
23
|
config_param :database, :string
|
30
24
|
desc 'RDBMS socket path'
|
31
|
-
config_param :socket, :string, :
|
25
|
+
config_param :socket, :string, default: nil
|
26
|
+
desc 'PostgreSQL schema search path'
|
27
|
+
config_param :schema_search_path, :string, default: nil
|
32
28
|
desc 'remove the given prefix from the events'
|
33
|
-
config_param :remove_tag_prefix, :string, :
|
29
|
+
config_param :remove_tag_prefix, :string, default: nil
|
34
30
|
desc 'enable fallback'
|
35
|
-
config_param :enable_fallback, :bool, :
|
31
|
+
config_param :enable_fallback, :bool, default: true
|
32
|
+
desc "size of ActiveRecord's connection pool"
|
33
|
+
config_param :pool, :integer, default: 5
|
34
|
+
desc "specifies the timeout to establish a new connection to the database before failing"
|
35
|
+
config_param :timeout, :integer, default: 5000
|
36
|
+
|
37
|
+
config_section :buffer do
|
38
|
+
config_set_default :chunk_keys, ["tag"]
|
39
|
+
end
|
36
40
|
|
37
41
|
attr_accessor :tables
|
38
42
|
|
39
|
-
unless method_defined?(:log)
|
40
|
-
define_method(:log) { $log }
|
41
|
-
end
|
42
|
-
|
43
43
|
# TODO: Merge SQLInput's TableElement
|
44
44
|
class TableElement
|
45
|
-
include Configurable
|
45
|
+
include Fluent::Configurable
|
46
46
|
|
47
47
|
config_param :table, :string
|
48
48
|
config_param :column_mapping, :string
|
49
|
-
config_param :num_retries, :integer, :
|
49
|
+
config_param :num_retries, :integer, default: 5
|
50
50
|
|
51
51
|
attr_reader :model
|
52
52
|
attr_reader :pattern
|
53
53
|
|
54
54
|
def initialize(pattern, log, enable_fallback)
|
55
55
|
super()
|
56
|
-
@pattern = MatchPattern.create(pattern)
|
56
|
+
@pattern = Fluent::MatchPattern.create(pattern)
|
57
57
|
@log = log
|
58
58
|
@enable_fallback = enable_fallback
|
59
59
|
end
|
@@ -88,14 +88,15 @@ module Fluent
|
|
88
88
|
# @model.column_names
|
89
89
|
end
|
90
90
|
|
91
|
-
def import(chunk)
|
91
|
+
def import(chunk, output)
|
92
|
+
tag = chunk.metadata.tag
|
92
93
|
records = []
|
93
|
-
chunk.msgpack_each { |
|
94
|
+
chunk.msgpack_each { |time, data|
|
94
95
|
begin
|
95
|
-
|
96
|
+
data = output.inject_values_to_record(tag, time, data)
|
96
97
|
records << @model.new(@format_proc.call(data))
|
97
98
|
rescue => e
|
98
|
-
args = {
|
99
|
+
args = {error: e, table: @table, record: Yajl.dump(data)}
|
99
100
|
@log.warn "Failed to create the model. Ignore a record:", args
|
100
101
|
end
|
101
102
|
}
|
@@ -104,10 +105,10 @@ module Fluent
|
|
104
105
|
rescue ActiveRecord::StatementInvalid, ActiveRecord::Import::MissingColumnError => e
|
105
106
|
if @enable_fallback
|
106
107
|
# ignore other exceptions to use Fluentd retry mechanizm
|
107
|
-
@log.warn "Got deterministic error. Fallback to one-by-one import",
|
108
|
+
@log.warn "Got deterministic error. Fallback to one-by-one import", error: e
|
108
109
|
one_by_one_import(records)
|
109
110
|
else
|
110
|
-
|
111
|
+
@log.warn "Got deterministic error. Fallback is disabled", error: e
|
111
112
|
raise e
|
112
113
|
end
|
113
114
|
end
|
@@ -119,15 +120,15 @@ module Fluent
|
|
119
120
|
begin
|
120
121
|
@model.import([record])
|
121
122
|
rescue ActiveRecord::StatementInvalid, ActiveRecord::Import::MissingColumnError => e
|
122
|
-
@log.error "Got deterministic error again. Dump a record",
|
123
|
+
@log.error "Got deterministic error again. Dump a record", error: e, record: record
|
123
124
|
rescue => e
|
124
125
|
retries += 1
|
125
126
|
if retries > @num_retries
|
126
|
-
@log.error "Can't recover undeterministic error. Dump a record",
|
127
|
+
@log.error "Can't recover undeterministic error. Dump a record", error: e, record: record
|
127
128
|
next
|
128
129
|
end
|
129
130
|
|
130
|
-
@log.warn "Failed to import a record: retry number = #{retries}",
|
131
|
+
@log.warn "Failed to import a record: retry number = #{retries}", error: e
|
131
132
|
sleep 0.5
|
132
133
|
retry
|
133
134
|
end
|
@@ -149,11 +150,11 @@ module Fluent
|
|
149
150
|
|
150
151
|
def initialize
|
151
152
|
super
|
152
|
-
require 'active_record'
|
153
|
-
require 'activerecord-import'
|
154
153
|
end
|
155
154
|
|
156
155
|
def configure(conf)
|
156
|
+
compat_parameters_convert(conf, :inject, :buffer)
|
157
|
+
|
157
158
|
super
|
158
159
|
|
159
160
|
if remove_tag_prefix = conf['remove_tag_prefix']
|
@@ -174,10 +175,13 @@ module Fluent
|
|
174
175
|
@tables << te
|
175
176
|
end
|
176
177
|
}
|
177
|
-
|
178
|
+
|
179
|
+
if @pool < @buffer_config.flush_thread_count
|
180
|
+
log.warn "connection pool size is smaller than buffer's flush_thread_count. Recommend to increase pool value", :pool => @pool, :flush_thread_count => @buffer_config.flush_thread_count
|
181
|
+
end
|
178
182
|
|
179
183
|
if @default_table.nil?
|
180
|
-
raise ConfigError, "There is no default table. <table> is required in sql output"
|
184
|
+
raise Fluent::ConfigError, "There is no default table. <table> is required in sql output"
|
181
185
|
end
|
182
186
|
end
|
183
187
|
|
@@ -185,13 +189,16 @@ module Fluent
|
|
185
189
|
super
|
186
190
|
|
187
191
|
config = {
|
188
|
-
:
|
189
|
-
:
|
190
|
-
:
|
191
|
-
:
|
192
|
-
:
|
193
|
-
:
|
194
|
-
:
|
192
|
+
adapter: @adapter,
|
193
|
+
host: @host,
|
194
|
+
port: @port,
|
195
|
+
database: @database,
|
196
|
+
username: @username,
|
197
|
+
password: @password,
|
198
|
+
socket: @socket,
|
199
|
+
schema_search_path: @schema_search_path,
|
200
|
+
pool: @pool,
|
201
|
+
timeout: @timeout,
|
195
202
|
}
|
196
203
|
|
197
204
|
@base_model = Class.new(ActiveRecord::Base) do
|
@@ -212,27 +219,20 @@ module Fluent
|
|
212
219
|
super
|
213
220
|
end
|
214
221
|
|
215
|
-
def
|
216
|
-
|
217
|
-
super(tag, es, chain)
|
218
|
-
else
|
219
|
-
super(tag, es, chain, format_tag(tag))
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
def format(tag, time, record)
|
224
|
-
[tag, time, record].to_msgpack
|
222
|
+
def formatted_to_msgpack_binary
|
223
|
+
true
|
225
224
|
end
|
226
225
|
|
227
226
|
def write(chunk)
|
228
227
|
ActiveRecord::Base.connection_pool.with_connection do
|
229
228
|
|
230
229
|
@tables.each { |table|
|
231
|
-
|
232
|
-
|
230
|
+
tag = format_tag(chunk.metadata.tag)
|
231
|
+
if table.pattern.match(tag)
|
232
|
+
return table.import(chunk, self)
|
233
233
|
end
|
234
234
|
}
|
235
|
-
@default_table.import(chunk)
|
235
|
+
@default_table.import(chunk, self)
|
236
236
|
end
|
237
237
|
end
|
238
238
|
|
@@ -244,14 +244,14 @@ module Fluent
|
|
244
244
|
log.info "Selecting '#{te.table}' table"
|
245
245
|
false
|
246
246
|
rescue => e
|
247
|
-
log.warn "Can't handle '#{te.table}' table. Ignoring.",
|
247
|
+
log.warn "Can't handle '#{te.table}' table. Ignoring.", error: e
|
248
248
|
log.warn_backtrace e.backtrace
|
249
249
|
true
|
250
250
|
end
|
251
251
|
end
|
252
252
|
|
253
253
|
def format_tag(tag)
|
254
|
-
if @remove_tag_prefix
|
254
|
+
if tag && @remove_tag_prefix
|
255
255
|
tag.gsub(@remove_tag_prefix, '')
|
256
256
|
else
|
257
257
|
tag
|
data/test/fixtures/schema.rb
CHANGED
@@ -20,5 +20,12 @@ ActiveRecord::Schema.define(version: 20160225030107) do
|
|
20
20
|
t.datetime "created_at", null: false
|
21
21
|
t.datetime "updated_at", null: false
|
22
22
|
end
|
23
|
+
|
24
|
+
create_table "messages_custom_time", force: :cascade do |t|
|
25
|
+
t.string "message"
|
26
|
+
t.datetime "created_at", null: false
|
27
|
+
t.datetime "updated_at", null: false
|
28
|
+
t.string "custom_time"
|
29
|
+
end
|
23
30
|
end
|
24
31
|
|
data/test/plugin/test_in_sql.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "helper"
|
2
|
+
require "fluent/test/driver/input"
|
2
3
|
|
3
4
|
class SqlInputTest < Test::Unit::TestCase
|
4
5
|
def setup
|
@@ -17,6 +18,8 @@ class SqlInputTest < Test::Unit::TestCase
|
|
17
18
|
username fluentd
|
18
19
|
password fluentd
|
19
20
|
|
21
|
+
schema_search_path public
|
22
|
+
|
20
23
|
tag_prefix db
|
21
24
|
|
22
25
|
<table>
|
@@ -28,7 +31,7 @@ class SqlInputTest < Test::Unit::TestCase
|
|
28
31
|
]
|
29
32
|
|
30
33
|
def create_driver(conf = CONFIG)
|
31
|
-
Fluent::Test::
|
34
|
+
Fluent::Test::Driver::Input.new(Fluent::Plugin::SQLInput).configure(conf)
|
32
35
|
end
|
33
36
|
|
34
37
|
def test_configure
|
@@ -40,6 +43,7 @@ class SqlInputTest < Test::Unit::TestCase
|
|
40
43
|
database: "fluentd_test",
|
41
44
|
username: "fluentd",
|
42
45
|
password: "fluentd",
|
46
|
+
schema_search_path: "public",
|
43
47
|
tag_prefix: "db"
|
44
48
|
}
|
45
49
|
actual = {
|
@@ -49,6 +53,7 @@ class SqlInputTest < Test::Unit::TestCase
|
|
49
53
|
database: d.instance.database,
|
50
54
|
username: d.instance.username,
|
51
55
|
password: d.instance.password,
|
56
|
+
schema_search_path: d.instance.schema_search_path,
|
52
57
|
tag_prefix: d.instance.tag_prefix
|
53
58
|
}
|
54
59
|
assert_equal(expected, actual)
|
@@ -65,18 +70,21 @@ class SqlInputTest < Test::Unit::TestCase
|
|
65
70
|
Message.create!(message: "message 2")
|
66
71
|
Message.create!(message: "message 3")
|
67
72
|
|
73
|
+
d.end_if do
|
74
|
+
d.record_count >= 3
|
75
|
+
end
|
68
76
|
d.run
|
69
77
|
|
70
|
-
assert_equal("db.logs", d.
|
78
|
+
assert_equal("db.logs", d.events[0][0])
|
71
79
|
expected = [
|
72
|
-
[d.
|
73
|
-
[d.
|
74
|
-
[d.
|
80
|
+
[d.events[0][1], "message 1"],
|
81
|
+
[d.events[1][1], "message 2"],
|
82
|
+
[d.events[2][1], "message 3"],
|
75
83
|
]
|
76
84
|
actual = [
|
77
|
-
[
|
78
|
-
[
|
79
|
-
[
|
85
|
+
[Fluent::EventTime.parse(d.events[0][2]["updated_at"]), d.events[0][2]["message"]],
|
86
|
+
[Fluent::EventTime.parse(d.events[1][2]["updated_at"]), d.events[1][2]["message"]],
|
87
|
+
[Fluent::EventTime.parse(d.events[2][2]["updated_at"]), d.events[2][2]["message"]],
|
80
88
|
]
|
81
89
|
assert_equal(expected, actual)
|
82
90
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "fluent/test/driver/input"
|
3
|
+
|
4
|
+
class SqlInputCustomTimeTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Fluent::Test.setup
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
end
|
11
|
+
|
12
|
+
CONFIG = %[
|
13
|
+
adapter postgresql
|
14
|
+
host localhost
|
15
|
+
port 5432
|
16
|
+
database fluentd_test
|
17
|
+
|
18
|
+
username fluentd
|
19
|
+
password fluentd
|
20
|
+
|
21
|
+
schema_search_path public
|
22
|
+
|
23
|
+
tag_prefix db
|
24
|
+
|
25
|
+
<table>
|
26
|
+
table messages_custom_time
|
27
|
+
tag logs
|
28
|
+
update_column updated_at
|
29
|
+
time_column custom_time
|
30
|
+
</table>
|
31
|
+
]
|
32
|
+
|
33
|
+
def create_driver(conf = CONFIG)
|
34
|
+
Fluent::Test::Driver::Input.new(Fluent::Plugin::SQLInput).configure(conf)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_configure
|
38
|
+
d = create_driver
|
39
|
+
expected = {
|
40
|
+
host: "localhost",
|
41
|
+
port: 5432,
|
42
|
+
adapter: "postgresql",
|
43
|
+
database: "fluentd_test",
|
44
|
+
username: "fluentd",
|
45
|
+
password: "fluentd",
|
46
|
+
schema_search_path: "public",
|
47
|
+
tag_prefix: "db"
|
48
|
+
}
|
49
|
+
actual = {
|
50
|
+
host: d.instance.host,
|
51
|
+
port: d.instance.port,
|
52
|
+
adapter: d.instance.adapter,
|
53
|
+
database: d.instance.database,
|
54
|
+
username: d.instance.username,
|
55
|
+
password: d.instance.password,
|
56
|
+
schema_search_path: d.instance.schema_search_path,
|
57
|
+
tag_prefix: d.instance.tag_prefix
|
58
|
+
}
|
59
|
+
assert_equal(expected, actual)
|
60
|
+
tables = d.instance.instance_variable_get(:@tables)
|
61
|
+
assert_equal(1, tables.size)
|
62
|
+
messages_custom_time = tables.first
|
63
|
+
assert_equal("messages_custom_time", messages_custom_time.table)
|
64
|
+
assert_equal("logs", messages_custom_time.tag)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_message
|
68
|
+
d = create_driver(CONFIG + "select_interval 1")
|
69
|
+
|
70
|
+
start_time = Fluent::EventTime.now
|
71
|
+
|
72
|
+
# Create one message with a valid timestamp containing milliseconds and a time zone
|
73
|
+
Message.create!(message: "message 1", custom_time: '2020-08-27 15:00:16.100758000 -0400')
|
74
|
+
|
75
|
+
# Create one message without a timestamp so that we can test auto-creation
|
76
|
+
Message.create!(message: "message 2 (no timestamp)", custom_time: nil)
|
77
|
+
|
78
|
+
# Create one message with an unparseable timestamp so that we can check that a valid
|
79
|
+
# one is auto-generated.
|
80
|
+
Message.create!(message: "message 3 (bad timestamp)", custom_time: 'foo')
|
81
|
+
|
82
|
+
d.end_if do
|
83
|
+
d.record_count >= 3
|
84
|
+
end
|
85
|
+
d.run(timeout: 5)
|
86
|
+
|
87
|
+
assert_equal("db.logs", d.events[0][0])
|
88
|
+
expected = [
|
89
|
+
[d.events[0][1], "message 1"],
|
90
|
+
[d.events[1][1], "message 2 (no timestamp)"],
|
91
|
+
[d.events[2][1], "message 3 (bad timestamp)"],
|
92
|
+
]
|
93
|
+
|
94
|
+
actual = [
|
95
|
+
[Fluent::EventTime.parse(d.events[0][2]["custom_time"]), d.events[0][2]["message"]],
|
96
|
+
d.events[1][2]["message"],
|
97
|
+
d.events[2][2]["message"],
|
98
|
+
]
|
99
|
+
|
100
|
+
assert_equal(expected[0], actual[0])
|
101
|
+
|
102
|
+
# Messages 2 and 3 should have the same messages but (usually) a slightly later
|
103
|
+
# timestamps because they are generated by the input plugin instead of the test
|
104
|
+
# code
|
105
|
+
[1,2].each do |i|
|
106
|
+
assert_equal(expected[i][1], actual[i])
|
107
|
+
assert_operator(expected[i][0], :>=, start_time)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Message < ActiveRecord::Base
|
112
|
+
self.table_name = "messages_custom_time"
|
113
|
+
end
|
114
|
+
end
|
data/test/plugin/test_out_sql.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "helper"
|
2
|
+
require "fluent/test/driver/output"
|
2
3
|
|
3
4
|
class SqlOutputTest < Test::Unit::TestCase
|
4
5
|
def setup
|
@@ -17,6 +18,8 @@ class SqlOutputTest < Test::Unit::TestCase
|
|
17
18
|
username fluentd
|
18
19
|
password fluentd
|
19
20
|
|
21
|
+
schema_search_path public
|
22
|
+
|
20
23
|
remove_tag_prefix db
|
21
24
|
|
22
25
|
<table>
|
@@ -26,7 +29,7 @@ class SqlOutputTest < Test::Unit::TestCase
|
|
26
29
|
]
|
27
30
|
|
28
31
|
def create_driver(conf = CONFIG)
|
29
|
-
Fluent::Test::
|
32
|
+
Fluent::Test::Driver::Output.new(Fluent::Plugin::SQLOutput).configure(conf)
|
30
33
|
end
|
31
34
|
|
32
35
|
def test_configure
|
@@ -38,8 +41,10 @@ class SqlOutputTest < Test::Unit::TestCase
|
|
38
41
|
database: "fluentd_test",
|
39
42
|
username: "fluentd",
|
40
43
|
password: "fluentd",
|
44
|
+
schema_search_path: 'public',
|
41
45
|
remove_tag_suffix: /^db/,
|
42
|
-
enable_fallback: true
|
46
|
+
enable_fallback: true,
|
47
|
+
pool: 5
|
43
48
|
}
|
44
49
|
actual = {
|
45
50
|
host: d.instance.host,
|
@@ -48,8 +53,10 @@ class SqlOutputTest < Test::Unit::TestCase
|
|
48
53
|
database: d.instance.database,
|
49
54
|
username: d.instance.username,
|
50
55
|
password: d.instance.password,
|
56
|
+
schema_search_path: d.instance.schema_search_path,
|
51
57
|
remove_tag_suffix: d.instance.remove_tag_prefix,
|
52
|
-
enable_fallback: d.instance.enable_fallback
|
58
|
+
enable_fallback: d.instance.enable_fallback,
|
59
|
+
pool: d.instance.pool
|
53
60
|
}
|
54
61
|
assert_equal(expected, actual)
|
55
62
|
assert_empty(d.instance.tables)
|
@@ -61,10 +68,10 @@ class SqlOutputTest < Test::Unit::TestCase
|
|
61
68
|
d = create_driver
|
62
69
|
time = Time.parse("2011-01-02 13:14:15 UTC").to_i
|
63
70
|
|
64
|
-
d.
|
65
|
-
|
66
|
-
|
67
|
-
|
71
|
+
d.run(default_tag: 'test') do
|
72
|
+
d.feed(time, {"message" => "message1"})
|
73
|
+
d.feed(time, {"message" => "message2"})
|
74
|
+
end
|
68
75
|
|
69
76
|
default_table = d.instance.instance_variable_get(:@default_table)
|
70
77
|
model = default_table.instance_variable_get(:@model)
|
@@ -78,10 +85,10 @@ class SqlOutputTest < Test::Unit::TestCase
|
|
78
85
|
d = create_driver
|
79
86
|
time = Time.parse("2011-01-02 13:14:15 UTC").to_i
|
80
87
|
|
81
|
-
d.
|
82
|
-
|
88
|
+
d.run(default_tag: 'test') do
|
89
|
+
d.feed(time, {"message" => "message1"})
|
90
|
+
d.feed(time, {"message" => "message2"})
|
83
91
|
|
84
|
-
d.run do
|
85
92
|
default_table = d.instance.instance_variable_get(:@default_table)
|
86
93
|
model = default_table.instance_variable_get(:@model)
|
87
94
|
mock(model).import(anything).at_least(1) do
|
@@ -95,10 +102,10 @@ class SqlOutputTest < Test::Unit::TestCase
|
|
95
102
|
d = create_driver
|
96
103
|
time = Time.parse("2011-01-02 13:14:15 UTC").to_i
|
97
104
|
|
98
|
-
d.
|
99
|
-
|
105
|
+
d.run(default_tag: 'test') do
|
106
|
+
d.feed(time, {"message" => "message1"})
|
107
|
+
d.feed(time, {"message" => "message2"})
|
100
108
|
|
101
|
-
d.run do
|
102
109
|
default_table = d.instance.instance_variable_get(:@default_table)
|
103
110
|
model = default_table.instance_variable_get(:@model)
|
104
111
|
mock(model).import([anything, anything]).once do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|
@@ -36,14 +36,14 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
39
|
+
version: '5.1'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
46
|
+
version: '5.1'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: activerecord-import
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -76,14 +76,14 @@ dependencies:
|
|
76
76
|
name: test-unit
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
78
78
|
requirements:
|
79
|
-
- - "
|
79
|
+
- - ">"
|
80
80
|
- !ruby/object:Gem::Version
|
81
81
|
version: 3.1.0
|
82
82
|
type: :development
|
83
83
|
prerelease: false
|
84
84
|
version_requirements: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
|
-
- - "
|
86
|
+
- - ">"
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: 3.1.0
|
89
89
|
- !ruby/object:Gem::Dependency
|
@@ -118,16 +118,16 @@ dependencies:
|
|
118
118
|
name: pg
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
120
120
|
requirements:
|
121
|
-
- - "
|
121
|
+
- - "~>"
|
122
122
|
- !ruby/object:Gem::Version
|
123
|
-
version: '0'
|
123
|
+
version: '1.0'
|
124
124
|
type: :development
|
125
125
|
prerelease: false
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
127
127
|
requirements:
|
128
|
-
- - "
|
128
|
+
- - "~>"
|
129
129
|
- !ruby/object:Gem::Version
|
130
|
-
version: '0'
|
130
|
+
version: '1.0'
|
131
131
|
description: SQL input/output plugin for Fluentd event collector
|
132
132
|
email: frsyuki@gmail.com
|
133
133
|
executables: []
|
@@ -135,6 +135,7 @@ extensions: []
|
|
135
135
|
extra_rdoc_files: []
|
136
136
|
files:
|
137
137
|
- ".travis.yml"
|
138
|
+
- ChangeLog
|
138
139
|
- Gemfile
|
139
140
|
- Gemfile.v0.12
|
140
141
|
- README.md
|
@@ -146,8 +147,9 @@ files:
|
|
146
147
|
- test/fixtures/schema.rb
|
147
148
|
- test/helper.rb
|
148
149
|
- test/plugin/test_in_sql.rb
|
150
|
+
- test/plugin/test_in_sql_with_custom_time.rb
|
149
151
|
- test/plugin/test_out_sql.rb
|
150
|
-
homepage: https://github.com/
|
152
|
+
homepage: https://github.com/fluent/fluent-plugin-sql
|
151
153
|
licenses:
|
152
154
|
- Apache-2.0
|
153
155
|
metadata: {}
|
@@ -166,8 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
168
|
- !ruby/object:Gem::Version
|
167
169
|
version: '0'
|
168
170
|
requirements: []
|
169
|
-
|
170
|
-
rubygems_version: 2.6.11
|
171
|
+
rubygems_version: 3.0.3
|
171
172
|
signing_key:
|
172
173
|
specification_version: 4
|
173
174
|
summary: SQL input/output plugin for Fluentd event collector
|
@@ -175,4 +176,5 @@ test_files:
|
|
175
176
|
- test/fixtures/schema.rb
|
176
177
|
- test/helper.rb
|
177
178
|
- test/plugin/test_in_sql.rb
|
179
|
+
- test/plugin/test_in_sql_with_custom_time.rb
|
178
180
|
- test/plugin/test_out_sql.rb
|