fluent-plugin-sql 0.2.2 → 0.3.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9ca23328ec98228710390c94c0f3f3a1d762e3c4
4
+ data.tar.gz: 0ff6ef9512db112b7938b2af612a2ffe59ddd59e
5
+ SHA512:
6
+ metadata.gz: 302054c46157cc5438085b75e612a8353d6859b5f997ca66984b2064231e25587cdaee438050e645c345ad33c3a2377a21a85f3317f6d25c1fdceaf181e8f29f
7
+ data.tar.gz: d567b461ca80f21adbd7c07e33a4deb801a20ea54ccc955e6091e84230ce51c7925b41520aa8640788079921d197bf1f33727a9b4f736dad2158a921cefc8537
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # SQL input plugin for Fluentd event collector
1
+ # SQL input plugin for [Fluentd](http://fluentd.org) event collector
2
2
 
3
3
  ## Overview
4
4
 
@@ -23,7 +23,7 @@ It stores last selected rows to a file (named *state\_file*) to not forget the l
23
23
  host rdb_host
24
24
  database rdb_database
25
25
  adapter mysql2_or_postgresql_etc
26
- user myusername
26
+ username myusername
27
27
  password mypassword
28
28
 
29
29
  tag_prefix my.rdb # optional, but recommended
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.3.0
@@ -18,7 +18,8 @@ Gem::Specification.new do |gem|
18
18
  gem.license = "Apache 2.0"
19
19
 
20
20
  gem.add_dependency "fluentd", "~> 0.10.0"
21
- gem.add_dependency 'activerecord', ['3.2.12']
21
+ gem.add_dependency 'activerecord', "~> 4.0.2"
22
+ gem.add_dependency 'activerecord-import', "~> 0.4.1"
22
23
  gem.add_dependency 'mysql2', ['~> 0.3.12']
23
24
  gem.add_dependency 'pg', ['~> 0.16.0']
24
25
  gem.add_development_dependency "rake", ">= 0.9.2"
@@ -28,12 +28,17 @@ module Fluent
28
28
  config_param :database, :string
29
29
  config_param :username, :string, :default => nil
30
30
  config_param :password, :string, :default => nil
31
+ config_param :socket, :string, :default => nil
31
32
 
32
33
  config_param :state_file, :string, :default => nil
33
34
  config_param :tag_prefix, :string, :default => nil
34
35
  config_param :select_interval, :time, :default => 60
35
36
  config_param :select_limit, :time, :default => 500
36
37
 
38
+ unless method_defined?(:log)
39
+ define_method(:log) { $log }
40
+ end
41
+
37
42
  class TableElement
38
43
  include Configurable
39
44
 
@@ -44,11 +49,6 @@ module Fluent
44
49
 
45
50
  def configure(conf)
46
51
  super
47
-
48
- unless @state_file
49
- $log.warn "'state_file PATH' parameter is not set to a 'sql' source."
50
- $log.warn "this parameter is highly recommended to save the last rows to resume tailing."
51
- end
52
52
  end
53
53
 
54
54
  def init(tag_prefix, base_model)
@@ -129,6 +129,11 @@ module Fluent
129
129
  def configure(conf)
130
130
  super
131
131
 
132
+ unless @state_file
133
+ $log.warn "'state_file PATH' parameter is not set to a 'sql' source."
134
+ $log.warn "this parameter is highly recommended to save the last rows to resume tailing."
135
+ end
136
+
132
137
  @tables = conf.elements.select {|e|
133
138
  e.name == 'table'
134
139
  }.map {|e|
@@ -154,6 +159,7 @@ module Fluent
154
159
  :database => @database,
155
160
  :username => @username,
156
161
  :password => @password,
162
+ :socket => @socket,
157
163
  }
158
164
 
159
165
  # creates subclass of ActiveRecord::Base so that it can have different
@@ -166,7 +172,7 @@ module Fluent
166
172
  # ActiveRecord requires the base_model to have a name. Here sets name
167
173
  # of an anonymous class by assigning it to a constant. In Ruby, class has
168
174
  # a name of a constant assigned first
169
- SQLInput.const_set("BaseModel_#{rand(1<<31)}", @base_model)
175
+ SQLInput.const_set("BaseModel_#{rand(1 << 31)}", @base_model)
170
176
 
