fluentd 0.10.38 → 0.10.39

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7dd0061dacd1f7c9ea0cbd4852cfa656a913f71b
4
- data.tar.gz: f73b52509cdc043da1a07ed0f46bcd0e36ddf820
3
+ metadata.gz: 14f93577d21ab248d08466b142442d503ba9ad55
4
+ data.tar.gz: 9036d89bf55c57a454120dc766c3da6fe445fbf8
5
5
  SHA512:
6
- metadata.gz: cf4231745ae0f5afe243a2e41901445575b63e0dd7fc91a532faa9416a73a3681a2e9282b06e429d2c35ad4b2a8c6782938ceb2ff18a5efd2bdc0e31d8f204d7
7
- data.tar.gz: 02f39e0e614f08bbb860fad4849cc0e6c92aaea20b0e0151bb7ba2c09515374181570359cc0e931a391a5aab52496cab8834b9a3edef1283601a65159fb5f5e8
6
+ metadata.gz: 5c4ae2365e4e3a59cc22e7089203ea57b3cf7bc89d759915e4f02b795312903116484dd65f43a7e07ed9e98a1da13f5e1aa6112a0448d03706e4171cd3dad63a
7
+ data.tar.gz: 806d690bccc9674f2bd45d693eb523a07e06d0aa4e2dc63e42396421d34ce8f27977bba119b0e5470d8844ee62c140cd1cf8c4bff1b8ce2aa16bd8d40e7fb419
@@ -11,3 +11,7 @@ branches:
11
11
  - v11
12
12
 
13
13
  script: bundle exec rake
14
+
15
+ matrix:
16
+ allow_failures:
17
+ - rvm: rbx-19mode
data/ChangeLog CHANGED
@@ -1,3 +1,13 @@
1
+ Release 0.10.39 - 2013/09/18
2
+
3
+ * out_file: Improve symlink handling with buf_file
4
+ * out_copy: Add deep_copy parameter for actual record copy
5
+ * in_tail/parser: Add none format for supporting non-parse tailing like fluent-agent-lite
6
+ * in_tail/parser: Improve performance using parsed time caching
7
+ * Engine: Fix signal related exception in trap context at Ruby 2.0
8
+ * buffer: Fix race condition when remove flushed chunk
9
+ * add --suppress-config-dump option to disable dump configuration at start
10
+
1
11
  Release 0.10.38 - 2013/08/29
2
12
 
3
13
  * Fix require related problem in running test
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
3
 
4
+ require 'fileutils'
4
5
  require 'rake/testtask'
5
6
  require 'rake/clean'
6
7
 
@@ -13,4 +14,10 @@ Rake::TestTask.new(:base_test) do |t|
13
14
  #t.warning = true
14
15
  end
15
16
 
17
+ task :parallel_test do
18
+ FileUtils.rm_rf('./test/tmp')
19
+ sh("parallel_test ./test/*.rb ./test/plugin/*.rb")
20
+ FileUtils.rm_rf('./test/tmp')
21
+ end
22
+
16
23
  task :default => [:test, :build]
@@ -25,6 +25,7 @@ Gem::Specification.new do |gem|
25
25
  gem.add_runtime_dependency(%q<http_parser.rb>, ["~> 0.5.1"])
26
26
 
27
27
  gem.add_development_dependency(%q<rake>, [">= 0.9.2"])
28
+ gem.add_development_dependency(%q<parallel_tests>, [">= 0.15.3"])
28
29
  gem.add_development_dependency(%q<rr>, [">= 1.0.0"])
29
30
  gem.add_development_dependency(%q<timecop>, [">= 0.3.0"])
30
31
  end
@@ -272,13 +272,17 @@ module Fluent
272
272
  write_chunk(chunk, out)
273
273
  end
274
274
 
275
- @queue.delete_if {|c|
276
- c.object_id == chunk.object_id
277
- }
275
+ empty = false
276
+ @queue.synchronize do
277
+ @queue.delete_if {|c|
278
+ c.object_id == chunk.object_id
279
+ }
280
+ empty = @queue.empty?
281
+ end
278
282
 
279
283
  chunk.purge
280
284
 
281
- return !@queue.empty?
285
+ return !empty
282
286
  ensure
283
287
  chunk.mon_exit
284
288
  end
@@ -98,6 +98,10 @@ op.on('-q', '--quiet', "decrease verbose level (-q: warn, -qq: error)", TrueClas
98
98
  end
99
99
  }
100
100
 
101
+ op.on('--suppress-config-dump', "suppress config dumping when fluentd starts", TrueClass) {|b|
102
+ opts[:suppress_config_dump] = b
103
+ }
104
+
101
105
  (class<<self;self;end).module_eval do
102
106
  define_method(:usage) do |msg|
103
107
  puts op.to_s
@@ -3,7 +3,7 @@ require 'fluent/config'
3
3
  module Fluent
4
4
  module Config
5
5
  module DSL
6
- module DSLParser
6
+ module Parser
7
7
  def self.read(path)
8
8
  path = File.expand_path(path)
9
9
  data = File.read(path)
@@ -11,62 +11,75 @@ module Fluent
11
11
  end
12
12
 
13
13
  def self.parse(source, source_path="config.rb")
14
- DSLElement.new('ROOT', nil).__eval(source, source_path).__to_config_element
14
+ Proxy.new('ROOT', nil).eval(source, source_path).to_config_element
15
15
  end
16
16
  end
17
17
 
18
- class DSLElement < BasicObject
18
+ class Proxy
19
19
  def initialize(name, arg)
20
- @name = name
21
- @arg = arg || ''
22
- @attrs = {}
23
- @elements = []
20
+ @element = Element.new(name, arg, self)
21
+ end
22
+
23
+ def element
24
+ @element
24
25
  end
