fluent-plugin-fields-autotype 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []