fluent-plugin-lookup 0.0.1

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: bc552128863c56880e1ed789e3dc9956f8063bfe
4
+ data.tar.gz: a870d43673d7027df0f90fdf6343dae2f4f397f1
5
+ SHA512:
6
+ metadata.gz: a4752b33dcba97bd4aaa359e63678efc02437f61315f3ea5c858715bb406ba22ca134aebc7299bdff21f0be591341c84851dfe88bbe986893b1533ddb01cddcc
7
+ data.tar.gz: 45bcabbafc264a61d6686ed6c305997d88b7726760da4102428c26db0537caca08697e50e6ae1a93a070abd176d75b53602c975a057fecacc90ec90e4c169f3d
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-filter_custom_cart.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ fluent-plugin-lookup
2
+ ====================
3
+
4
+ (Yet another Fluentd plugin)
5
+
6
+ What
7
+ ----
8
+
9
+ Allows to replace record values for specific keys, using a lookup table from a *CSV* file.
10
+
11
+ How
12
+ ---
13
+
14
+ You basically want to define :
15
+ - Input field : **field**.
16
+ - Output field : **output_field** (omitting this parameter will replace *input* field value).
17
+ - Lookup table CSV file : **table_file** (two columns per row, separated by a comma).
18
+ - Sanity check, raises error if empty, malformed file or duplicates entries inside the file : **strict** (omitting this parameter will default to *false*).
19
+
20
+ Use this filter multiple times if you need to repalce multiple fields.
21
+
22
+ Examples
23
+ ========
24
+
25
+ This is our *lookup.csv* file :
26
+
27
+ ```
28
+ value,other_value
29
+ nicolas,cage
30
+ input,output
31
+ 1,one
32
+ two,2
33
+ ```
34
+
35
+ Example 1
36
+ ---------
37
+
38
+ ```
39
+ <match *.test>
40
+ type lookup
41
+ add_tag_prefix lookup.
42
+ table_file /usr/share/my/lookup.csv
43
+ field key1
44
+ output_field key2
45
+ </match>
46
+ ```
47
+
48
+ Example of records :
49
+ ```
50
+ {
51
+ 'key1' => "nicolas",
52
+ 'foo' => "bar"
53
+ }
54
+ ```
55
+ ... will output :
56
+ ```
57
+ {
58
+ 'key1' => "nicolas",
59
+ 'key2' => "cage",
60
+ 'foo' => "bar"
61
+ }
62
+ ```
63
+
64
+ Example 2
65
+ ---------
66
+
67
+ ```
68
+ <match *.test>
69
+ type lookup
70
+ add_tag_prefix lookup.
71
+ table_file /usr/share/my/lookup.csv
72
+ field key1
73
+ </match>
74
+ ```
75
+
76
+ Example of records :
77
+ ```
78
+ {
79
+ 'key1' => "nicolas",
80
+ 'foo' => "bar"
81
+ }
82
+ ```
83
+ ... will output :
84
+ ```
85
+ {
86
+ 'key1' => "cage",
87
+ 'foo' => "bar"
88
+ }
89
+ ```
90
+
91
+ Since *output_field* is not defined, the input *field* value is replaced.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ desc 'Default: run test.'
6
+ task :default => :test
7
+
8
+ Rake::TestTask.new(:test) do |test|
9
+ test.libs << 'lib' << 'test'
10
+ test.pattern = 'test/**/test_*.rb'
11
+ test.verbose = true
12
+ end
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "fluent-plugin-lookup"
3
+ spec.version = "0.0.1"
4
+ spec.authors = ["Neozaru"]
5
+ spec.email = ["neozaru@mailoo.org"]
6
+ spec.description = %q{Fluentd custom plugin to replace fields values using lookup table file}
7
+ spec.summary = %q{Fluentd custom plugin to replace fields values using lookup table file}
8
+ spec.homepage = "https://github.com/Neozaru/fluent-plugin-lookup.git"
9
+ spec.license = "WTFPL"
10
+
11
+ spec.files = `git ls-files`.split($/)
12
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
13
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
14
+ spec.require_paths = ["lib"]
15
+
16
+ spec.add_development_dependency "bundler"
17
+ spec.add_development_dependency "rake"
18
+ spec.add_development_dependency "fluentd"
19
+ end
@@ -0,0 +1,101 @@
1
+ # coding: utf-8
2
+ require "csv"
3
+
4
+ module Fluent
5
+ class LookupOutput < Output
6
+ include Fluent::HandleTagNameMixin
7
+
8
+ Fluent::Plugin.register_output('lookup', self)
9
+
10
+ config_param :table_file, :string, :default => nil
11
+ config_param :field, :string, :default => nil
12
+ config_param :output_field, :string, :default => nil
13
+ config_param :strict, :bool, :default => false
14
+
15
+ def handle_row(lookup_table, row)
16
+ if (row.length < 2)
17
+ return handle_row_error(row, "Too few columns : #{row.length} instead of 2")
18
+ end
19
+
20
+ # If too much columns
21
+ if (strict && row.length > 2)
22
+ return handle_row_error(row, "Too much columns : #{row.length} instead of 2")
23
+ end
24
+
25
+ # If duplicates
26
+ if (strict && lookup_table.has_key?(row[0]))
27
+ return handle_row_error(row, "Duplicate entry")
28
+ end
29
+
30
+ lookup_table[row[0]] = row[1]
31
+
32
+ end
33
+
34
+
35
+ def create_lookup_table(file)
36
+ lookup_table = {}
37
+ CSV.foreach(file) do |row|
38
+ handle_row(lookup_table, row)
39
+ end
40
+
41
+ if (strict && lookup_table.length == 0)
42
+ raise ConfigError, "Lookup file is empty"
43
+ end
44
+
45
+ return lookup_table
46
+ rescue Errno::ENOENT => e
47
+ handle_file_err(file, e)
48
+ rescue Errno::EACCES => e
49
+ handle_file_err(file, e)
50
+ end
51
+
52
+
53
+ def configure(conf)
54
+ super
55
+
56
+ if (field.nil? || table_file.nil?)
57
+ raise ConfigError, "lookup: Both 'field', and 'table_file' are required to be set."
58
+ end
59
+
60
+ @lookup_table = create_lookup_table(table_file)
61
+ @field = field
62
+ @output_field = output_field || field
63
+
64
+ end
65
+
66
+ def emit(tag, es, chain)
67
+ es.each { |time, record|
68
+ t = tag.dup
69
+ filter_record(t, time, record)
70
+ Engine.emit(t, time, record)
71
+ }
72
+
73
+ chain.next
74
+ end
75
+
76
+ private
77
+
78
+ def filter_record(tag, time, record)
79
+ super(tag, time, record)
80
+ if (not record.has_key?(@field))
81
+ return
82
+ end
83
+ record[@output_field] = process(record[@field]) || record[@field]
84
+ end
85
+
86
+ def process(value)
87
+ return @lookup_table[value]
88
+ end
89
+
90
+
91
+ def handle_row_error(row, e)
92
+ raise ConfigError, "Error at row #{row} : #{e}"
93
+ end
94
+
95
+
96
+ def handle_file_err(file, e)
97
+ raise ConfigError, "Unable to open file '#{file}' : #{e.message}"
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,5 @@
1
+ foo,bar
2
+ nicolas,cage
3
+ input,output
4
+ 1,one
5
+ two,2
@@ -0,0 +1,6 @@
1
+ foo,bar
2
+ nicolas,cage
3
+ foo,bar
4
+ input,output
5
+ 1,one
6
+ two,2
File without changes
@@ -0,0 +1,375 @@
1
+ # coding: utf-8
2
+
3
+ require 'test_helper'
4
+ require 'fluent/plugin/out_lookup'
5
+
6
+ class LookupOutputTest < Test::Unit::TestCase
7
+ def setup
8
+ Fluent::Test.setup
9
+ dir = File.dirname(__FILE__)
10
+ @nonexisting_file = "#{dir}/tobeornottobe.csv"
11
+ @correct_file = "#{dir}/correct.csv"
12
+ @duplicates_file = "#{dir}/duplicates.csv"
13
+ @empty_file = "#{dir}/empty.csv"
14
+ end
15
+
16
+ def create_driver(conf, tag = 'test')
17
+ Fluent::Test::OutputTestDriver.new(
18
+ Fluent::LookupOutput, tag
19
+ ).configure(conf)
20
+ end
21
+
22
+
23
+ def test_configure_on_success
24
+
25
+
26
+ # All set
27
+ d = create_driver(%[
28
+ strict true
29
+ add_tag_prefix lookup.
30
+ table_file #{@correct_file}
31
+ field key1
32
+ output_field key2
33
+ ])
34
+
35
+ assert_equal 'lookup.', d.instance.add_tag_prefix
36
+ assert_equal 'key1', d.instance.field
37
+ assert_equal 'key2', d.instance.output_field
38
+ assert_equal true, d.instance.strict
39
+ assert_equal @correct_file, d.instance.table_file
40
+
41
+
42
+ # "Strict" omitted
43
+ d = create_driver(%[
44
+ add_tag_prefix lookup.
45
+ table_file #{@correct_file}
46
+ field key1
47
+ output_field key2
48
+ ])
49
+
50
+ assert_equal 'lookup.', d.instance.add_tag_prefix
51
+ assert_equal 'key1', d.instance.field
52
+ assert_equal 'key2', d.instance.output_field
53
+ assert_equal false, d.instance.strict
54
+ assert_equal @correct_file, d.instance.table_file
55
+
56
+
57
+ # "output_field" omitted
58
+ d = create_driver(%[
59
+ strict true
60
+ add_tag_prefix lookup.
61
+ table_file #{@correct_file}
62
+ field key1
63
+ ])
64
+
65
+ assert_equal 'lookup.', d.instance.add_tag_prefix
66
+ assert_equal 'key1', d.instance.field
67
+ assert_equal 'key1', d.instance.output_field
68
+ assert_equal true, d.instance.strict
69
+ assert_equal @correct_file, d.instance.table_file
70
+
71
+
72
+ # File with duplicates in non-strict mode
73
+ d = create_driver(%[
74
+ strict false
75
+ add_tag_prefix lookup.
76
+ table_file #{@duplicates_file}
77
+ field key1
78
+ output_field key2
79
+ ])
80
+
81
+ assert_equal 'lookup.', d.instance.add_tag_prefix
82
+ assert_equal 'key1', d.instance.field
83
+ assert_equal 'key2', d.instance.output_field
84
+ assert_equal false, d.instance.strict
85
+ assert_equal @duplicates_file, d.instance.table_file
86
+
87
+
88
+ # Empty file in non-strict mode
89
+ d = create_driver(%[
90
+ strict false
91
+ add_tag_prefix lookup.
92
+ table_file #{@empty_file}
93
+ field key1
94
+ output_field key2
95
+ ])
96
+
97
+ assert_equal 'lookup.', d.instance.add_tag_prefix
98
+ assert_equal 'key1', d.instance.field
99
+ assert_equal 'key2', d.instance.output_field
100
+ assert_equal false, d.instance.strict
101
+ assert_equal @empty_file, d.instance.table_file
102
+
103
+ end
104
+
105
+ def test_configure_on_failure
106
+ # when mandatory keys not set
107
+ assert_raise(Fluent::ConfigError) do
108
+ create_driver(%[
109
+ blah blah
110
+ ])
111
+ end
112
+
113
+ # 'field' is missing
114
+ assert_raise(Fluent::ConfigError) do
115
+ create_driver(%[
116
+ strict true
117
+ add_tag_prefix lookup.
118
+ table_file #{@correct_file}
119
+ output_field key2
120
+ ])
121
+ end
122
+
123
+ # 'table_file' is missing
124
+ assert_raise(Fluent::ConfigError) do
125
+ create_driver(%[
126
+ strict true
127
+ add_tag_prefix lookup.
128
+ field key1
129
+ output_field key2
130
+ ])
131
+ end
132
+
133
+ # 'table_file' is not readable in strict mode
134
+ assert_raise(Fluent::ConfigError) do
135
+ create_driver(%[
136
+ strict true
137
+ add_tag_prefix lookup.
138
+ table_file #{@nonexisting_file}
139
+ field key1
140
+ output_field key2
141
+ ])
142
+ end
143
+
144
+ # 'table_file' is not readable in non-strict mode
145
+ assert_raise(Fluent::ConfigError) do
146
+ create_driver(%[
147
+ strict false
148
+ add_tag_prefix lookup.
149
+ table_file #{@nonexisting_file}
150
+ field key1
151
+ output_field key2
152
+ ])
153
+ end
154
+
155
+ # 'table_file' contains duplicates in strict mode
156
+ assert_raise(Fluent::ConfigError) do
157
+ create_driver(%[
158
+ strict true
159
+ add_tag_prefix lookup.
160
+ table_file #{@duplicates_file}
161
+ field key1
162
+ output_field key2
163
+ ])
164
+ end
165
+
166
+ # 'table_file' is empty in strict mode
167
+ assert_raise(Fluent::ConfigError) do
168
+ create_driver(%[
169
+ strict true
170
+ add_tag_prefix lookup.
171
+ table_file #{@empty_file}
172
+ field key1
173
+ output_field key2
174
+ ])
175
+ end
176
+
177
+ end
178
+
179
+
180
+ def test_unit_create_lookup_table
181
+
182
+ # Correct file
183
+ d = create_driver(%[
184
+ add_tag_prefix lookup.
185
+ table_file #{@correct_file}
186
+ field key1
187
+ output_field key2
188
+ ])
189
+
190
+ table = d.instance.create_lookup_table(@correct_file)
191
+ assert_equal({"foo"=>"bar", "nicolas"=>"cage", "input"=>"output", "1"=>"one", "two"=>"2"}, table)
192
+
193
+ # With duplicates
194
+ d = create_driver(%[
195
+ add_tag_prefix lookup.
196
+ table_file #{@duplicates_file}
197
+ field key1
198
+ output_field key2
199
+ ])
200
+
201
+ table = d.instance.create_lookup_table(@duplicates_file)
202
+ assert_equal({"foo"=>"bar", "nicolas"=>"cage", "input"=>"output", "1"=>"one", "two"=>"2"}, table)
203
+
204
+ # Empty file
205
+ d = create_driver(%[
206
+ add_tag_prefix lookup.
207
+ table_file #{@empty_file}
208
+ field key1
209
+ output_field key2
210
+ ])
211
+
212
+ table = d.instance.create_lookup_table(@empty_file)
213
+ assert_equal({}, table)
214
+
215
+ end
216
+
217
+
218
+ def test_unit_handle_row
219
+ # Correct row
220
+ d = create_driver(%[
221
+ add_tag_prefix lookup.
222
+ table_file #{@correct_file}
223
+ field key1
224
+ ])
225
+
226
+ table = {}
227
+ d.instance.handle_row(table, ["foo", "bar"])
228
+ assert_equal({"foo"=>"bar"}, table)
229
+
230
+ # Too small row
231
+ d = create_driver(%[
232
+ add_tag_prefix lookup.
233
+ table_file #{@correct_file}
234
+ field key1
235
+ ])
236
+
237
+ table = {}
238
+ assert_raise do d.instance.handle_row(table, ["foo"]) end
239
+ assert_equal({}, table)
240
+
241
+ # Too large row non strict
242
+ d = create_driver(%[
243
+ add_tag_prefix lookup.
244
+ table_file #{@correct_file}
245
+ field key1
246
+ ])
247
+
248
+ table = {}
249
+ d.instance.handle_row(table, ["foo", "bar", "baz"])
250
+ assert_equal({"foo" => "bar"}, table)
251
+
252
+ # Too large row strict
253
+ d = create_driver(%[
254
+ add_tag_prefix lookup.
255
+ strict true
256
+ table_file #{@correct_file}
257
+ field key1
258
+ ])
259
+
260
+ table = {}
261
+ assert_raise do d.instance.handle_row(table, ["foo", "bar", "baz"]) end
262
+ assert_equal({}, table)
263
+
264
+
265
+ # Too duplicate row non strict
266
+ d = create_driver(%[
267
+ add_tag_prefix lookup.
268
+ table_file #{@correct_file}
269
+ field key1
270
+ ])
271
+
272
+ table = {}
273
+ d.instance.handle_row(table, ["foo", "bar"])
274
+ d.instance.handle_row(table, ["foo", "baz"])
275
+ assert_equal({"foo" => "baz"}, table)
276
+
277
+
278
+ # Too duplicate row strict
279
+ d = create_driver(%[
280
+ add_tag_prefix lookup.
281
+ strict true
282
+ table_file #{@correct_file}
283
+ field key1
284
+ ])
285
+
286
+ table = {}
287
+ d.instance.handle_row(table, ["foo", "bar"])
288
+ assert_raise do d.instance.handle_row(table, ["foo", "baz"]) end
289
+ assert_equal({"foo" => "bar"}, table)
290
+ end
291
+
292
+
293
+ def test_emit_with_output_field
294
+ d = create_driver(%[
295
+ add_tag_prefix lookup.
296
+ table_file #{@correct_file}
297
+ field key1
298
+ output_field key2
299
+ ])
300
+
301
+ record = {
302
+ 'key1' => "nicolas",
303
+ 'foo' => "bar"
304
+ }
305
+
306
+ d.run { d.emit(record) }
307
+ emits = d.emits
308
+
309
+ assert_equal 1, emits.count
310
+ assert_equal 'lookup.test', emits[0][0]
311
+ assert_equal 'cage', emits[0][2]['key2']
312
+ end
313
+
314
+ def test_emit_with_output_field_no_correspondance
315
+ d = create_driver(%[
316
+ add_tag_prefix lookup.
317
+ table_file #{@correct_file}
318
+ field key1
319
+ output_field key2
320
+ ])
321
+
322
+ record = {
323
+ 'key1' => "myvalue",
324
+ 'foo' => "bar"
325
+ }
326
+
327
+ d.run { d.emit(record) }
328
+ emits = d.emits
329
+
330
+ assert_equal 1, emits.count
331
+ assert_equal 'lookup.test', emits[0][0]
332
+ assert_equal 'myvalue', emits[0][2]['key2']
333
+ end
334
+
335
+ def test_emit_without_output_field
336
+ d = create_driver(%[
337
+ add_tag_prefix lookup.
338
+ table_file #{@correct_file}
339
+ field key1
340
+ ])
341
+
342
+ record = {
343
+ 'key1' => "nicolas",
344
+ 'foo' => "bar"
345
+ }
346
+
347
+ d.run { d.emit(record) }
348
+ emits = d.emits
349
+
350
+ assert_equal 1, emits.count
351
+ assert_equal 'lookup.test', emits[0][0]
352
+ assert_equal 'cage', emits[0][2]['key1']
353
+ end
354
+
355
+ def test_emit_without_output_field_no_correspondance
356
+ d = create_driver(%[
357
+ add_tag_prefix lookup.
358
+ table_file #{@correct_file}
359
+ field key1
360
+ ])
361
+
362
+ record = {
363
+ 'key1' => "myvalue",
364
+ 'foo' => "bar"
365
+ }
366
+
367
+ d.run { d.emit(record) }
368
+ emits = d.emits
369
+
370
+ assert_equal 1, emits.count
371
+ assert_equal 'lookup.test', emits[0][0]
372
+ assert_equal 'myvalue', emits[0][2]['key1']
373
+ end
374
+
375
+ end
@@ -0,0 +1,19 @@
1
+ require 'test/unit'
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+ require 'fluent/test'
7
+
8
+ unless ENV.has_key?('VERBOSE')
9
+ nulllogger = Object.new
10
+ nulllogger.instance_eval {|obj|
11
+ def method_missing(method, *args)
12
+ # pass
13
+ end
14
+ }
15
+ $log = nulllogger
16
+ end
17
+
18
+ class Test::Unit::TestCase
19
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-lookup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Neozaru
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fluentd
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Fluentd custom plugin to replace fields values using lookup table file
56
+ email:
57
+ - neozaru@mailoo.org
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - Gemfile
63
+ - README.md
64
+ - Rakefile
65
+ - fluent-plugin-lookup.gemspec
66
+ - lib/fluent/plugin/out_lookup.rb
67
+ - test/plugin/correct.csv
68
+ - test/plugin/duplicates.csv
69
+ - test/plugin/empty.csv
70
+ - test/plugin/test_out_lookup.rb
71
+ - test/test_helper.rb
72
+ homepage: https://github.com/Neozaru/fluent-plugin-lookup.git
73
+ licenses:
74
+ - WTFPL
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.4.5
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Fluentd custom plugin to replace fields values using lookup table file
96
+ test_files:
97
+ - test/plugin/correct.csv
98
+ - test/plugin/duplicates.csv
99
+ - test/plugin/empty.csv
100
+ - test/plugin/test_out_lookup.rb
101
+ - test/test_helper.rb