25
26
 
26
- def __eval(source, source_path)
27
- instance_eval(source, source_path)
27
+ def eval(source, source_path)
28
+ @element.instance_eval(source, source_path)
28
29
  self
29
30
  end
30
31
 
31
- def __to_config_element
32
- Config::Element.new(@name, @arg, @attrs, @elements)
32
+ def to_config_element
33
+ @element.instance_eval do
34
+ Config::Element.new(@name, @arg, @attrs, @elements)
35
+ end
36
+ end
37
+
38
+ def add_element(name, arg, block)
39
+ ::Kernel.raise ::ArgumentError, "#{name} block must be specified" if block.nil?
40
+
41
+ proxy = self.class.new(name.to_s, arg)
42
+ proxy.element.instance_exec(&block)
43
+
44
+ @element.instance_eval do
45
+ @elements.push(proxy.to_config_element)
46
+ end
47
+
48
+ self
49
+ end
50
+ end
51
+
52
+ class Element < BasicObject
53
+ def initialize(name, arg, proxy)
54
+ @name = name
55
+ @arg = arg || ''
56
+ @attrs = {}
57
+ @elements = []
58
+ @proxy = proxy
33
59
  end
34
60
 
35
61
  def method_missing(name, *args, &block)
36
- raise ArgumentError, "Configuration DSL Syntax Error: only one argument allowed" if args.size > 1
62
+ ::Kernel.raise ::ArgumentError, "Configuration DSL Syntax Error: only one argument allowed" if args.size > 1
37
63
  value = args.first
64
+
38
65
  if block
39
- element = DSLElement.new(name.to_s, value)
40
- element.instance_exec(&block)
41
- @elements.push(element.__to_config_element)
66
+ proxy = Proxy.new(name.to_s, value)
67
+ proxy.element.instance_exec(&block)
68
+ @elements.push(proxy.to_config_element)
42
69
  else
43
- # @attrs[name.to_s] = value.is_a?(::Symbol) ? value.to_s : value
44
70
  @attrs[name.to_s] = value.to_s
45
71
  end
72
+
46
73
  self
47
74
  end
48
75
 
49
76
  def source(&block)
50
- __element('source', nil, block)
77
+ @proxy.add_element('source', nil, block)
51
78
  end
52
79
 
53
80
  def match(*args, &block)
54
- __need_arg('match', args); __element('match', args.first, block)
55
- end
56
-
57
- private
58
-
59
- def __element(name, arg, block)
60
- raise ArgumentError, "#{name} block must be specified" if block.nil?
61
- element = DSLElement.new(name.to_s, arg)
62
- element.instance_exec(&block)
63
- @elements.push(element.__to_config_element)
64
- self
65
- end
66
-
67
- def __need_arg(name, args)
68
- raise ArgumentError, "#{name} block requires arguments for match pattern" if args.nil? || args.size != 1
69
- true
81
+ ::Kernel.raise ::ArgumentError, "#{name} block requires arguments for match pattern" if args.nil? || args.size != 1
82
+ @proxy.add_element('match', args.first, block)
70
83
  end
71
84
  end
72
85
  end
@@ -31,6 +31,8 @@ module Fluent
31
31
 
32
32
  @suppress_emit_error_log_interval = 0
33
33
  @next_emit_error_log_time = nil
34
+
35
+ @suppress_config_dump = false
34
36
  end
35
37
 
36
38
  MATCH_CACHE_SIZE = 1024
@@ -54,6 +56,10 @@ module Fluent
54
56
  @next_emit_error_log_time = Time.now.to_i
55
57
  end
56
58
 
59
+ def suppress_config_dump=(flag)
60
+ @suppress_config_dump = flag
61
+ end
62
+
57
63
  def read_config(path)
58
64
  $log.info "reading config file", :path=>path
