fluentd 0.10.44 → 0.10.45

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

@@ -15,6 +15,9 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+
19
+ require 'fluent/plugin/exec_util'
20
+
18
21
  module Fluent
19
22
  class ExecOutput < TimeSlicedOutput
20
23
  Plugin.register_output('exec', self)
@@ -26,41 +29,52 @@ module Fluent
26
29
  end
27
30
 
28
31
  config_param :command, :string
29
- config_param :keys, :string
32
+ config_param :keys, :default => [] do |val|
33
+ val.split(',')
34
+ end
30
35
  config_param :tag_key, :string, :default => nil
31
36
  config_param :time_key, :string, :default => nil
32
37
  config_param :time_format, :string, :default => nil
38
+ config_param :format, :default => :tsv do |val|
39
+ f = ExecUtil::SUPPORTED_FORMAT[val]
40
+ raise ConfigError, "Unsupported format '#{val}'" unless f
41
+ f
42
+ end
33
43
 
34
44
  def configure(conf)
35
45
  super
36
46
 
37
- @keys = @keys.split(',')
47
+ @formatter = case @format
48
+ when :tsv
49
+ if @keys.empty?
50
+ raise ConfigError, "keys option is required on exec output for tsv format"
51
+ end
52
+ ExecUtil::TSVFormatter.new(@keys)
53
+ when :json
54
+ ExecUtil::JSONFormatter.new
55
+ when :msgpack
56
+ ExecUtil::MessagePackFormatter.new
57
+ end
38
58
 
39
59
  if @time_key
40
60
  if @time_format
41
61
  tf = TimeFormatter.new(@time_format, @localtime)
42
62
  @time_format_proc = tf.method(:format)
43
63
  else
44
- @time_format_proc = Proc.new {|time| time.to_s }
64
+ @time_format_proc = Proc.new { |time| time.to_s }
45
65
  end
46
66
  end
47
67
  end
48
68
 
49
69
  def format(tag, time, record)
50
70
  out = ''
51
- last = @keys.length-1
52
- for i in 0..last
53
- key = @keys[i]
54
- if key == @time_key
55
- out << @time_format_proc.call(time)
56
- elsif key == @tag_key
57
- out << tag
58
- else
59
- out << record[key].to_s
60
- end
61
- out << "\t" if i != last
71
+ if @time_key
72
+ record[@time_key] = @time_format_proc.call(time)
73
+ end
74
+ if @tag_key
75
+ record[@tag_key] = tag
62
76
  end
63
- out << "\n"
77
+ @formatter.call(record, out)
64
78
  out
65
79
  end
66
80
 
@@ -15,28 +15,24 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+
19
+ require 'fluent/plugin/exec_util'
20
+
18
21
  module Fluent
19
22
  class ExecFilterOutput < BufferedOutput
20
23
  Plugin.register_output('exec_filter', self)
21
24
 
22
25
  def initialize
23
26
  super
24
- require 'fluent/plugin/exec_util'
25
27
  end
26
28
 
27
- SUPPORTED_FORMAT = {
28
- 'tsv' => :tsv,
29
- 'json' => :json,
30
- 'msgpack' => :msgpack,
31
- }
32
-
33
29
  config_param :command, :string
34
30
 
35
31
  config_param :remove_prefix, :string, :default => nil
36
32
  config_param :add_prefix, :string, :default => nil
37
33
 
38
34
  config_param :in_format, :default => :tsv do |val|
39
- f = SUPPORTED_FORMAT[val]
35
+ f = ExecUtil::SUPPORTED_FORMAT[val]
40
36
  raise ConfigError, "Unsupported in_format '#{val}'" unless f
41
37
  f
42
38
  end
@@ -48,7 +44,7 @@ module Fluent
48
44
  config_param :in_time_format, :default => nil
49
45
 
50
46
  config_param :out_format, :default => :tsv do |val|
51
- f = SUPPORTED_FORMAT[val]
47
+ f = ExecUtil::SUPPORTED_FORMAT[val]
52
48
  raise ConfigError, "Unsupported out_format '#{val}'" unless f
53
49
  f
54
50
  end
@@ -140,11 +136,11 @@ module Fluent
140
136
  if @in_keys.empty?
141
137
  raise ConfigError, "in_keys option is required on exec_filter output for tsv in_format"
142
138
  end
