fluent-plugin-fields-autotype 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ fluent-plugin-fields-autotype
2
+ ===========================
3
+
4
+ Fluent output filter plugin for parsing key/value fields in records
5
+ based on &lt;key>=&lt;value> pattern. Automatically determines the data type of the value (string, integer or float).
6
+
7
+ ## Installation
8
+
9
+ Use RubyGems:
10
+
11
+ gem install fluent-plugin-fields-autotype
12
+
13
+ ## Configuration
14
+
15
+ <match pattern>
16
+ type fields_autotype
17
+
18
+ remove_tag_prefix raw
19
+ add_tag_prefix parsed
20
+ pattern (\S+)=(\S+)
21
+ </match>
22
+
23
+ If following record is passed:
24
+
25
+ ```
26
+ {"message": "Audit log user=Johny action=add-user result=success" }
27
+ ```
28
+
29
+ then you will get a new record:
30
+
31
+ ```
32
+ {
33
+ "message": "Audit log username=Johny action=add-user result=success",
34
+ "username": "Johny",
35
+ "action": "add-user",
36
+ "result": "success"
37
+ }
38
+ ```
39
+
40
+ ### Parameter parse_key
41
+
42
+ For configuration
43
+
44
+ <match pattern>
45
+ type fields_autotype
46
+
47
+ parse_key log_message
48
+ </match>
49
+
50
+ it parses key "log_message" instead of default key `message`.
51
+
52
+ ### Parameter fields_key
53
+
54
+ Configuration
55
+
56
+ <match pattern>
57
+ type fields_autotype
58
+
59
+ parse_key log_message
60
+ fields_key fields
61
+ </match>
62
+
63
+ For input like:
64
+
65
+ ```
66
+ {
67
+ "log_message": "Audit log username=Johny action=add-user result=success",
68
+ }
69
+ ```
70
+
71
+ it adds parsed fields into defined key.
72
+
73
+ ```
74
+ {
75
+ "log_message": "Audit log username=Johny action='add-user' result=success",
76
+ "fields": {"user": "Johny", "action": "add-user", "result": "success"}
77
+ }
78
+ ```
79
+
80
+ (It adds new keys into top-level record by default.)
81
+
82
+ ### Parameter pattern
83
+
84
+ You can define custom pattern (regexp) for seaching keys/values. Data type like float and int are automatically determined.
85
+
86
+ Configuration
87
+
88
+ <match pattern>
89
+ type fields_autotype
90
+
91
+ pattern (\w+):(\S+)
92
+ </match>
93
+
94
+ For input like:
95
+ ```
96
+ { "message": "black:54 white:55 red:10.1"}
97
+ ```
98
+
99
+ it returns:
100
+
101
+ ```
102
+ { "message": "black:54 white=55 red=10.1",
103
+ "black": 54, "white": 55, "red": 10.1
104
+ }
105
+ ```
106
+
107
+ ### Tag prefix
108
+
109
+ You cat add and/or remove tag prefix using Configuration parameters
110
+
111
+ <match pattern>
112
+ type fields_autotype
113
+
114
+ remove_tag_prefix raw
115
+ add_tag_prefix parsed
116
+ </match>
117
+
118
+ If it matched tag "raw.some.record", then it emits tag "parsed.some.record".
119
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rake/testtask'
7
+
8
+ Rake::TestTask.new(:test) do |test|
9
+ test.libs << 'lib' << 'test'
10
+ test.test_files = FileList['test/*.rb']
11
+ test.verbose = true
12
+ end
13
+
14
+ task :default => [:build]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "fluent-plugin-fields-autotype"
6
+ gem.description = "Fluent output filter plugin for parsing key/value fields in records. Automatically determines type of the value as integer, float or string"
7
+ gem.homepage = "https://github.com/CiscoZeus/fluent-plugin-fields-autotype"
8
+ gem.summary = gem.description
9
+ gem.version = File.read("VERSION").strip
10
+ gem.authors = ["Manoj Sharma"]
11
+ gem.email = ["vigyanik@gmail.com"]
12
+ gem.has_rdoc = false
13
+ gem.license = 'MIT'
14
+
15
+ gem.files = `git ls-files`.split("\n")
16
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency "fluentd"
21
+ gem.add_development_dependency "rake"
22
+ end
23
+
@@ -0,0 +1,76 @@
1
+ class String
2
+ def is_i?
3
+ /\A[-+]?\d+\z/ === self
4
+ end
5
+ def nan?
6
+ self !~ /^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/
7
+ end
8
+ end
9
+
10
+ module Fluent
11
+ class OutputFieldsAutotype < Fluent::Output
12
+ Fluent::Plugin.register_output('fields_autotype', self)
13
+
14
+ config_param :remove_tag_prefix, :string, :default => nil
15
+ config_param :add_tag_prefix, :string, :default => nil
16
+ config_param :parse_key, :string, :default => 'message'
17
+ config_param :fields_key, :string, :default => nil
18
+ config_param :pattern, :string,
19
+ :default => %{(\\S+)=(\\S+)}
20
+
21
+
22
+ def compiled_pattern
23
+ @compiled_pattern ||= Regexp.new(pattern)
24
+ end
25
+
26
+ def emit(tag, es, chain)
27
+ tag = update_tag(tag)
28
+ es.each { |time, record|
29
+ Engine.emit(tag, time, parse_fields(record))
30
+ }
31
+ chain.next
32
+ end
33
+
34
+ def update_tag(tag)
35
+ if remove_tag_prefix
36
+ if remove_tag_prefix == tag
37
+ tag = ''
38
+ elsif tag.to_s.start_with?(remove_tag_prefix+'.')
39
+ tag = tag[remove_tag_prefix.length+1 .. -1]
40
+ end
41
+ end
42
+ if add_tag_prefix
43
+ tag = tag && tag.length > 0 ? "#{add_tag_prefix}.#{tag}" : add_tag_prefix
44
+ end
45
+ return tag
46
+ end
47
+
48
+ def parse_fields(record)
49
+ source = record[parse_key].to_s
50
+ target = fields_key ? (record[fields_key] ||= {}) : record
51
+
52
+ reduced_source = source.dup
53
+ until reduced_source.length == 0 do
54
+ match1 = reduced_source.match pattern
55
+ if !(match1.nil?)
56
+ key = match1[1].to_s
57
+ val = match1[2].to_s
58
+ if !(target.has_key?(key))
59
+ if val.is_i?
60
+ target[key] = val.to_i
61
+ elsif val.nan?
62
+ target[key] = val
63
+ else
64
+ target[key] = val.to_f
65
+ end
66
+ end
67
+ reduced_source = reduced_source[match1.offset(2)[1]..-1]
68
+ else
69
+ reduced_source = ""
70
+ end
71
+ end
72
+ return record
73
+ end
74
+ end
75
+ end
76
+
@@ -0,0 +1,269 @@
1
+ require 'fluent/test'
2
+ require 'fluent/plugin/out_fields_autotype'
3
+
4
+ class FieldsAutotypeOutputTest < Test::Unit::TestCase
5
+ def setup
6
+ Fluent::Test.setup
7
+ end
8
+
9
+ def create_driver(conf='', tag='orig.test.tag')
10
+ Fluent::Test::OutputTestDriver.new(Fluent::OutputFieldsAutotype, tag).configure(conf)
11
+ end
12
+
13
+ def test_config_defaults
14
+ d = create_driver()
15
+
16
+ orig_message = %{parse this num=-56.7 tok=abc%25 null=}
17
+ d.run do
18
+ d.emit({
19
+ 'message' => orig_message,
20
+ 'other_key' => %{ test2 a=b },
21
+ })
22
+ end
23
+
24
+ emits = d.emits
25
+ assert_equal 1, emits.size
26
+ assert_equal "orig.test.tag", emits[0][0]
27
+ assert_equal(
28
+ {
29
+ 'message' => orig_message,
30
+ 'other_key' => %{ test2 a=b },
31
+ 'num' => -56.7,
32
+ 'tok' => 'abc%25',
33
+ },
34
+ emits[0][2]
35
+ )
36
+ end
37
+
38
+ def test_quoted_values
39
+ d = create_driver()
40
+
41
+ orig_message = %{blax dq="asd ' asd +3" sq='as " s " 4' s=yu 6}
42
+ d.run do
43
+ d.emit({
44
+ 'message' => orig_message,
45
+ })
46
+ end
47
+
48
+ emits = d.emits
49
+ assert_equal 1, emits.size
50
+ assert_equal "orig.test.tag", emits[0][0]
51
+ assert_equal(
52
+ {
53
+ 'message' => orig_message,
54
+ 'dq' => "\"asd",
55
+ 'sq' => "'as",
56
+ 's' => 'yu'
57
+ },
58
+ emits[0][2]
59
+ )
60
+ end
61
+
62
+ def test_parsed_key_is_missing
63
+ d = create_driver()
64
+
65
+ d.run do
66
+ d.emit({})
67
+ end
68
+
69
+ emits = d.emits
70
+ assert_equal 1, emits.size
71
+ assert_equal "orig.test.tag", emits[0][0]
72
+ assert_equal(
73
+ {},
74
+ emits[0][2]
75
+ )
76
+ end
77
+
78
+ def test_existing_keys_are_not_overriden
79
+ d = create_driver()
80
+
81
+ orig_message = %{mock a=77 message=blax a=999 e=5}
82
+ d.run do
83
+ d.emit({'message' => orig_message, 'e' => nil })
84
+ end
85
+
86
+ emits = d.emits
87
+ assert_equal 1, emits.size
88
+ assert_equal "orig.test.tag", emits[0][0]
89
+ assert_equal(
90
+ {
91
+ 'message' => orig_message,
92
+ 'a' => 77,
93
+ 'e' => nil,
94
+ },
95
+ emits[0][2]
96
+ )
97
+ end
98
+
99
+ def test_tag_prefixes
100
+ d = create_driver(%{
101
+ remove_tag_prefix orig
102
+ add_tag_prefix new
103
+ })
104
+
105
+ d.run do
106
+ d.emit({ message => 'abc' })
107
+ end
108
+
109
+ emits = d.emits
110
+ assert_equal 1, emits.size
111
+ assert_equal "new.test.tag", emits[0][0]
112
+
113
+ d = create_driver(%{
114
+ remove_tag_prefix orig
115
+ add_tag_prefix new
116
+ }, tag=nil)
117
+
118
+ d.run do
119
+ d.emit({ message => 'abc' })
120
+ end
121
+
122
+ emits = d.emits
123
+ assert_equal 1, emits.size
124
+ assert_equal "new", emits[0][0]
125
+
126
+ d = create_driver(%{
127
+ remove_tag_prefix orig
128
+ add_tag_prefix new
129
+ }, tag='original')
130
+
131
+ d.run do
132
+ d.emit({ message => 'abc' })
133
+ end
134
+
135
+ emits = d.emits
136
+ assert_equal 1, emits.size
137
+ assert_equal "new.original", emits[0][0]
138
+
139
+ d = create_driver(%{
140
+ remove_tag_prefix orig
141
+ add_tag_prefix new
142
+ }, tag='orig')
143
+
144
+ d.run do
145
+ d.emit({ message => 'abc' })
146
+ end
147
+
148
+ emits = d.emits
149
+ assert_equal 1, emits.size
150
+ assert_equal "new", emits[0][0]
151
+ end
152
+
153
+ def test_parse_key
154
+ d = create_driver('parse_key custom_key')
155
+
156
+ d.run do
157
+ d.emit({
158
+ 'message' => %{ test2 c=d },
159
+ 'custom_key' => %{ test2 a=b },
160
+ })
161
+ d.emit({})
162
+ end
163
+
164
+ emits = d.emits
165
+ assert_equal 2, emits.size
166
+ assert_equal "orig.test.tag", emits[0][0]
167
+ assert_equal(
168
+ {
169
+ 'message' => %{ test2 c=d },
170
+ 'custom_key' => %{ test2 a=b },
171
+ 'a' => 'b'
172
+ },
173
+ emits[0][2]
174
+ )
175
+ assert_equal(
176
+ {
177
+ },
178
+ emits[1][2]
179
+ )
180
+ end
181
+
182
+ def test_fields_key
183
+ d = create_driver("fields_key output-key")
184
+
185
+ orig_message = %{parse this num=-56.7 tok=abc%25 message=a+b}
186
+ d.run do
187
+ d.emit({'message' => orig_message})
188
+ end
189
+
190
+ emits = d.emits
191
+ assert_equal 1, emits.size
192
+ assert_equal "orig.test.tag", emits[0][0]
193
+ assert_equal(
194
+ {
195
+ 'message' => orig_message,
196
+ 'output-key' => {
197
+ 'num' => -56.7,
198
+ 'tok' => 'abc%25',
199
+ 'message' => 'a+b',
200
+ }
201
+ },
202
+ emits[0][2]
203
+ )
204
+ end
205
+
206
+ def test_custom_pattern
207
+ d = create_driver("pattern (\\w+):(\\d+)")
208
+
209
+ orig_message = %{parse this a:44 b:ignore-this h=7 bbb:999}
210
+ d.run do
211
+ d.emit({'message' => orig_message})
212
+ d.emit({'message' => 'a'})
213
+ end
214
+
215
+ emits = d.emits
216
+ assert_equal 2, emits.size
217
+ assert_equal "orig.test.tag", emits[0][0]
218
+ assert_equal(
219
+ {
220
+ 'message' => orig_message,
221
+ 'a' => 44,
222
+ 'bbb' => 999,
223
+ },
224
+ emits[0][2]
225
+ )
226
+ assert_equal(
227
+ {
228
+ 'message' => 'a',
229
+ },
230
+ emits[1][2]
231
+ )
232
+ end
233
+
234
+ def test_strict_key_value
235
+ d = create_driver()
236
+
237
+ orig_message = %{msg=Auditlog user=Johnny action=add-user dontignore=don't-ignore-this result=success iVal=23 fVal=1.02 bVal=true}
238
+ d.run do
239
+ d.emit({'message' => orig_message})
240
+ d.emit({'message' => 'a'})
241
+ end
242
+
243
+ emits = d.emits
244
+ assert_equal 2, emits.size
245
+ assert_equal "orig.test.tag", emits[0][0]
246
+ assert_equal(
247
+ {
248
+ 'message' => orig_message,
249
+ "msg"=>"Auditlog",
250
+ 'user' => "Johnny",
251
+ 'action' => 'add-user',
252
+ 'dontignore' => "don't-ignore-this",
253
+ 'result' => 'success',
254
+ 'iVal' => 23,
255
+ 'fVal' => 1.02,
256
+ 'bVal' => "true"
257
+ },
258
+ emits[0][2]
259
+ )
260
+ assert_equal(
261
+ {
262
+ 'message' => 'a',
263
+ },
264
+ emits[1][2]
265
+ )
266
+ end
267
+
268
+
269
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-fields-autotype
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Manoj Sharma
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-10-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fluentd
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Fluent output filter plugin for parsing key/value fields in records.
47
+ Automatically determines type of the value as integer, float or string
48
+ email:
49
+ - vigyanik@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - README.md
56
+ - Rakefile
57
+ - VERSION
58
+ - fluent-plugin-fields-autotype.gemspec
59
+ - lib/fluent/plugin/out_fields_autotype.rb
60
+ - test/out_fields_autotype.rb
61
+ homepage: https://github.com/CiscoZeus/fluent-plugin-fields-autotype
62
+ licenses:
63
+ - MIT
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.23
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Fluent output filter plugin for parsing key/value fields in records. Automatically
86
+ determines type of the value as integer, float or string
87
+ test_files: []