59
65
  File.open(path) {|io|
@@ -63,7 +69,7 @@ module Fluent
63
69
 
64
70
  def parse_config(io, fname, basepath=Dir.pwd)
65
71
  conf = if fname =~ /\.rb$/
66
- Config::DSL::DSLParser.parse(io, fname)
72
+ Config::DSL::Parser.parse(io, File.join(basepath, fname))
67
73
  else
68
74
  Config.parse(io, fname, basepath)
69
75
  end
@@ -74,7 +80,9 @@ module Fluent
74
80
  end
75
81
 
76
82
  def configure(conf)
77
- $log.info "using configuration file: #{conf.to_s.rstrip}"
83
+ unless @suppress_config_dump
84
+ $log.info "using configuration file: #{conf.to_s.rstrip}"
85
+ end
78
86
 
79
87
  conf.elements.select {|e|
80
88
  e.name == 'source'
@@ -42,6 +42,10 @@ module Fluent
42
42
  @record = record
43
43
  end
44
44
 
45
+ def dup
46
+ OneEventStream.new(@time, @record.dup)
47
+ end
48
+
45
49
  def repeatable?
46
50
  true
47
51
  end
@@ -58,6 +62,11 @@ module Fluent
58
62
  @entries = entries
59
63
  end
60
64
 
65
+ def dup
66
+ entries = @entries.map(:dup)
67
+ ArrayEventStream.new(entries)
68
+ end
69
+
61
70
  def repeatable?
62
71
  true
63
72
  end
@@ -85,6 +94,14 @@ module Fluent
85
94
  @record_array = []
86
95
  end
87
96
 
97
+ def dup
98
+ es = MultiEventStream.new
99
+ @time_array.zip(@record_array).each { |time, record|
100
+ es.add(time, record.dup)
101
+ }
102
+ es
103
+ end
104
+
88
105
  def add(time, record)
89
106
  @time_array << time
90
107
  @record_array << record
@@ -35,6 +35,18 @@ module Fluent
35
35
  end
36
36
  end
37
37
 
38
+ class CopyOutputChain < OutputChain
39
+ def next
40
+ if @array.length <= @offset
41
+ return @chain.next
42
+ end
43
+ @offset += 1
44
+ es = @array.length > @offset ? @es.dup : @es
45
+ result = @array[@offset-1].emit(@tag, es, self)
46
+ result
47
+ end
48
+ end
49
+
38
50
  class NullOutputChain
39
51
  require 'singleton'
40
52
  include Singleton
@@ -17,6 +17,40 @@
17
17
  #
18
18
  module Fluent
19
19
  class TextParser
20
+ class TimeParser
21
+ def initialize(time_format)
22
+ @cache1_key = nil
23
+ @cache1_time = nil
24
+ @cache2_key = nil
25
+ @cache2_time = nil
26
+ @parser =
27
+ if time_format
28
+ Proc.new { |value| Time.strptime(value, time_format) }
29
+ else
30
+ Time.method(:parse)
31
+ end
32
+ end
33
+
34
+ def parse(value)
35
+ unless value.is_a?(String)
36
+ raise ArgumentError, "Value must be string: #{value}"
37
+ end
38
+
39
+ if @cache1_key == value
40
+ return @cache1_time
41
+ elsif @cache2_key == value
42
+ return @cache2_time
43
+ else
44
+ time = @parser.call(value).to_i
45
+ @cache1_key = @cache2_key
46
+ @cache1_time = @cache2_time
47
+ @cache2_key = value
48
+ @cache2_time = time
49
+ return time
50
+ end
51
+ end
52
+ end
53
+
20
54
  class RegexpParser
21
55
  include Configurable
22
56
 
@@ -28,6 +62,9 @@ module Fluent
28
62
  unless conf.empty?
29
63
  configure(conf)
30
64
  end
65
+
66
+ @time_parser = TimeParser.new(@time_format)
67
+ @mutex = Mutex.new
31
68
  end
32
69
 
33
70
  def call(text)
@@ -44,11 +81,7 @@ module Fluent
44
81
  if value = m[name]
45
82
  case name
46
83
  when "time"
47
- if @time_format
48
- time = Time.strptime(value, @time_format).to_i
49
- else
50
- time = Time.parse(value).to_i
51
- end
84
+ time = @mutex.synchronize { @time_parser.parse(value) }
52
85
  else
53
86
  record[name] = value
54
87
  end
@@ -67,12 +100,21 @@ module Fluent
67
100
  config_param :time_key, :string, :default => 'time'
68
101
  config_param :time_format, :string, :default => nil
69
102
 
103
+ def configure(conf)
104
+ super
105
+
106
+ unless @time_format.nil?
107
+ @time_parser = TimeParser.new(@time_format)
108
+ @mutex = Mutex.new
109
+ end
110
+ end
111
+
70
112
  def call(text)
71
113
  record = Yajl.load(text)
72
114
 
73
115
  if value = record.delete(@time_key)
74
116
  if @time_format
75
- time = Time.strptime(value, @time_format).to_i
117
+ time = @mutex.synchronize { @time_parser.parse(value) }
76
118
  else
77
119
  time = value.to_i
78
120
  end
@@ -106,6 +148,9 @@ module Fluent
106
148
  if @time_format && !@time_key
107
149
  raise ConfigError, "time_format parameter is ignored because time_key parameter is not set. at #{conf.inspect}"
108
150
  end
151
+
152
+ @time_parser = TimeParser.new(@time_format)
153
+ @mutex = Mutex.new
109
154
  end
110
155
 
111
156
  def values_map(values)
@@ -113,11 +158,7 @@ module Fluent
113
158
 
114
159
  if @time_key
115
160
  value = record.delete(@time_key)
116
- if @time_format
117
- time = Time.strptime(value, @time_format).to_i
118
- else
119
- time = Time.parse(value).to_i
120
- end
161
+ time = @mutex.synchronize { @time_parser.parse(value) }
121
162
  else
122
163
  time = Engine.now
123
164
  end
@@ -169,11 +210,28 @@ module Fluent
169
210
  end
170
211
  end
171
212
 
213
+ class NoneParser
214
+ include Configurable
215
+
216
+ config_param :message_key, :string, :default => 'message'
217
+
218
+ def call(text)
219
+ record = {}
220
+ record[@message_key] = text
221
+ return Engine.now, record
222
+ end
223
+ end
224
+
172
225
  class ApacheParser
173
226
  include Configurable
174
227
 
175
228
  REGEXP = /^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
176
229
 
230
+ def initialize
231
+ @time_parser = TimeParser.new("%d/%b/%Y:%H:%M:%S %z")
232
+ @mutex = Mutex.new
233
+ end
234
+
177
235
  def call(text)
178
236
  m = REGEXP.match(text)
179
237
  unless m
@@ -188,7 +246,7 @@ module Fluent
188
246
  user = (user == '-') ? nil : user
189
247
 
190
248
  time = m['time']
191
- time = Time.strptime(time, "%d/%b/%Y:%H:%M:%S %z").to_i
249
+ time = @mutex.synchronize { @time_parser.parse(time) }
192
250
 
193
251
  method = m['method']
194
252
  path = m['path']
@@ -229,6 +287,7 @@ module Fluent
229
287
  'ltsv' => Proc.new { LabeledTSVParser.new },
230
288
  'csv' => Proc.new { CSVParser.new },
231
289
  'nginx' => Proc.new { RegexpParser.new(/^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/, {'time_format'=>"%d/%b/%Y:%H:%M:%S %z"}) },
290
+ 'none' => Proc.new { NoneParser.new },
232
291
  }
233
292
 
234
293
  def self.register_template(name, regexp_or_proc, time_format=nil)
@@ -17,13 +17,14 @@
17
17
  #
18
18
  module Fluent
19
19
  class FileBufferChunk < BufferChunk
20
- def initialize(key, path, unique_id, mode="a+")
20
+ def initialize(key, path, unique_id, mode="a+", symlink_path = nil)
21
21
  super(key)
22
22
  @path = path
23
23
  @unique_id = unique_id
24
24
  @file = File.open(@path, mode, DEFAULT_FILE_PERMISSION)
25
25
  @file.sync = true
26
26
  @size = @file.stat.size
27
+ FileUtils.ln_sf(@path, symlink_path) if symlink_path
27
28
  end
28
29
 
29
30
  attr_reader :unique_id
@@ -84,6 +85,8 @@ module Fluent
84
85
 
85
86
  config_param :buffer_path, :string
86
87
 
88
+ attr_accessor :symlink_path
89
+
87
90
  def configure(conf)
88
91
  super
89
92
 
@@ -119,7 +122,7 @@ module Fluent
119
122
  encoded_key = encode_key(key)
120
123
  path, tsuffix = make_path(encoded_key, "b")
121
124
  unique_id = tsuffix_to_unique_id(tsuffix)
122
- FileBufferChunk.new(key, path, unique_id)
125
+ FileBufferChunk.new(key, path, unique_id, "a+", @symlink_path)
123
126
  end
124
127
 
125
128
  def resume
@@ -39,11 +39,7 @@ module Fluent
39
39
  raise ConfigError, "tail: 'path' parameter is required on tail input"
40
40
  end
41
41
 
42
- if @pos_file
43
- @pf_file = File.open(@pos_file, File::RDWR|File::CREAT, DEFAULT_FILE_PERMISSION)
44
- @pf_file.sync = true
45
- @pf = PositionFile.parse(@pf_file)
46
- else
42
+ unless @pos_file
47
43
  $log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
48
44
  $log.warn "this parameter is highly recommended to save the position to resume tailing."
49
45
  end
@@ -57,6 +53,12 @@ module Fluent
57
53
  end
58
54
 
59
55
  def start
56
+ if @pos_file
57
+ @pf_file = File.open(@pos_file, File::RDWR|File::CREAT, DEFAULT_FILE_PERMISSION)
58
+ @pf_file.sync = true
59
+ @pf = PositionFile.parse(@pf_file)
60
+ end
61
+
60
62
  @loop = Coolio::Loop.new
61
63
  @tails = @paths.map {|path|
62
64
  pe = @pf ? @pf[path] : MemoryPositionEntry.new
@@ -19,13 +19,17 @@ module Fluent
19
19
  class CopyOutput < MultiOutput
20
20
  Plugin.register_output('copy', self)
21
21
 
22
+ config_param :deep_copy, :bool, :default => false
23
+
22
24
  def initialize
25
+ super
23
26
  @outputs = []
24
27
  end
25
28
 
26
29
  attr_reader :outputs
27
30
 
28
31
  def configure(conf)
32
+ super
29
33
  conf.elements.select {|e|
30
34
  e.name == 'store'
31
35
  }.each {|e|
@@ -61,7 +65,11 @@ module Fluent
61
65
  }
62
66
  es = m
63
67
  end
64
- chain = OutputChain.new(@outputs, tag, es, chain)
68
+ if @deep_copy
69
+ chain = CopyOutputChain.new(@outputs, tag, es, chain)
70
+ else
71
+ chain = OutputChain.new(@outputs, tag, es, chain)
72
+ end
65
73
  chain.next
66
74
  end
67
75
  end
@@ -65,6 +65,8 @@ module Fluent
65
65
  super
66
66
 
67
67
  @timef = TimeFormatter.new(@time_format, @localtime)
68
+
69
+ @buffer.symlink_path = @symlink_path if @symlink_path
68
70
  end
69
71
 
70
72
  def format(tag, time, record)
@@ -97,7 +99,6 @@ module Fluent
97
99
  chunk.write_to(f)
98
100
  }
99
101
  end
100
- create_symlink(path, suffix) if @symlink_path
101
102
 
102
103
  return path # for test
103
104
  end
@@ -105,11 +106,5 @@ module Fluent
105
106
  def secondary_init(primary)
106
107
  # don't warn even if primary.class is not FileOutput
107
108
  end
108
-
109
- private
110
-
111
- def create_symlink(path, suffix)
112
- FileUtils.ln_sf(path, "#{@symlink_path}#{suffix}")
113
- end
114
109
  end
115
110
  end
@@ -67,6 +67,7 @@ module Fluent
67
67
  @inline_config = opt[:inline_config]
68
68
  @suppress_interval = opt[:suppress_interval]
69
69
  @dry_run = opt[:dry_run]
70
+ @suppress_config_dump = opt[:suppress_config_dump]
70
71
 
71
72
  @log = LoggerInitializer.new(@log_path, @log_level, @chuser, @chgroup)
72
73
  @finished = false
@@ -320,6 +321,8 @@ module Fluent
320
321
  Fluent::Engine.suppress_interval(@suppress_interval)
321
322
  end
322
323
 
324
+ Fluent::Engine.suppress_config_dump = @suppress_config_dump
325
+
323
326
  @libs.each {|lib|
324
327
  require lib
325
328
  }
@@ -363,7 +366,17 @@ module Fluent
363
366
  $log.debug "fluentd main process get SIGUSR1"
364
367
  $log.info "force flushing buffered events"
365
368
  @log.reopen!
366
- Fluent::Engine.flush!
369
+
370
+ # Creating new thread due to mutex can't lock
371
+ # in main thread during trap context
372
+ Thread.new {
373
+ begin
374
+ Fluent::Engine.flush!
375
+ $log.debug "flushing thread: flushed"
376
+ rescue Exception => e
377
+ $log.warn "flushing thread error: #{e}"
378
+ end
379
+ }.run
367
380
  end
368
381
  end
369
382
 
@@ -1,5 +1,5 @@
1
1
  module Fluent
2
2
 
3
- VERSION = '0.10.38'
3
+ VERSION = '0.10.39'
4
4
 
5
5
  end
@@ -7,7 +7,7 @@ require 'fileutils'
7
7
  class ConfigTest < Test::Unit::TestCase
8
8
  include Fluent
9
9
 
10
- TMP_DIR = File.dirname(__FILE__) + "/tmp"
10
+ TMP_DIR = File.dirname(__FILE__) + "/tmp/config#{ENV['TEST_ENV_NUMBER']}"
11
11
 
12
12
  def prepare_config
13
13
  write_config "#{TMP_DIR}/config_test_1.conf", %[
@@ -1,4 +1,6 @@
1
1
  require 'fluent/config_dsl'
2
+ require 'fluent/test'
3
+ require 'helper'
2
4
 
3
5
  class ConfigDSLTest < Test::Unit::TestCase
4
6
  # TEST_CONFIG1 = %[
@@ -38,10 +40,24 @@ match('test.**') {
38
40
 
39
41
  TEST_DSL_CONFIG2 = %q[
40
42
  v = [0, 1, 2]
43
+ ]
44
+
45
+ TEST_DSL_CONFIG3 = %q[
46
+ match
47
+ ]
48
+
49
+ TEST_DSL_CONFIG4 = %q[
50
+ match('aa', 'bb'){
51
+ type :null
52
+ }
53
+ ]
54
+
55
+ TEST_DSL_CONFIG5 = %q[
56
+ match('aa')
41
57
  ]
42
58
 
43
59
  def test_parse
44
- root = Fluent::Config::DSL::DSLParser.parse(TEST_DSL_CONFIG1)
60
+ root = Fluent::Config::DSL::Parser.parse(TEST_DSL_CONFIG1)
45
61
 
46
62
  assert_equal 0, root.keys.size
47
63
  assert_equal 2, root.elements.size
@@ -69,9 +85,23 @@ v = [0, 1, 2]
69
85
  end
70
86
 
71
87
  def test_parse2
72
- root = Fluent::Config::DSL::DSLParser.parse(TEST_DSL_CONFIG2)
88
+ root = Fluent::Config::DSL::Parser.parse(TEST_DSL_CONFIG2)
73
89
 
74
90
  assert_equal 0, root.keys.size
75
91
  assert_equal 0, root.elements.size
76
92
  end
93
+
94
+ def test_config_error
95
+ assert_raise(ArgumentError) {
96
+ Fluent::Config::DSL::Parser.parse(TEST_DSL_CONFIG3)
97
+ }
98
+
99
+ assert_raise(ArgumentError) {
100
+ Fluent::Config::DSL::Parser.parse(TEST_DSL_CONFIG4)
101
+ }
102
+
103
+ assert_raise(ArgumentError) {
104
+ Fluent::Config::DSL::Parser.parse(TEST_DSL_CONFIG5)
105
+ }
106
+ end
77
107
  end
@@ -8,8 +8,11 @@ unless defined?(Test::Unit::AssertionFailedError)
8
8
  end
9
9
  end
10
10
 
11
- class Test::Unit::TestCase
12
- include RR::Adapters::TestUnit
11
+ def unused_port
12
+ s = TCPServer.open(0)
13
+ port = s.addr[1]
14
+ s.close
15
+ port
13
16
  end
14
17
 
15
18
  def ipv6_enabled?
@@ -9,7 +9,35 @@ module ParserTest
9
9
  if format
10
10
  Time.strptime(str_time, format).to_i
11
11
  else
12
- Time.parse(str_time)
12
+ Time.parse(str_time).to_i
13
+ end
14
+ end
15
+
16
+ class TimeParserTest < ::Test::Unit::TestCase
17
+ include ParserTest
18
+
19
+ def test_call_with_parse
20
+ parser = TextParser::TimeParser.new(nil)
21
+
22
+ time = str2time('2013-09-18 12:00:00 +0900')
23
+ assert_equal(time, parser.parse('2013-09-18 12:00:00 +0900'))
24
+ end
25
+
26
+ def test_call_with_strptime
27
+ parser = TextParser::TimeParser.new('%d/%b/%Y:%H:%M:%S %z')
28
+
29
+ time = str2time('28/Feb/2013:12:00:00 +0900', '%d/%b/%Y:%H:%M:%S %z')
30
+ assert_equal(time, parser.parse('28/Feb/2013:12:00:00 +0900'))
31
+ end
32
+
33
+ def test_call_with_invalid_argument
34
+ parser = TextParser::TimeParser.new(nil)
35
+
36
+ [[], {}, nil, true, 10000].each { |v|
37
+ assert_raise ArgumentError do
38
+ parser.parse(v)
39
+ end
40
+ }
13
41
  end
14
42
  end
15
43
 
@@ -151,6 +179,7 @@ module ParserTest
151
179
 
152
180
  def test_call
153
181
  parser = TextParser::LabeledTSVParser.new
182
+ parser.configure({})
154
183
  time, record = parser.call("time:2013/02/28 12:00:00\thost:192.168.0.1\treq_id:111")
155
184
 
156
185
  assert_equal(str2time('2013/02/28 12:00:00', '%Y/%m/%d %H:%M:%S'), time)
@@ -190,4 +219,32 @@ module ParserTest
190
219
  }, record)
191
220
  end
192
221
  end
222
+
223
+ class NoneParserTest < ::Test::Unit::TestCase
224
+ include ParserTest
225
+
226
+ def test_config_params
227
+ parser = TextParser::NoneParser.new
228
+ assert_equal "message", parser.message_key
229
+
230
+ parser.configure('message_key' => 'foobar')
231
+ assert_equal "foobar", parser.message_key
232
+ end
233
+
234
+ def test_call
235
+ parser = TextParser::TEMPLATE_FACTORIES['none'].call
236
+ time, record = parser.call('log message!')
237
+
238
+ assert_equal({'message' => 'log message!'}, record)
239
+ end
240
+
241
+ def test_call_with_message_key
242
+ parser = TextParser::NoneParser.new
243
+ parser.configure('message_key' => 'foobar')
244
+ time, record = parser.call('log message!')
245
+
246
+ assert_equal({'foobar' => 'log message!'}, record)
247
+ end
248
+ end
249
+
193
250
  end
@@ -1,12 +1,14 @@
1
1
  require 'fluent/test'
2
+ require 'helper'
2
3
 
3
4
  class ForwardInputTest < Test::Unit::TestCase
4
5
  def setup
5
6
  Fluent::Test.setup
6
7
  end
7
8
 
9
+ PORT = unused_port
8
10
  CONFIG = %[
9
- port 13998
11
+ port #{PORT}
10
12
  bind 127.0.0.1
11
13
  ]
12
14
 
@@ -16,12 +18,12 @@ class ForwardInputTest < Test::Unit::TestCase
16
18
 
17
19
  def test_configure
18
20
  d = create_driver
19
- assert_equal 13998, d.instance.port
21
+ assert_equal PORT, d.instance.port
20
22
  assert_equal '127.0.0.1', d.instance.bind
21
23
  end
22
24
 
23
25
  def connect
24
- TCPSocket.new('127.0.0.1', 13998)
26
+ TCPSocket.new('127.0.0.1', PORT)
25
27
  end
26
28
 
27
29
  def test_time
@@ -120,4 +122,3 @@ class ForwardInputTest < Test::Unit::TestCase
120
122
 
121
123
  # TODO heartbeat
122
124
  end
123
-
@@ -1,4 +1,5 @@
1
1
  require 'fluent/test'
2
+ require 'helper'
2
3
  require 'net/http'
3
4
 
4
5
  class HttpInputTest < Test::Unit::TestCase
@@ -6,8 +7,9 @@ class HttpInputTest < Test::Unit::TestCase
6
7
  Fluent::Test.setup
7
8
  end
8
9
 
10
+ PORT = unused_port
9
11
  CONFIG = %[
10
- port 9911
12
+ port #{PORT}
11
13
  bind 127.0.0.1
12
14
  body_size_limit 10m
13
15
  keepalive_timeout 5
@@ -19,7 +21,7 @@ class HttpInputTest < Test::Unit::TestCase
19
21
 
20
22
  def test_configure
21
23
  d = create_driver
22
- assert_equal 9911, d.instance.port
24
+ assert_equal PORT, d.instance.port
23
25
  assert_equal '127.0.0.1', d.instance.bind
24
26
  assert_equal 10*1024*1024, d.instance.body_size_limit
25
27
  assert_equal 5, d.instance.keepalive_timeout
@@ -68,7 +70,7 @@ class HttpInputTest < Test::Unit::TestCase
68
70
 
69
71
  d.run do
70
72
  d.expected_emits.each {|tag,time,record|
71
- http = Net::HTTP.new("127.0.0.1", 9911)
73
+ http = Net::HTTP.new("127.0.0.1", PORT)
72
74
  req = Net::HTTP::Post.new("/#{tag}?time=#{time.to_s}", {"content-type"=>"application/json; charset=utf-8"})
73
75
  req.body = record.to_json
74
76
  res = http.request(req)
@@ -94,7 +96,7 @@ class HttpInputTest < Test::Unit::TestCase
94
96
  end
95
97
 
96
98
  def post(path, params)
97
- http = Net::HTTP.new("127.0.0.1", 9911)
99
+ http = Net::HTTP.new("127.0.0.1", PORT)
98
100
  req = Net::HTTP::Post.new(path, {})
99
101
  req.set_form_data(params)
100
102
  http.request(req)
@@ -1,4 +1,5 @@
1
1
  require 'fluent/test'
2
+ require 'helper'
2
3
 
3
4
  module StreamInputTest
4
5
  def setup
@@ -107,8 +108,9 @@ end
107
108
  class TcpInputTest < Test::Unit::TestCase
108
109
  include StreamInputTest
109
110
 
111
+ PORT = unused_port
110
112
  CONFIG = %[
111
- port 13998
113
+ port #{PORT}
112
114
  bind 127.0.0.1
113
115
  ]
114
116
 
@@ -118,20 +120,19 @@ class TcpInputTest < Test::Unit::TestCase
118
120
 
119
121
  def test_configure
120
122
  d = create_driver
121
- assert_equal 13998, d.instance.port
123
+ assert_equal PORT, d.instance.port
122
124
  assert_equal '127.0.0.1', d.instance.bind
123
125
  end
124
126
 
125
127
  def connect
126
- TCPSocket.new('127.0.0.1', 13998)
128
+ TCPSocket.new('127.0.0.1', PORT)
127
129
  end
128
130
  end
129
131
 
130
132
  class UnixInputTest < Test::Unit::TestCase
131
133
  include StreamInputTest
132
134
 
133
- TMP_DIR = File.dirname(__FILE__) + "/../tmp"
134
-
135
+ TMP_DIR = File.dirname(__FILE__) + "/../tmp/in_unix#{ENV['TEST_ENV_NUMBER']}"
135
136
  CONFIG = %[
136
137
  path #{TMP_DIR}/unix
137
138
  backlog 1000
@@ -151,4 +152,3 @@ class UnixInputTest < Test::Unit::TestCase
151
152
  UNIXSocket.new("#{TMP_DIR}/unix")
152
153
  end
153
154
  end
154
-
@@ -7,14 +7,15 @@ class SyslogInputTest < Test::Unit::TestCase
7
7
  require 'fluent/plugin/socket_util'
8
8
  end
9
9
 
10
+ PORT = unused_port
10
11
  CONFIG = %[
11
- port 9911
12
+ port #{PORT}
12
13
  bind 127.0.0.1
13
14
  tag syslog
14
15
  ]
15
16
 
16
17
  IPv6_CONFIG = %[
17
- port 9911
18
+ port #{PORT}
18
19
  bind ::1
19
20
  tag syslog
20
21
  ]
@@ -29,7 +30,7 @@ class SyslogInputTest < Test::Unit::TestCase
29
30
 
30
31
  configs.each_pair { |k, v|
31
32
  d = create_driver(v)
32
- assert_equal 9911, d.instance.port
33
+ assert_equal PORT, d.instance.port
33
34
  assert_equal k, d.instance.bind
34
35
  }
35
36
  end
@@ -48,7 +49,7 @@ class SyslogInputTest < Test::Unit::TestCase
48
49
 
49
50
  d.run do
50
51
  u = Fluent::SocketUtil.create_udp_socket(k)
51
- u.connect(k, 9911)
52
+ u.connect(k, PORT)
52
53
  tests.each {|test|
53
54
  u.send(test['msg'], 0)
54
55
  }
@@ -72,7 +73,7 @@ class SyslogInputTest < Test::Unit::TestCase
72
73
 
73
74
  d.run do
74
75
  u = UDPSocket.new
75
- u.connect('127.0.0.1', 9911)
76
+ u.connect('127.0.0.1', PORT)
76
77
  tests.each {|test|
77
78
  u.send(test['msg'], 0)
78
79
  }
@@ -8,7 +8,7 @@ class TailInputTest < Test::Unit::TestCase
8
8
  FileUtils.mkdir_p(TMP_DIR)
9
9
  end
10
10
 
11
- TMP_DIR = File.dirname(__FILE__) + "/../tmp"
11
+ TMP_DIR = File.dirname(__FILE__) + "/../tmp/tail#{ENV['TEST_ENV_NUMBER']}"
12
12
 
13
13
  CONFIG = %[
14
14
  path #{TMP_DIR}/tail.txt
@@ -88,5 +88,83 @@ class CopyOutputTest < Test::Unit::TestCase
88
88
  ], o.events
89
89
  }
90
90
  end
91
+
92
+ def create_event_test_driver(is_deep_copy = false)
93
+ deep_copy_config = %[
94
+ deep_copy true
95
+ ]
96
+
97
+ output1 = Fluent::Plugin.new_output('test')
98
+ output1.configure('name' => 'output1')
99
+ output1.define_singleton_method(:emit) do |tag, es, chain|
100
+ es.each do |time, record|
101
+ record['foo'] = 'bar'
102
+ super(tag, [[time, record]], chain)
103
+ end
104
+ end
105
+
106
+ output2 = Fluent::Plugin.new_output('test')
107
+ output2.configure('name' => 'output2')
108
+ output2.define_singleton_method(:emit) do |tag, es, chain|
109
+ es.each do |time, record|
110
+ super(tag, [[time, record]], chain)
111
+ end
112
+ end
113
+
114
+ outputs = [output1, output2]
115
+
116
+ d = Fluent::Test::OutputTestDriver.new(Fluent::CopyOutput)
117
+ d = d.configure(deep_copy_config) if is_deep_copy
118
+ d.instance.instance_eval { @outputs = outputs }
119
+ d
120
+ end
121
+
122
+ def test_one_event
123
+ time = Time.parse("2013-05-26 06:37:22 UTC").to_i
124
+
125
+ d = create_event_test_driver(false)
126
+ es = Fluent::OneEventStream.new(time, {"a" => 1})
127
+ d.instance.emit('test', es, Fluent::NullOutputChain.instance)
128
+
129
+ assert_equal [
130
+ [[time, {"a"=>1, "foo"=>"bar"}]],
131
+ [[time, {"a"=>1, "foo"=>"bar"}]]
132
+ ], d.instance.outputs.map{ |o| o.events }
133
+
134
+ d = create_event_test_driver(true)
135
+ es = Fluent::OneEventStream.new(time, {"a" => 1})
136
+ d.instance.emit('test', es, Fluent::NullOutputChain.instance)
137
+
138
+ assert_equal [
139
+ [[time, {"a"=>1, "foo"=>"bar"}]],
140
+ [[time, {"a"=>1}]]
141
+ ], d.instance.outputs.map{ |o| o.events }
142
+ end
143
+
144
+ def test_multi_event
145
+ time = Time.parse("2013-05-26 06:37:22 UTC").to_i
146
+
147
+ d = create_event_test_driver(false)
148
+ es = Fluent::MultiEventStream.new
149
+ es.add(time, {"a" => 1})
150
+ es.add(time, {"b" => 2})
151
+ d.instance.emit('test', es, Fluent::NullOutputChain.instance)
152
+
153
+ assert_equal [
154
+ [[time, {"a"=>1, "foo"=>"bar"}], [time, {"b"=>2, "foo"=>"bar"}]],
155
+ [[time, {"a"=>1, "foo"=>"bar"}], [time, {"b"=>2, "foo"=>"bar"}]]
156
+ ], d.instance.outputs.map{ |o| o.events }
157
+
158
+ d = create_event_test_driver(true)
159
+ es = Fluent::MultiEventStream.new
160
+ es.add(time, {"a" => 1})
161
+ es.add(time, {"b" => 2})
162
+ d.instance.emit('test', es, Fluent::NullOutputChain.instance)
163
+
164
+ assert_equal [
165
+ [[time, {"a"=>1, "foo"=>"bar"}], [time, {"b"=>2, "foo"=>"bar"}]],
166
+ [[time, {"a"=>1}], [time, {"b"=>2}]]
167
+ ], d.instance.outputs.map{ |o| o.events }
168
+ end
91
169
  end
92
170
 
@@ -8,7 +8,7 @@ class ExecOutputTest < Test::Unit::TestCase
8
8
  FileUtils.mkdir_p(TMP_DIR)
9
9
  end
10
10
 
11
- TMP_DIR = File.dirname(__FILE__) + "/../tmp"
11
+ TMP_DIR = File.dirname(__FILE__) + "/../tmp/out_exec#{ENV['TEST_ENV_NUMBER']}"
12
12
 
13
13
  CONFIG = %[
14
14
  buffer_path #{TMP_DIR}/buffer
@@ -9,7 +9,7 @@ class FileOutputTest < Test::Unit::TestCase
9
9
  FileUtils.mkdir_p(TMP_DIR)
10
10
  end
11
11
 
12
- TMP_DIR = File.expand_path(File.dirname(__FILE__) + "/../tmp")
12
+ TMP_DIR = File.expand_path(File.dirname(__FILE__) + "/../tmp/out_file#{ENV['TEST_ENV_NUMBER']}")
13
13
  SYMLINK_PATH = File.expand_path("#{TMP_DIR}/current")
14
14
 
15
15
  CONFIG = %[
@@ -81,20 +81,32 @@ class FileOutputTest < Test::Unit::TestCase
81
81
  end
82
82
 
83
83
  def test_write_with_symlink
84
- d = create_driver(CONFIG + %[
84
+ conf = CONFIG + %[
85
85
  symlink_path #{SYMLINK_PATH}
86
- ])
87
- symlink_path = "#{SYMLINK_PATH}.gz"
86
+ ]
87
+ symlink_path = "#{SYMLINK_PATH}"
88
88
 
89
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
90
- d.emit({"a"=>1}, time)
89
+ Fluent::FileBuffer.clear_buffer_paths
90
+ d = Fluent::Test::TestDriver.new(Fluent::FileOutput).configure(conf)
91
91
 
92
- # FileOutput#write returns path
93
- path = d.run
94
- assert File.exists?(symlink_path)
95
- assert File.symlink?(symlink_path)
96
- ensure
97
- FileUtils.rm_rf(symlink_path)
92
+ begin
93
+ d.instance.start
94
+ 10.times { sleep 0.05 }
95
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
96
+ es = Fluent::OneEventStream.new(time, {"a"=>1})
97
+ d.instance.emit('tag', es, Fluent::NullOutputChain.instance)
98
+
99
+ assert File.exists?(symlink_path)
100
+ assert File.symlink?(symlink_path)
101
+
102
+ d.instance.enqueue_buffer
103
+
104
+ assert !File.exists?(symlink_path)
105
+ assert File.symlink?(symlink_path)
106
+ ensure
107
+ d.instance.shutdown
108
+ FileUtils.rm_rf(symlink_path)
109
+ end
98
110
  end
99
111
  end
100
112
 
@@ -1,4 +1,5 @@
1
1
  require 'fluent/test'
2
+ require 'helper'
2
3
 
3
4
  module StreamOutputTest
4
5
  def setup
@@ -33,8 +34,9 @@ end
33
34
  class TcpOutputTest < Test::Unit::TestCase
34
35
  include StreamOutputTest
35
36
 
37
+ PORT = unused_port
36
38
  CONFIG = %[
37
- port 13999
39
+ port #{PORT}
38
40
  host 127.0.0.1
39
41
  send_timeout 51
40
42
  ]
@@ -45,7 +47,7 @@ class TcpOutputTest < Test::Unit::TestCase
45
47
 
46
48
  def test_configure
47
49
  d = create_driver
48
- assert_equal 13999, d.instance.port
50
+ assert_equal PORT, d.instance.port
49
51
  assert_equal '127.0.0.1', d.instance.host
50
52
  assert_equal 51, d.instance.send_timeout
51
53
  end
@@ -54,8 +56,7 @@ end
54
56
  class UnixOutputTest < Test::Unit::TestCase
55
57
  include StreamOutputTest
56
58
 
57
- TMP_DIR = File.dirname(__FILE__) + "/../tmp"
58
-
59
+ TMP_DIR = File.dirname(__FILE__) + "/../tmp/out_unix#{ENV['TEST_ENV_NUMBER']}"
59
60
  CONFIG = %[
60
61
  path #{TMP_DIR}/unix
61
62
  send_timeout 52
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluentd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.38
4
+ version: 0.10.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sadayuki Furuhashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-29 00:00:00.000000000 Z
11
+ date: 2013-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -124,6 +124,20 @@ dependencies:
124
124
  - - '>='
125
125
  - !ruby/object:Gem::Version
126
126
  version: 0.9.2
127
+ - !ruby/object:Gem::Dependency
128
+ name: parallel_tests
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - '>='
132
+ - !ruby/object:Gem::Version
133
+ version: 0.15.3
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - '>='
139
+ - !ruby/object:Gem::Version
140
+ version: 0.15.3
127
141
  - !ruby/object:Gem::Dependency
128
142
  name: rr
129
143
  requirement: !ruby/object:Gem::Requirement