171
177
  # Now base_model can have independent configuration from ActiveRecord::Base
172
178
  @base_model.establish_connection(config)
@@ -193,11 +199,11 @@ module Fluent
193
199
  @tables.reject! do |te|
194
200
  begin
195
201
  te.init(@tag_prefix, @base_model)
196
- $log.info "Selecting '#{te.table}' table"
202
+ log.info "Selecting '#{te.table}' table"
197
203
  false
198
- rescue
199
- $log.warn "Can't handle '#{te.table}' table. Ignoring.", :error => $!
200
- $log.warn_backtrace $!.backtrace
204
+ rescue => e
205
+ log.warn "Can't handle '#{te.table}' table. Ignoring.", :error => e.message, :error_class => e.class
206
+ log.warn_backtrace e.backtrace
201
207
  true
202
208
  end
203
209
  end
@@ -219,9 +225,9 @@ module Fluent
219
225
  last_record = @state_store.last_records[t.table]
220
226
  @state_store.last_records[t.table] = t.emit_next_records(last_record, @select_limit)
221
227
  @state_store.update!
222
- rescue
223
- $log.error "unexpected error", :error=>$!.to_s
224
- $log.error_backtrace
228
+ rescue => e
229
+ log.error "unexpected error", :error => e.message, :error_class => e.class
230
+ log.error_backtrace e.backtrace
225
231
  end
226
232
  end
227
233
  end