143
- @formatter = TSVFormatter.new(@in_keys)
139
+ @formatter = ExecUtil::TSVFormatter.new(@in_keys)
144
140
  when :json
145
- @formatter = JSONFormatter.new
141
+ @formatter = ExecUtil::JSONFormatter.new
146
142
  when :msgpack
147
- @formatter = MessagePackFormatter.new
143
+ @formatter = ExecUtil::MessagePackFormatter.new
148
144
  end
149
145
 
150
146
  case @out_format
@@ -333,38 +329,6 @@ module Fluent
333
329
  end
334
330
  end
335
331
 
336
- class Formatter
337
- end
338
-
339
- class TSVFormatter < Formatter
340
- def initialize(in_keys)
341
- @in_keys = in_keys
342
- super()
343
- end
344
-
345
- def call(record, out)
346
- last = @in_keys.length-1
347
- for i in 0..last
348
- key = @in_keys[i]
349
- out << record[key].to_s
350
- out << "\t" if i != last
351
- end
352
- out << "\n"
353
- end
354
- end
355
-
356
- class JSONFormatter < Formatter
357
- def call(record, out)
358
- out << Yajl.dump(record) << "\n"
359
- end
360
- end
361
-
362
- class MessagePackFormatter < Formatter
363
- def call(record, out)
364
- record.to_msgpack(out)
365
- end
366
- end
367
-
368
332
  def on_message(record)
369
333
  if val = record.delete(@out_time_key)
370
334
  time = @time_parse_proc.call(val)
@@ -1,5 +1,5 @@
1
1
  module Fluent
2
2
 
3
- VERSION = '0.10.44'
3
+ VERSION = '0.10.45'
4
4
 
5
5
  end
@@ -282,4 +282,85 @@ module ParserTest
282
282
  end
283
283
  end
284
284
 
285
+ class MultilineParserTest < ::Test::Unit::TestCase
286
+ include ParserTest
287
+
288
+ def create_parser(conf)
289
+ parser = TextParser::TEMPLATE_FACTORIES['multiline'].call
290
+ parser.configure(conf)
291
+ parser
292
+ end
293
+
294
+ def test_configure_with_invalid_params
295
+ [{'format100' => '/(?<msg>.*)/'}, {'format1' => '/(?<msg>.*)/', 'format3' => '/(?<msg>.*)/'}, 'format1' => '/(?<msg>.*)'].each { |config|
296
+ assert_raise(ConfigError) {
297
+ create_parser(config)
298
+ }
299
+ }
300
+ end
301
+
302
+ def test_call
303
+ parser = create_parser('format1' => '/^(?<time>\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}) \[(?<thread>.*)\] (?<level>[^\s]+)(?<message>.*)/')
304
+ time, record = parser.call(<<EOS.chomp)
305
+ 2013-3-03 14:27:33 [main] ERROR Main - Exception
306
+ javax.management.RuntimeErrorException: null
307
+ \tat Main.main(Main.java:16) ~[bin/:na]
308
+ EOS
309
+
310
+ assert_equal(str2time('2013-3-03 14:27:33').to_i, time)
311
+ assert_equal({
312
+ "thread" => "main",
313
+ "level" => "ERROR",
314
+ "message" => " Main - Exception\njavax.management.RuntimeErrorException: null\n\tat Main.main(Main.java:16) ~[bin/:na]"
315
+ }, record)
316
+ end
317
+
318
+ def test_call_with_firstline
319
+ parser = create_parser('format_firstline' => '/----/', 'format1' => '/time=(?<time>\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}).*message=(?<message>.*)/')
320
+ time, record = parser.call(<<EOS.chomp)
321
+ ----
322
+ time=2013-3-03 14:27:33
323
+ message=test1
324
+ EOS
325
+
326
+ assert(parser.firstline?('----'))
327
+ assert_equal(str2time('2013-3-03 14:27:33').to_i, time)
328
+ assert_equal({"message" => "test1"}, record)
329
+ end
330
+
331
+ def test_call_with_multiple_formats
332
+ parser = create_parser('format_firstline' => '/^Started/',
333
+ 'format1' => '/Started (?<method>[^ ]+) "(?<path>[^"]+)" for (?<host>[^ ]+) at (?<time>[^ ]+ [^ ]+ [^ ]+)\n/',
334
+ 'format2' => '/Processing by (?<controller>[^\u0023]+)\u0023(?<controller_method>[^ ]+) as (?<format>[^ ]+?)\n/',
335
+ 'format3' => '/( Parameters: (?<parameters>[^ ]+)\n)?/',
336
+ 'format4' => '/ Rendered (?<template>[^ ]+) within (?<layout>.+) \([\d\.]+ms\)\n/',
337
+ 'format5' => '/Completed (?<code>[^ ]+) [^ ]+ in (?<runtime>[\d\.]+)ms \(Views: (?<view_runtime>[\d\.]+)ms \| ActiveRecord: (?<ar_runtime>[\d\.]+)ms\)/'
338
+ )
339
+ time, record = parser.call(<<EOS.chomp)
340
+ Started GET "/users/123/" for 127.0.0.1 at 2013-06-14 12:00:11 +0900
341
+ Processing by UsersController#show as HTML
342
+ Parameters: {"user_id"=>"123"}
343
+ Rendered users/show.html.erb within layouts/application (0.3ms)
344
+ Completed 200 OK in 4ms (Views: 3.2ms | ActiveRecord: 0.0ms)
345
+ EOS
346
+
347
+ assert(parser.firstline?('Started GET "/users/123/" for 127.0.0.1...'))
348
+ assert_equal(str2time('2013-06-14 12:00:11 +0900').to_i, time)
349
+ assert_equal({
350
+ "method" => "GET",
351
+ "path" => "/users/123/",
352
+ "host" => "127.0.0.1",
353
+ "controller" => "UsersController",
354
+ "controller_method" => "show",
355
+ "format" => "HTML",
356
+ "parameters" => "{\"user_id\"=>\"123\"}",
357
+ "template" => "users/show.html.erb",
358
+ "layout" => "layouts/application",
359
+ "code" => "200",
360
+ "runtime" => "4",
361
+ "view_runtime" => "3.2",
362
+ "ar_runtime" => "0.0"
363
+ }, record)
364
+ end
365
+ end
285
366
  end
