fluent-plugin-fields-parser 0.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.
@@ -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
@@ -0,0 +1,119 @@
1
+ fluent-plugin-fields-parser
2
+ ===========================
3
+
4
+ Fluent output filter plugin for parsing key/value fields in records
5
+ based on &lt;key>=&lt;value> pattern.
6
+
7
+ ## Installation
8
+
9
+ Use RubyGems:
10
+
11
+ gem install fluent-plugin-fields-parser
12
+
13
+ ## Configuration
14
+
15
+ <match pattern>
16
+ type fields_parser
17
+
18
+ remove_tag_prefix raw
19
+ add_tag_prefix parsed
20
+ </match>
21
+
22
+ If following record is passed:
23
+
24
+ ```
25
+ {"message": "Audit log user=Johny action='add-user' result=success" }
26
+ ```
27
+
28
+ then you got new record:
29
+
30
+ ```
31
+ {
32
+ "message": "Audit log username=Johny action='add-user' result=success",
33
+ "user": "Johny",
34
+ "action": "add-user",
35
+ "result": "success"
36
+ }
37
+ ```
38
+
39
+ ### Parameter parse_key
40
+
41
+ For configuration
42
+
43
+ <match pattern>
44
+ type fields_parser
45
+
46
+ parse_key log_message
47
+ </match>
48
+
49
+ it parses key "log_message" instead of default key `message`.
50
+
51
+ ### Parameter fields_key
52
+
53
+ Configuration
54
+
55
+ <match pattern>
56
+ type fields_parser
57
+
58
+ parse_key log_message
59
+ fields_key fields
60
+ </match>
61
+
62
+ For input like:
63
+
64
+ ```
65
+ {
66
+ "log_message": "Audit log username=Johny action='add-user' result=success",
67
+ }
68
+ ```
69
+
70
+ it adds parsed fields into defined key.
71
+
72
+ ```
73
+ {
74
+ "log_message": "Audit log username=Johny action='add-user' result=success",
75
+ "fields": {"user": "Johny", "action": "add-user", "result": "success"}
76
+ }
77
+ ```
78
+
79
+ (It adds new keys into top-level record by default.)
80
+
81
+ ### Parameter pattern
82
+
83
+ You can define custom pattern (regexp) for seaching keys/values.
84
+
85
+ Configuration
86
+
87
+ <match pattern>
88
+ type fields_parser
89
+
90
+ pattern (\w+):(\d+)
91
+ </match>
92
+
93
+ For input like:
94
+ ```
95
+ { "message": "data black:54 white=55 red=10"}
96
+ ```
97
+
98
+ it returns:
99
+
100
+ ```
101
+ { "message": "data black:54 white=55 red=10",
102
+ "black": "54", "white": "55", "red": "10"
103
+ }
104
+ ```
105
+
106
+ ### Tag prefix
107
+
108
+ You cat add and/or remove tag prefix using Configuration parameters
109
+
110
+ <match pattern>
111
+ type fields_parser
112
+
113
+ remove_tag_prefix raw
114
+ add_tag_prefix parsed
115
+ </match>
116
+
117
+ It it matched tag "raw.some.record", then it emits tag "parsed.some.record".
118
+
119
+
@@ -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.0
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "fluent-plugin-fields-parser"
6
+ gem.description = "Fluent output filter plugin for parsing key/value fields in records"
7
+ gem.homepage = "https://github.com/tomas-zemres/fluent-plugin-fields-parser"
8
+ gem.summary = gem.description
9
+ gem.version = File.read("VERSION").strip
10
+ gem.authors = ["Tomas Pokorny"]
11
+ gem.email = ["tomas.zemres@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
@@ -0,0 +1,54 @@
1
+ module Fluent
2
+ class OutputFieldsParser < Fluent::Output
3
+ Fluent::Plugin.register_output('fields_parser', self)
4
+
5
+ config_param :remove_tag_prefix, :string, :default => nil
6
+ config_param :add_tag_prefix, :string, :default => nil
7
+ config_param :parse_key, :string, :default => 'message'
8
+ config_param :fields_key, :string, :default => nil
9
+ config_param :pattern, :string,
10
+ :default => %{([a-zA-Z_]\\w*)=((['"]).*?(\\3)|[\\w.@$%/+-]*)}
11
+
12
+ def compiled_pattern
13
+ @compiled_pattern ||= Regexp.new(pattern)
14
+ end
15
+
16
+ def emit(tag, es, chain)
17
+ tag = update_tag(tag)
18
+ es.each { |time, record|
19
+ Engine.emit(tag, time, parse_fields(record))
20
+ }
21
+ chain.next
22
+ end
23
+
24
+ def update_tag(tag)
25
+ if remove_tag_prefix
26
+ if remove_tag_prefix == tag
27
+ tag = ''
28
+ elsif tag.to_s.start_with?(remove_tag_prefix+'.')
29
+ tag = tag[remove_tag_prefix.length+1 .. -1]
30
+ end
31
+ end
32
+ if add_tag_prefix
33
+ tag = tag && tag.length > 0 ? "#{add_tag_prefix}.#{tag}" : add_tag_prefix
34
+ end
35
+ return tag
36
+ end
37
+
38
+ def parse_fields(record)
39
+ source = record[parse_key].to_s
40
+ target = fields_key ? (record[fields_key] ||= {}) : record
41
+
42
+ source.scan(compiled_pattern) do |match|
43
+ (key, value, begining_quote, ending_quote) = match
44
+ next if key.nil?
45
+ next if target.has_key?(key)
46
+ value = value.to_s
47
+ from_pos = begining_quote.to_s.length
48
+ to_pos = value.length - ending_quote.to_s.length - 1
49
+ target[key] = value[from_pos..to_pos]
50
+ end
51
+ return record
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,234 @@
1
+ require 'fluent/test'
2
+ require 'fluent/plugin/out_fields_parser'
3
+
4
+ class FieldsParserOutputTest < 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::OutputFieldsParser, 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
+ 'null' => '',
34
+ },
35
+ emits[0][2]
36
+ )
37
+ end
38
+
39
+ def test_quoted_values
40
+ d = create_driver()
41
+
42
+ orig_message = %{blax dq="asd ' asd +3" sq='as " s " 4' s=yu 6}
43
+ d.run do
44
+ d.emit({
45
+ 'message' => orig_message,
46
+ })
47
+ end
48
+
49
+ emits = d.emits
50
+ assert_equal 1, emits.size
51
+ assert_equal "orig.test.tag", emits[0][0]
52
+ assert_equal(
53
+ {
54
+ 'message' => orig_message,
55
+ 'dq' => "asd ' asd +3",
56
+ 'sq' => 'as " s " 4',
57
+ 's' => 'yu'
58
+ },
59
+ emits[0][2]
60
+ )
61
+ end
62
+
63
+ def test_parsed_key_is_missing
64
+ d = create_driver()
65
+
66
+ d.run do
67
+ d.emit({})
68
+ end
69
+
70
+ emits = d.emits
71
+ assert_equal 1, emits.size
72
+ assert_equal "orig.test.tag", emits[0][0]
73
+ assert_equal(
74
+ {},
75
+ emits[0][2]
76
+ )
77
+ end
78
+
79
+ def test_existing_keys_are_not_overriden
80
+ d = create_driver()
81
+
82
+ orig_message = %{mock a=77 message=blax a=999 e=5}
83
+ d.run do
84
+ d.emit({'message' => orig_message, 'e' => nil })
85
+ end
86
+
87
+ emits = d.emits
88
+ assert_equal 1, emits.size
89
+ assert_equal "orig.test.tag", emits[0][0]
90
+ assert_equal(
91
+ {
92
+ 'message' => orig_message,
93
+ 'a' => '77',
94
+ 'e' => nil,
95
+ },
96
+ emits[0][2]
97
+ )
98
+ end
99
+
100
+ def test_tag_prefixes
101
+ d = create_driver(%{
102
+ remove_tag_prefix orig
103
+ add_tag_prefix new
104
+ })
105
+
106
+ d.run do
107
+ d.emit({ message => 'abc' })
108
+ end
109
+
110
+ emits = d.emits
111
+ assert_equal 1, emits.size
112
+ assert_equal "new.test.tag", emits[0][0]
113
+
114
+ d = create_driver(%{
115
+ remove_tag_prefix orig
116
+ add_tag_prefix new
117
+ }, tag=nil)
118
+
119
+ d.run do
120
+ d.emit({ message => 'abc' })
121
+ end
122
+
123
+ emits = d.emits
124
+ assert_equal 1, emits.size
125
+ assert_equal "new", emits[0][0]
126
+
127
+ d = create_driver(%{
128
+ remove_tag_prefix orig
129
+ add_tag_prefix new
130
+ }, tag='original')
131
+
132
+ d.run do
133
+ d.emit({ message => 'abc' })
134
+ end
135
+
136
+ emits = d.emits
137
+ assert_equal 1, emits.size
138
+ assert_equal "new.original", emits[0][0]
139
+
140
+ d = create_driver(%{
141
+ remove_tag_prefix orig
142
+ add_tag_prefix new
143
+ }, tag='orig')
144
+
145
+ d.run do
146
+ d.emit({ message => 'abc' })
147
+ end
148
+
149
+ emits = d.emits
150
+ assert_equal 1, emits.size
151
+ assert_equal "new", emits[0][0]
152
+ end
153
+
154
+ def test_parse_key
155
+ d = create_driver('parse_key custom_key')
156
+
157
+ d.run do
158
+ d.emit({
159
+ 'message' => %{ test2 c=d },
160
+ 'custom_key' => %{ test2 a=b },
161
+ })
162
+ d.emit({})
163
+ end
164
+
165
+ emits = d.emits
166
+ assert_equal 2, emits.size
167
+ assert_equal "orig.test.tag", emits[0][0]
168
+ assert_equal(
169
+ {
170
+ 'message' => %{ test2 c=d },
171
+ 'custom_key' => %{ test2 a=b },
172
+ 'a' => 'b'
173
+ },
174
+ emits[0][2]
175
+ )
176
+ assert_equal(
177
+ {
178
+ },
179
+ emits[1][2]
180
+ )
181
+ end
182
+
183
+ def test_fields_key
184
+ d = create_driver("fields_key output-key")
185
+
186
+ orig_message = %{parse this num=-56.7 tok=abc%25 message=a+b}
187
+ d.run do
188
+ d.emit({'message' => orig_message})
189
+ end
190
+
191
+ emits = d.emits
192
+ assert_equal 1, emits.size
193
+ assert_equal "orig.test.tag", emits[0][0]
194
+ assert_equal(
195
+ {
196
+ 'message' => orig_message,
197
+ 'output-key' => {
198
+ 'num' => '-56.7',
199
+ 'tok' => 'abc%25',
200
+ 'message' => 'a+b',
201
+ }
202
+ },
203
+ emits[0][2]
204
+ )
205
+ end
206
+
207
+ def test_custom_pattern
208
+ d = create_driver("pattern (\\w+):(\\d+)")
209
+
210
+ orig_message = %{parse this a:44 b:ignore-this h=7 bbb:999}
211
+ d.run do
212
+ d.emit({'message' => orig_message})
213
+ d.emit({'message' => 'a'})
214
+ end
215
+
216
+ emits = d.emits
217
+ assert_equal 2, emits.size
218
+ assert_equal "orig.test.tag", emits[0][0]
219
+ assert_equal(
220
+ {
221
+ 'message' => orig_message,
222
+ 'a' => '44',
223
+ 'bbb' => '999',
224
+ },
225
+ emits[0][2]
226
+ )
227
+ assert_equal(
228
+ {
229
+ 'message' => 'a',
230
+ },
231
+ emits[1][2]
232
+ )
233
+ end
234
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-fields-parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tomas Pokorny
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-07-07 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
+ email:
48
+ - tomas.zemres@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - README.md
55
+ - Rakefile
56
+ - VERSION
57
+ - fluent-plugin-tokenizer.gemspec
58
+ - lib/fluent/plugin/out_fields_parser.rb
59
+ - test/out_fields_parser.rb
60
+ homepage: https://github.com/tomas-zemres/fluent-plugin-fields-parser
61
+ licenses:
62
+ - MIT
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 1.8.23
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Fluent output filter plugin for parsing key/value fields in records
85
+ test_files: []