@@ -0,0 +1,232 @@
1
+ module Fluent
2
+ class SQLOutput < BufferedOutput
3
+ Plugin.register_output('sql', self)
4
+
5
+ include SetTimeKeyMixin
6
+ include SetTagKeyMixin
7
+
8
+ config_param :host, :string
9
+ config_param :port, :integer, :default => nil
10
+ config_param :adapter, :string
11
+ config_param :username, :string, :default => nil
12
+ config_param :password, :string, :default => nil
13
+ config_param :database, :string
14
+ config_param :socket, :string, :default => nil
15
+ config_param :remove_tag_prefix, :string, :default => nil
16
+
17
+ attr_accessor :tables
18
+
19
+ unless method_defined?(:log)
20
+ define_method(:log) { $log }
21
+ end
22
+
23
+ # TODO: Merge SQLInput's TableElement
24
+ class TableElement
25
+ include Configurable
26
+
27
+ config_param :table, :string
28
+ config_param :column_mapping, :string
29
+ config_param :num_retries, :integer, :default => 5
30
+
31
+ attr_reader :model
32
+ attr_reader :pattern
33
+
34
+ def initialize(pattern, log)
35
+ super()
36
+ @pattern = MatchPattern.create(pattern)
37
+ @log = log
38
+ end
39
+
40
+ def configure(conf)
41
+ super
42
+
43
+ @mapping = parse_column_mapping(@column_mapping)
44
+ @format_proc = Proc.new { |record|
45
+ new_record = {}
46
+ @mapping.each { |k, c|
47
+ new_record[c] = record[k]
48
+ }
49
+ new_record
50
+ }
51
+ end
52
+
53
+ def init(base_model)
54
+ # See SQLInput for more details of following code
55
+ table_name = @table
56
+ @model = Class.new(base_model) do
57
+ self.table_name = table_name
58
+ self.inheritance_column = '_never_use_output_'
59
+ end
60
+
61
+ class_name = table_name.singularize.camelize
62
+ base_model.const_set(class_name, @model)
63
+ model_name = ActiveModel::Name.new(@model, nil, class_name)
64
+ @model.define_singleton_method(:model_name) { model_name }
65
+
66
+ # TODO: check column_names and table schema
67
+ columns = @model.columns.map { |column| column.name }.sort
68
+ end
69
+
70
+ def import(chunk)
71
+ records = []
72
+ chunk.msgpack_each { |tag, time, data|
73
+ begin
74
+ # format process should be moved to emit / format after supports error stream.
75
+ records << @model.new(@format_proc.call(data))
76
+ rescue => e
77
+ args = {:error => e.message, :error_class => e.class, :table => @table, :record => Yajl.dump(data)}
78
+ @log.warn "Failed to create the model. Ignore a record:", args
79
+ end
80
+ }
81
+ begin
82
+ @model.import(records)
83
+ rescue ActiveRecord::StatementInvalid, ActiveRecord::ThrowResult, ActiveRecord::Import::MissingColumnError => e
84
+ # ignore other exceptions to use Fluentd retry mechanizm
85
+ @log.warn "Got deterministic error. Fallback to one-by-one import", :error => e.message, :error_class => e.class
86
+ one_by_one_import(records)
87
+ end
88
+ end
89
+
90
+ def one_by_one_import(records)
91
+ records.each { |record|
92
+ retries = 0
93
+ begin
94
+ @model.import([record])
95
+ rescue ActiveRecord::StatementInvalid, ActiveRecord::ThrowResult, ActiveRecord::Import::MissingColumnError => e
96
+ @log.error "Got deterministic error again. Dump a record", :error => e.message, :error_class => e.class, :record => record
97
+ rescue => e
98
+ retries += 1
99
+ if retries > @num_retries
100
+ @log.error "Can't recover undeterministic error. Dump a record", :error => e.message, :error_class => e.class, :record => record
101
+ next
102
+ end
103
+
104
+ @log.warn "Failed to import a record: retry number = #{retries}", :error => e.message, :error_class => e.class
105
+ sleep 0.5
106
+ retry
107
+ end
108
+ }
109
+ end
110
+
111
+ private
112
+
113
+ def parse_column_mapping(column_mapping_conf)
114
+ mapping = {}
115
+ column_mapping_conf.split(',').each { |column_map|
116
+ key, column = column_map.strip.split(':', 2)
117
+ column = key if column.nil?
118
+ mapping[key] = column
119
+ }
120
+ mapping
121
+ end
122
+ end
123
+
124
+ def initialize
125
+ super
126
+ require 'active_record'
127
+ require 'activerecord-import'
128
+ end
129
+
130
+ def configure(conf)
131
+ super
132
+
133
+ if remove_tag_prefix = conf['remove_tag_prefix']
134
+ @remove_tag_prefix = Regexp.new('^' + Regexp.escape(remove_tag_prefix))
135
+ end
136
+
137
+ @tables = []
138
+ @default_table = nil
139
+ conf.elements.select { |e|
140
+ e.name == 'table'
141
+ }.each { |e|
142
+ te = TableElement.new(e.arg, log)
143
+ te.configure(e)
144
+ if e.arg.empty?
145
+ $log.warn "Detect duplicate default table definition" if @default_table
146
+ @default_table = te
147
+ else
148
+ @tables << te
149
+ end
150
+ }
151
+ @only_default = @tables.empty?
152
+
153
+ if @default_table.nil?
154
+ raise ConfigError, "There is no default table. <table> is required in sql output"
155
+ end
156
+ end
157
+
158
+ def start
159
+ super
160
+
161
+ config = {
162
+ :adapter => @adapter,
163
+ :host => @host,
164
+ :port => @port,
165
+ :database => @database,
166
+ :username => @username,
167
+ :password => @password,
168
+ :socket => @socket,
169
+ }
170
+
171
+ @base_model = Class.new(ActiveRecord::Base) do
172
+ self.abstract_class = true
173
+ end
174
+
175
+ SQLOutput.const_set("BaseModel_#{rand(1 << 31)}", @base_model)
176
+ @base_model.establish_connection(config)
177
+
178
+ # ignore tables if TableElement#init failed
179
+ @tables.reject! do |te|
180
+ init_table(te, @base_model)
181
+ end
182
+ init_table(@default_table, @base_model)
183
+ end
184
+
185
+ def shutdown
186
+ super
187
+ end
188
+
189
+ def emit(tag, es, chain)
190
+ if @only_default
191
+ super(tag, es, chain)
192
+ else
193
+ super(tag, es, chain, format_tag(tag))
194
+ end
195
+ end
196
+
197
+ def format(tag, time, record)
198
+ [tag, time, record].to_msgpack
199
+ end
200
+
201
+ def write(chunk)
202
+ @tables.each { |table|
203
+ if table.pattern.match(chunk.key)
204
+ return table.import(chunk)
205
+ end
206
+ }
207
+ @default_table.import(chunk)
208
+ end
209
+
210
+ private
211
+
212
+ def init_table(te, base_model)
213
+ begin
214
+ te.init(base_model)
215
+ log.info "Selecting '#{te.table}' table"
216
+ false
217
+ rescue => e
218
+ log.warn "Can't handle '#{te.table}' table. Ignoring.", :error => e.message, :error_class => e.class
219
+ log.warn_backtrace e.backtrace
220
+ true
221
+ end
222
+ end
223
+
224
+ def format_tag(tag)
225
+ if @remove_tag_prefix
226
+ tag.gsub(@remove_tag_prefix, '')
227
+ else
228
+ tag
229
+ end
230
+ end
231
+ end
232
+ end
metadata CHANGED
@@ -1,94 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
5
- prerelease:
4
+ version: 0.3.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Sadayuki Furuhashi
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-12-09 00:00:00.000000000 Z
11
+ date: 2014-08-28 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: fluentd
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
19
  version: 0.10.0
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
26
  version: 0.10.0
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: activerecord
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - '='
31
+ - - "~>"
36
32
  - !ruby/object:Gem::Version