File without changes
File without changes
File without changes
File without changes
@@ -1,7 +1,10 @@
1
1
  require 'fluent/test'
2
2
  require 'net/http'
3
+ require 'flexmock'
3
4
 
4
5
  class TailInputTest < Test::Unit::TestCase
6
+ include FlexMock::TestCase
7
+
5
8
  def setup
6
9
  Fluent::Test.setup
7
10
  FileUtils.rm_rf(TMP_DIR)
@@ -10,16 +13,19 @@ class TailInputTest < Test::Unit::TestCase
10
13
 
11
14
  TMP_DIR = File.dirname(__FILE__) + "/../tmp/tail#{ENV['TEST_ENV_NUMBER']}"
12
15
 
13
- CONFIG = %[
16
+ COMMON_CONFIG = %[
14
17
  path #{TMP_DIR}/tail.txt
15
18
  tag t1
16
19
  rotate_wait 2s
17
20
  pos_file #{TMP_DIR}/tail.pos
21
+ ]
22
+ SINGLE_LINE_CONFIG = %[
18
23
  format /(?<message>.*)/
19
24
  ]
20
25
 
21
- def create_driver(conf=CONFIG)
22
- Fluent::Test::InputTestDriver.new(Fluent::TailInput).configure(conf)
26
+ def create_driver(conf = SINGLE_LINE_CONFIG, use_common_conf = true)
27
+ config = use_common_conf ? COMMON_CONFIG + conf : conf
28
+ Fluent::Test::InputTestDriver.new(Fluent::NewTailInput).configure(config)
23
29
  end
24
30
 
25
31
  def test_configure
@@ -104,4 +110,231 @@ class TailInputTest < Test::Unit::TestCase
104
110
  assert_equal({"message"=>" tab"}, emits[4][2])
105
111
  assert_equal({"message"=>"tab "}, emits[5][2])
106
112
  end
113
+
114
+ # multiline mode test
115
+
116
+ def test_multiline
117
+ File.open("#{TMP_DIR}/tail.txt", "w") { |f| }
118
+
119
+ d = create_driver %[
120
+ format multiline
121
+ format1 /^s (?<message1>[^\\n]+)(\\nf (?<message2>[^\\n]+))?(\\nf (?<message3>.*))?/
122
+ format_firstline /^[s]/
123
+ ]
124
+ d.run do
125
+ File.open("#{TMP_DIR}/tail.txt", "a") { |f|
126
+ f.puts "f test1"
127
+ f.puts "s test2"
128
+ f.puts "f test3"
129
+ f.puts "f test4"
130
+ f.puts "s test5"
131
+ f.puts "s test6"
132
+ f.puts "f test7"
133
+ f.puts "s test8"
134
+ }
135
+ sleep 1
136
+ end
137
+
138
+ emits = d.emits
139
+ assert(emits.length > 0)
140
+ assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, emits[0][2])
141
+ assert_equal({"message1" => "test5"}, emits[1][2])
142
+ assert_equal({"message1" => "test6", "message2" => "test7"}, emits[2][2])
143
+ assert_equal({"message1" => "test8"}, emits[3][2])
144
+ end
145
+
146
+ def test_multiline_with_multiple_formats
147
+ File.open("#{TMP_DIR}/tail.txt", "w") { |f| }
148
+
149
+ d = create_driver %[
150
+ format multiline
151
+ format1 /^s (?<message1>[^\\n]+)\\n?/
152
+ format2 /(f (?<message2>[^\\n]+)\\n?)?/
153
+ format3 /(f (?<message3>.*))?/
154
+ format_firstline /^[s]/
155
+ ]
156
+ d.run do
157
+ File.open("#{TMP_DIR}/tail.txt", "a") { |f|
158
+ f.puts "f test1"
159
+ f.puts "s test2"
160
+ f.puts "f test3"
161
+ f.puts "f test4"
162
+ f.puts "s test5"
163
+ f.puts "s test6"
164
+ f.puts "f test7"
165
+ f.puts "s test8"
166
+ }
167
+ sleep 1
168
+ end
169
+
170
+ emits = d.emits
171
+ assert(emits.length > 0)
172
+ assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, emits[0][2])
173
+ assert_equal({"message1" => "test5"}, emits[1][2])
174
+ assert_equal({"message1" => "test6", "message2" => "test7"}, emits[2][2])
175
+ assert_equal({"message1" => "test8"}, emits[3][2])
176
+ end
177
+
178
+ def test_multilinelog_with_multiple_paths
179
+ files = ["#{TMP_DIR}/tail1.txt", "#{TMP_DIR}/tail2.txt"]
180
+ files.each { |file| File.open(file, "w") { |f| } }
181
+
182
+ d = create_driver(%[
183
+ path #{files[0]},#{files[1]}
184
+ tag t1
185
+ format multiline
186
+ format1 /^[s|f] (?<message>.*)/
187
+ format_firstline /^[s]/
188
+ ], false)
189
+ d.run do
190
+ files.each do |file|
191
+ File.open(file, 'a') { |f|
192
+ f.puts "f #{file} line should be ignored"
193
+ f.puts "s test1"
194
+ f.puts "f test2"
195
+ f.puts "f test3"
196
+ f.puts "s test4"
197
+ }
198
+ end
199
+ sleep 1
200
+ end
201
+
202
+ emits = d.emits
203
+ assert_equal({"message" => "test1\nf test2\nf test3"}, emits[0][2])
204
+ assert_equal({"message" => "test1\nf test2\nf test3"}, emits[1][2])
205
+ # "test4" events are here because these events are flushed at shutdown phase
206
+ assert_equal({"message" => "test4"}, emits[2][2])
207
+ assert_equal({"message" => "test4"}, emits[3][2])
208
+ end
209
+
210
+ # * path test
211
+ # TODO: Clean up tests
212
+ EX_RORATE_WAIT = 0
213
+
214
+ EX_CONFIG = %[
215
+ tag tail
216
+ path test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log
217
+ format none
218
+ pos_file #{TMP_DIR}/tail.pos
219
+ read_from_head true
220
+ refresh_interval 30
221
+ rotate_wait #{EX_RORATE_WAIT}s
222
+ ]
223
+ EX_PATHS = [
224
+ 'test/plugin/data/2010/01/20100102-030405.log',
225
+ 'test/plugin/data/log/foo/bar.log',
226
+ 'test/plugin/data/log/test.log'
227
+ ]
228
+
229
+ def test_expand_paths
230
+ plugin = create_driver(EX_CONFIG, false).instance
231
+ flexstub(Time) do |timeclass|
232
+ timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 2, 3, 4, 5))
233
+ assert_equal EX_PATHS, plugin.expand_paths.sort
234
+ end
235
+ end
236
+
237
+ def test_refresh_watchers
238
+ plugin = create_driver(EX_CONFIG, false).instance
239
+ sio = StringIO.new
240
+ plugin.instance_eval do
241
+ @pf = Fluent::NewTailInput::PositionFile.parse(sio)
242
+ @loop = Coolio::Loop.new
243
+ end
244
+
245
+ flexstub(Time) do |timeclass|
246
+ timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 2, 3, 4, 5), Time.new(2010, 1, 2, 3, 4, 6), Time.new(2010, 1, 2, 3, 4, 7))
247
+
248
+ flexstub(Fluent::NewTailInput::TailWatcher) do |watcherclass|
249
+ EX_PATHS.each do |path|
250
+ watcherclass.should_receive(:new).with(path, EX_RORATE_WAIT, Fluent::NewTailInput::FilePositionEntry, any, any, any).once.and_return do
251
+ flexmock('TailWatcher') { |watcher|
252
+ watcher.should_receive(:attach).once
253
+ watcher.should_receive(:unwatched=).zero_or_more_times
254
+ watcher.should_receive(:line_buffer).zero_or_more_times
255
+ }
256
+ end
257
+ end
258
+ plugin.refresh_watchers
259
+ end
260
+
261
+ plugin.instance_eval do
262
+ @tails['test/plugin/data/2010/01/20100102-030405.log'].should_receive(:close).zero_or_more_times
263
+ end
264
+
265
+ flexstub(Fluent::NewTailInput::TailWatcher) do |watcherclass|
266
+ watcherclass.should_receive(:new).with('test/plugin/data/2010/01/20100102-030406.log', EX_RORATE_WAIT, Fluent::NewTailInput::FilePositionEntry, any, any, any).once.and_return do
267
+ flexmock('TailWatcher') do |watcher|
268
+ watcher.should_receive(:attach).once
269
+ watcher.should_receive(:unwatched=).zero_or_more_times
270
+ watcher.should_receive(:line_buffer).zero_or_more_times
271
+ end
272
+ end
273
+ plugin.refresh_watchers
274
+ end
275
+
276
+ flexstub(Fluent::NewTailInput::TailWatcher) do |watcherclass|
277
+ watcherclass.should_receive(:new).never
278
+ plugin.refresh_watchers
279
+ end
280
+ end
281
+ end
282
+
283
+ DummyWatcher = Struct.new("DummyWatcher", :tag)
284
+
285
+ def test_receive_lines
286
+ plugin = create_driver(EX_CONFIG, false).instance
287
+ flexstub(Fluent::Engine) do |engineclass|
288
+ engineclass.should_receive(:emit_stream).with('tail', any).once
289
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
290
+ end
291
+
292
+ config = %[
293
+ tag pre.*
294
+ path test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log
295
+ format none
296
+ read_from_head true
297
+ ]
298
+ plugin = create_driver(config, false).instance
299
+ flexstub(Fluent::Engine) do |engineclass|
300
+ engineclass.should_receive(:emit_stream).with('pre.foo.bar.log', any).once
301
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
302
+ end
303
+
304
+ config = %[
305
+ tag *.post
306
+ path test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log
307
+ format none
308
+ read_from_head true
309
+ ]
310
+ plugin = create_driver(config, false).instance
311
+ flexstub(Fluent::Engine) do |engineclass|
312
+ engineclass.should_receive(:emit_stream).with('foo.bar.log.post', any).once
313
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
314
+ end
315
+
316
+ config = %[
317
+ tag pre.*.post
318
+ path test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log
319
+ format none
320
+ read_from_head true
321
+ ]
322
+ plugin = create_driver(config, false).instance
323
+ flexstub(Fluent::Engine) do |engineclass|
324
+ engineclass.should_receive(:emit_stream).with('pre.foo.bar.log.post', any).once
325
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
326
+ end
327
+
328
+ config = %[
329
+ tag pre.*.post*ignore
330
+ path test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log
331
+ format none
332
+ read_from_head true
333
+ ]
334
+ plugin = create_driver(config, false).instance
335
+ flexstub(Fluent::Engine) do |engineclass|
336
+ engineclass.should_receive(:emit_stream).with('pre.foo.bar.log.post', any).once
337
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
338
+ end
339
+ end
107
340
  end