37
- version: 3.2.12
33
+ version: 4.0.2
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - '='
38
+ - - "~>"
44
39
  - !ruby/object:Gem::Version
45
- version: 3.2.12
40
+ version: 4.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: activerecord-import
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.4.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.4.1
46
55
  - !ruby/object:Gem::Dependency
47
56
  name: mysql2
48
57
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
58
  requirements:
51
- - - ~>
59
+ - - "~>"
52
60
  - !ruby/object:Gem::Version
53
61
  version: 0.3.12
54
62
  type: :runtime
55
63
  prerelease: false
56
64
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
65
  requirements:
59
- - - ~>
66
+ - - "~>"
60
67
  - !ruby/object:Gem::Version
61
68
  version: 0.3.12
62
69
  - !ruby/object:Gem::Dependency
63
70
  name: pg
64
71
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
72
  requirements:
67
- - - ~>
73
+ - - "~>"
68
74
  - !ruby/object:Gem::Version
69
75
  version: 0.16.0
70
76
  type: :runtime
71
77
  prerelease: false
72
78
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
79
  requirements:
75
- - - ~>
80
+ - - "~>"
76
81
  - !ruby/object:Gem::Version
77
82
  version: 0.16.0
78
83
  - !ruby/object:Gem::Dependency
79
84
  name: rake
80
85
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
86
  requirements:
83
- - - ! '>='
87
+ - - ">="
84
88
  - !ruby/object:Gem::Version
85
89
  version: 0.9.2
86
90
  type: :development
87
91
  prerelease: false
88
92
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
93
  requirements:
91
- - - ! '>='
94
+ - - ">="
92
95
  - !ruby/object:Gem::Version
93
96
  version: 0.9.2
94
97
  description: SQL input/output plugin for Fluentd event collector
@@ -103,30 +106,29 @@ files:
103
106
  - VERSION
104
107
  - fluent-plugin-sql.gemspec
105
108
  - lib/fluent/plugin/in_sql.rb
109
+ - lib/fluent/plugin/out_sql.rb
106
110
  homepage: https://github.com/frsyuki/fluent-plugin-sql
107
111
  licenses:
108
112
  - Apache 2.0
113
+ metadata: {}
109
114
  post_install_message:
110
115
  rdoc_options: []
111
116
  require_paths:
112
117
  - lib
113
118
  required_ruby_version: !ruby/object:Gem::Requirement
114
- none: false
115
119
  requirements:
116
- - - ! '>='
120
+ - - ">="
117
121
  - !ruby/object:Gem::Version
118
122
  version: '0'
119
123
  required_rubygems_version: !ruby/object:Gem::Requirement
120
- none: false
121
124
  requirements:
122
- - - ! '>='
125
+ - - ">="
123
126
  - !ruby/object:Gem::Version
124
127
  version: '0'
125
128
  requirements: []
126
129
  rubyforge_project:
127
- rubygems_version: 1.8.23
130
+ rubygems_version: 2.2.2
128
131
  signing_key:
129
- specification_version: 3
132
+ specification_version: 4
130
133
  summary: SQL input/output plugin for Fluentd event collector
131
134
  test_files: []
132
- has_rdoc: false