fluentd 0.12.0.pre.1 → 0.12.0.pre.2

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.

Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +1 -0
  4. data/ChangeLog +21 -0
  5. data/README.md +10 -2
  6. data/Rakefile +4 -13
  7. data/example/v1_literal_example.conf +36 -0
  8. data/fluentd.gemspec +4 -1
  9. data/lib/fluent/buffer.rb +73 -46
  10. data/lib/fluent/command/fluentd.rb +7 -2
  11. data/lib/fluent/config/basic_parser.rb +5 -0
  12. data/lib/fluent/config/element.rb +2 -5
  13. data/lib/fluent/config/literal_parser.rb +26 -7
  14. data/lib/fluent/config/section.rb +2 -0
  15. data/lib/fluent/config/v1_parser.rb +9 -2
  16. data/lib/fluent/formatter.rb +2 -1
  17. data/lib/fluent/mixin.rb +22 -7
  18. data/lib/fluent/output.rb +17 -8
  19. data/lib/fluent/parser.rb +14 -3
  20. data/lib/fluent/plugin/buf_file.rb +30 -15
  21. data/lib/fluent/plugin/filter_grep.rb +69 -0
  22. data/lib/fluent/plugin/filter_record_transformer.rb +183 -0
  23. data/lib/fluent/plugin/in_exec.rb +6 -0
  24. data/lib/fluent/plugin/in_forward.rb +34 -4
  25. data/lib/fluent/plugin/in_http.rb +1 -1
  26. data/lib/fluent/plugin/out_exec.rb +1 -1
  27. data/lib/fluent/plugin/out_exec_filter.rb +8 -1
  28. data/lib/fluent/plugin/out_forward.rb +82 -4
  29. data/lib/fluent/supervisor.rb +1 -1
  30. data/lib/fluent/timezone.rb +131 -0
  31. data/lib/fluent/version.rb +1 -1
  32. data/test/config/assertions.rb +42 -0
  33. data/test/config/test_config_parser.rb +385 -0
  34. data/test/config/test_configurable.rb +530 -0
  35. data/test/config/test_configure_proxy.rb +99 -0
  36. data/test/config/test_dsl.rb +237 -0
  37. data/test/config/test_literal_parser.rb +293 -0
  38. data/test/config/test_section.rb +112 -0
  39. data/test/config/test_system_config.rb +49 -0
  40. data/test/helper.rb +25 -0
  41. data/test/plugin/test_buf_file.rb +604 -0
  42. data/test/plugin/test_buf_memory.rb +204 -0
  43. data/test/plugin/test_filter_grep.rb +124 -0
  44. data/test/plugin/test_filter_record_transformer.rb +251 -0
  45. data/test/plugin/test_in_exec.rb +1 -0
  46. data/test/plugin/test_in_forward.rb +205 -2
  47. data/test/plugin/test_in_gc_stat.rb +1 -0
  48. data/test/plugin/test_in_http.rb +58 -2
  49. data/test/plugin/test_in_object_space.rb +1 -0
  50. data/test/plugin/test_in_status.rb +1 -0
  51. data/test/plugin/test_in_stream.rb +1 -1
  52. data/test/plugin/test_in_syslog.rb +1 -1
  53. data/test/plugin/test_in_tail.rb +1 -0
  54. data/test/plugin/test_in_tcp.rb +1 -1
  55. data/test/plugin/test_in_udp.rb +1 -1
  56. data/test/plugin/test_out_copy.rb +1 -0
  57. data/test/plugin/test_out_exec.rb +1 -0
  58. data/test/plugin/test_out_exec_filter.rb +1 -0
  59. data/test/plugin/test_out_file.rb +36 -0
  60. data/test/plugin/test_out_forward.rb +279 -8
  61. data/test/plugin/test_out_roundrobin.rb +1 -0
  62. data/test/plugin/test_out_stdout.rb +1 -0
  63. data/test/plugin/test_out_stream.rb +1 -1
  64. data/test/test_buffer.rb +530 -0
  65. data/test/test_config.rb +1 -1
  66. data/test/test_configdsl.rb +1 -1
  67. data/test/test_formatter.rb +223 -0
  68. data/test/test_match.rb +1 -2
  69. data/test/test_mixin.rb +74 -2
  70. data/test/test_parser.rb +7 -1
  71. metadata +88 -35
  72. data/lib/fluent/plugin/buf_zfile.rb +0 -75
  73. data/spec/config/config_parser_spec.rb +0 -314
  74. data/spec/config/configurable_spec.rb +0 -524
  75. data/spec/config/configure_proxy_spec.rb +0 -96
  76. data/spec/config/dsl_spec.rb +0 -239
  77. data/spec/config/helper.rb +0 -49
  78. data/spec/config/literal_parser_spec.rb +0 -222
  79. data/spec/config/section_spec.rb +0 -97
  80. data/spec/config/system_config_spec.rb +0 -49
  81. data/spec/spec_helper.rb +0 -60
@@ -45,6 +45,7 @@ module Fluent
45
45
  "name:#{@name}, arg:#{@arg}, " + attrs + ", " + @elements.inspect
46
46
  end
47
47
 
48
+ # This method assumes _o_ is an Element object. Should return false for nil or other object
48
49
  def ==(o)
49
50
  self.name == o.name && self.arg == o.arg &&
50
51
  self.keys.size == o.keys.size &&
@@ -102,11 +103,7 @@ module Fluent
102
103
  out << "#{indent}<#{@name} #{@arg}>\n"
103
104
  end
104
105
  each_pair { |k, v|
105
- if @v1_config
106
- out << "#{nindent}#{k} #{Element.unescape_parameter(v)}\n"
107
- else
108
- out << "#{nindent}#{k} #{v}\n"
109
- end
106
+ out << "#{nindent}#{k} #{v}\n"
110
107
  }
111
108
  @elements.each { |e|
112
109
  out << e.to_s(nest + 1)
@@ -51,7 +51,7 @@ module Fluent
51
51
  end
52
52
 
53
53
  def parse_literal(string_boundary_charset = LINE_END)
54
- spacing
54
+ spacing_without_comment
55
55
 
56
56
  value = if skip(/\[/)
57
57
  scan_json(true)
@@ -65,13 +65,15 @@ module Fluent
65
65
 
66
66
  def scan_string(string_boundary_charset = LINE_END)
67
67
  if skip(/\"/)
68
- return scan_quoted_string
68
+ return scan_double_quoted_string
69
+ elsif skip(/\'/)
70
+ return scan_single_quoted_string
69
71
  else
70
72
  return scan_nonquoted_string(string_boundary_charset)
71
73
  end
72
74
  end
73
75
 
74
- def scan_quoted_string
76
+ def scan_double_quoted_string
75
77
  string = []
76
78
  while true
77
79
  if skip(/\"/)
@@ -84,7 +86,24 @@ module Fluent
84
86
  elsif s = scan(/./)
85
87
  string << s
86
88
  else
87
- parse_error! "unexpected end of file in a quoted string"
89
+ parse_error! "unexpected end of file in a double quoted string"
90
+ end
91
+ end
92
+ end
93
+
94
+ def scan_single_quoted_string
95
+ string = []
96
+ while true
97
+ if skip(/\'/)
98
+ return string.join
99
+ elsif s = scan(/\\'/)
100
+ string << "'"
101
+ elsif s = scan(/\\\\/)
102
+ string << "\\"
103
+ elsif s = scan(/./)
104
+ string << s
105
+ else
106
+ parse_error! "unexpected end of file in a signle quoted string"
88
107
  end
89
108
  end
90
109
  end
@@ -94,8 +113,8 @@ module Fluent
94
113
 
95
114
  string = []
96
115
  while true
97
- if s = scan(/\\./)
98
- string << eval_escape_char(s[1,1])
116
+ if s = scan(/\#/)
117
+ string << '#'
99
118
  elsif s = scan(charset)
100
119
  string << s
101
120
  else
@@ -214,7 +233,7 @@ module Fluent
214
233
  end
215
234
 
216
235
  unless result
217
- parse_error! "got incomplete #{is_array ? 'array' : 'hash'} configuration"
236
+ parse_error! "got incomplete JSON #{is_array ? 'array' : 'hash'} configuration"
218
237
  end
219
238
 
220
239
  JSON.dump(result)
@@ -30,6 +30,8 @@ module Fluent
30
30
  @params = params
31
31
  end
32
32
 
33
+ alias :object_id :__id__
34
+
33
35
  def inspect
34
36
  "<Fluent::Config::Section #{@params.to_json}>"
35
37
  end
@@ -50,6 +50,8 @@ module Fluent
50
50
  return root
51
51
  end
52
52
 
53
+ ELEM_SYMBOLS = ['match', 'source', 'filter', 'system']
54
+
53
55
  def parse_element(root_element, elem_name, attrs = {}, elems = [])
54
56
  while true
55
57
  spacing
@@ -100,7 +102,7 @@ module Fluent
100
102
 
101
103
  else
102
104
  k = scan_string(SPACING)
103
- spacing
105
+ spacing_without_comment
104
106
  if prev_match.include?("\n") # support 'tag_mapped' like "without value" configuration
105
107
  attrs[k] = ""
106
108
  else
@@ -114,7 +116,12 @@ module Fluent
114
116
  attrs[k] = v
115
117
  else
116
118
  if k.start_with?('@')
117
- parse_error! "'@' is reserved prefix. Don't use '@' in parameter name"
119
+ if root_element || ELEM_SYMBOLS.include?(elem_name)
120
+ parse_error! "'@' is the system reserved prefix. Don't use '@' prefix parameter in the configuration: #{k}"
121
+ else
122
+ # TODO: This is for backward compatibility. It will throw an error in the future.
123
+ $log.warn "'@' is the system reserved prefix. It works in the nested configuration for now but it will be rejected: #{k}"
124
+ end
118
125
  end
119
126
 
120
127
  v = parse_literal
@@ -27,6 +27,7 @@ module Fluent
27
27
  config_param :include_tag_key, :bool, :default => false
28
28
  config_param :tag_key, :string, :default => 'tag'
29
29
  config_param :localtime, :bool, :default => true
30
+ config_param :timezone, :string, :default => nil
30
31
  }
31
32
  end
32
33
 
@@ -36,7 +37,7 @@ module Fluent
36
37
  if conf['utc']
37
38
  @localtime = false
38
39
  end
39
- @timef = TimeFormatter.new(@time_format, @localtime)
40
+ @timef = TimeFormatter.new(@time_format, @localtime, @timezone)
40
41
  end
41
42
 
42
43
  def filter_record(tag, time, record)
data/lib/fluent/mixin.rb CHANGED
@@ -16,12 +16,21 @@
16
16
 
17
17
  module Fluent
18
18
  class TimeFormatter
19
- def initialize(format, localtime)
19
+ require 'fluent/timezone'
20
+
21
+ def initialize(format, localtime, timezone = nil)
20
22
  @tc1 = 0
21
23
  @tc1_str = nil
22
24
  @tc2 = 0
23
25
  @tc2_str = nil
24
26
 
27
+ if formatter = Fluent::Timezone.formatter(timezone, format)
28
+ define_singleton_method(:format_nocache) {|time|
29
+ formatter.call(time)
30
+ }
31
+ return
32
+ end
33
+
25
34
  if format
26
35
  if localtime
27
36
  define_singleton_method(:format_nocache) {|time|
@@ -112,15 +121,16 @@ module Fluent
112
121
  end
113
122
 
114
123
  module SetTimeKeyMixin
124
+ require 'fluent/timezone'
115
125
  include RecordFilterMixin
116
126
 
117
- attr_accessor :include_time_key, :time_key, :localtime
127
+ attr_accessor :include_time_key, :time_key, :localtime, :timezone
118
128
 
119
129
  def configure(conf)
120
- super
121
-
122
130
  @include_time_key = false
123
131
 
132
+ super
133
+
124
134
  if s = conf['include_time_key']
125
135
  include_time_key = Config.bool_value(s)
126
136
  raise ConfigError, "Invalid boolean expression '#{s}' for include_time_key parameter" if include_time_key.nil?
@@ -138,7 +148,12 @@ module Fluent
138
148
  @localtime = false
139
149
  end
140
150
 
141
- @timef = TimeFormatter.new(@time_format, @localtime)
151
+ if conf['timezone']
152
+ @timezone = conf['timezone']
153
+ Fluent::Timezone.validate!(@timezone)
154
+ end
155
+
156
+ @timef = TimeFormatter.new(@time_format, @localtime, @timezone)
142
157
  end
143
158
  end
144
159
 
@@ -155,10 +170,10 @@ module Fluent
155
170
  attr_accessor :include_tag_key, :tag_key
156
171
 
157
172
  def configure(conf)
158
- super
159
-
160
173
  @include_tag_key = false
161
174
 
175
+ super
176
+
162
177
  if s = conf['include_tag_key']
163
178
  include_tag_key = Config.bool_value(s)
164
179
  raise ConfigError, "Invalid boolean expression '#{s}' for include_tag_key parameter" if include_tag_key.nil?
data/lib/fluent/output.rb CHANGED
@@ -322,7 +322,7 @@ module Fluent
322
322
  @num_errors = 0
323
323
  # Note: don't notify to other threads to prevent
324
324
  # burst to recovered server
325
- $log.warn "retry succeeded.", :instance=>object_id
325
+ $log.warn "retry succeeded.", :plugin_id=>plugin_id
326
326
  end
327
327
 
328
328
  if has_next
@@ -347,20 +347,20 @@ module Fluent
347
347
  end
348
348
 
349
349
  if @disable_retry_limit || error_count < @retry_limit
350
- $log.warn "temporarily failed to flush the buffer.", :next_retry=>Time.at(@next_retry_time), :error_class=>e.class.to_s, :error=>e.to_s, :instance=>object_id
350
+ $log.warn "temporarily failed to flush the buffer.", :next_retry=>Time.at(@next_retry_time), :error_class=>e.class.to_s, :error=>e.to_s, :plugin_id=>plugin_id
351
351
  $log.warn_backtrace e.backtrace
352
352
 
353
353
  elsif @secondary
354
354
  if error_count == @retry_limit
355
- $log.warn "failed to flush the buffer.", :error_class=>e.class.to_s, :error=>e.to_s, :instance=>object_id
355
+ $log.warn "failed to flush the buffer.", :error_class=>e.class.to_s, :error=>e.to_s, :plugin_id=>plugin_id
356
356
  $log.warn "retry count exceededs limit. falling back to secondary output."
357
357
  $log.warn_backtrace e.backtrace
358
358
  retry # retry immediately
359
359
  elsif error_count <= @retry_limit + @secondary_limit
360
- $log.warn "failed to flush the buffer, next retry will be with secondary output.", :next_retry=>Time.at(@next_retry_time), :error_class=>e.class.to_s, :error=>e.to_s, :instance=>object_id
360
+ $log.warn "failed to flush the buffer, next retry will be with secondary output.", :next_retry=>Time.at(@next_retry_time), :error_class=>e.class.to_s, :error=>e.to_s, :plugin_id=>plugin_id
361
361
  $log.warn_backtrace e.backtrace
362
362
  else
363
- $log.warn "failed to flush the buffer.", :error_class=>e.class, :error=>e.to_s, :instance=>object_id
363
+ $log.warn "failed to flush the buffer.", :error_class=>e.class, :error=>e.to_s, :plugin_id=>plugin_id
364
364
  $log.warn "secondary retry count exceededs limit."
365
365
  $log.warn_backtrace e.backtrace
366
366
  write_abort
@@ -368,7 +368,7 @@ module Fluent
368
368
  end
369
369
 
370
370
  else
371
- $log.warn "failed to flush the buffer.", :error_class=>e.class.to_s, :error=>e.to_s, :instance=>object_id
371
+ $log.warn "failed to flush the buffer.", :error_class=>e.class.to_s, :error=>e.to_s, :plugin_id=>plugin_id
372
372
  $log.warn "retry count exceededs limit."
373
373
  $log.warn_backtrace e.backtrace
374
374
  write_abort
@@ -459,6 +459,8 @@ module Fluent
459
459
 
460
460
 
461
461
  class TimeSlicedOutput < BufferedOutput
462
+ require 'fluent/timezone'
463
+
462
464
  def initialize
463
465
  super
464
466
  @localtime = true
@@ -467,6 +469,7 @@ module Fluent
467
469
 
468
470
  config_param :time_slice_format, :string, :default => '%Y%m%d'
469
471
  config_param :time_slice_wait, :time, :default => 10*60
472
+ config_param :timezone, :string, :default => nil
470
473
  config_set_default :buffer_type, 'file' # overwrite default buffer_type
471
474
  config_set_default :buffer_chunk_limit, 256*1024*1024 # overwrite default buffer_chunk_limit
472
475
  config_set_default :flush_interval, nil
@@ -476,14 +479,20 @@ module Fluent
476
479
  def configure(conf)
477
480
  super
478
481
 
479
- # TODO timezone
480
482
  if conf['utc']
481
483
  @localtime = false
482
484
  elsif conf['localtime']
483
485
  @localtime = true
484
486
  end
485
487
 
486
- if @localtime
488
+ if conf['timezone']
489
+ @timezone = conf['timezone']
490
+ Fluent::Timezone.validate!(@timezone)
491
+ end
492
+
493
+ if @timezone
494
+ @time_slicer = Timezone.formatter(@timezone, @time_slice_format)
495
+ elsif @localtime
487
496
  @time_slicer = Proc.new {|time|
488
497
  Time.at(time).strftime(@time_slice_format)
489
498
  }
data/lib/fluent/parser.rb CHANGED
@@ -18,6 +18,9 @@ module Fluent
18
18
  require 'fluent/registry'
19
19
 
20
20
  class TextParser
21
+ class ParserError < StandardError
22
+ end
23
+
21
24
  class TimeParser
22
25
  def initialize(time_format)
23
26
  @cache1_key = nil
@@ -34,7 +37,7 @@ module Fluent
34
37
 
35
38
  def parse(value)
36
39
  unless value.is_a?(String)
37
- raise ArgumentError, "Value must be string: #{value}"
40
+ raise ParserError, "value must be string: #{value}"
38
41
  end
39
42
 
40
43
  if @cache1_key == value
@@ -42,7 +45,11 @@ module Fluent
42
45
  elsif @cache2_key == value
43
46
  return @cache2_time
44
47
  else
45
- time = @parser.call(value).to_i
48
+ begin
49
+ time = @parser.call(value).to_i
50
+ rescue => e
51
+ raise ParserError, "invalid time format: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
52
+ end
46
53
  @cache1_key = @cache2_key
47
54
  @cache1_time = @cache2_time
48
55
  @cache2_key = value
@@ -224,7 +231,11 @@ module Fluent
224
231
  if @time_format
225
232
  time = @mutex.synchronize { @time_parser.parse(value) }
226
233
  else
227
- time = value.to_i
234
+ begin
235
+ time = value.to_i
236
+ rescue => e
237
+ raise ParserError, "invalid time value: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
238
+ end
228
239
  end
229
240
  else
230
241
  if @estimate_current_event
@@ -26,7 +26,7 @@ module Fluent
26
26
  FileUtils.ln_sf(@path, symlink_path) if symlink_path
27
27
  end
28
28
 
29
- attr_reader :unique_id
29
+ attr_reader :unique_id, :path
30
30
 
31
31
  def <<(data)
32
32
  @file.write(data)
@@ -64,8 +64,6 @@ module Fluent
64
64
  yield @file
65
65
  end
66
66
 
67
- attr_reader :path
68
-
69
67
  def mv(path)
70
68
  File.rename(@path, path)
71
69
  @path = path
@@ -80,10 +78,15 @@ module Fluent
80
78
  def initialize
81
79
  require 'uri'
82
80
  super
81
+
82
+ @uri_parser = URI::Parser.new
83
83
  end
84
84
 
85
85
  config_param :buffer_path, :string
86
86
 
87
+ # 'symlink_path' is currently only for out_file.
88
+ # That is the reason why this is not config_param, but attr_accessor.
89
+ # See: https://github.com/fluent/fluentd/pull/181
87
90
  attr_accessor :symlink_path
88
91
 
89
92
  def configure(conf)
@@ -96,10 +99,10 @@ module Fluent
96
99
  end
97
100
 
98
101
  if pos = @buffer_path.index('*')
99
- @buffer_path_prefix = @buffer_path[0,pos]
100
- @buffer_path_suffix = @buffer_path[pos+1..-1]
102
+ @buffer_path_prefix = @buffer_path[0, pos]
103
+ @buffer_path_suffix = @buffer_path[(pos + 1)..-1]
101
104
  else
102
- @buffer_path_prefix = @buffer_path+"."
105
+ @buffer_path_prefix = @buffer_path + "."
103
106
  @buffer_path_suffix = ".log"
104
107
  end
105
108
 
@@ -111,11 +114,13 @@ module Fluent
111
114
  end
112
115
 
113
116
  def start
114
- FileUtils.mkdir_p File.dirname(@buffer_path_prefix+"path")
117
+ FileUtils.mkdir_p File.dirname(@buffer_path_prefix + "path")
115
118
  super
116
119
  end
117
120
 
118
- PATH_MATCH = /^(.*)[\._](b|q)([0-9a-fA-F]{1,32})$/
121
+ # Dots are separator for many cases:
122
+ # we should have to escape dots in keys...
123
+ PATH_MATCH = /^([-_.%0-9a-zA-Z]*)\.(b|q)([0-9a-fA-F]{1,32})$/
119
124
 
120
125
  def new_chunk(key)
121
126
  encoded_key = encode_key(key)
@@ -129,8 +134,8 @@ module Fluent
129
134
  queues = []
130
135
 
131
136
  Dir.glob("#{@buffer_path_prefix}*#{@buffer_path_suffix}") {|path|
132
- match = path[@buffer_path_prefix.length..-(@buffer_path_suffix.length+1)]
133
- if m = PATH_MATCH.match(match)
137
+ identifier_part = chunk_identifier_in_path(path)
138
+ if m = PATH_MATCH.match(identifier_part)
134
139
  key = decode_key(m[1])
135
140
  bq = m[2]
136
141
  tsuffix = m[3]
@@ -163,11 +168,18 @@ module Fluent
163
168
  return queue, map
164
169
  end
165
170
 
171
+ def chunk_identifier_in_path(path)
172
+ pos_after_prefix = @buffer_path_prefix.length
173
+ pos_before_suffix = @buffer_path_suffix.length + 1 # from tail of path
174
+
175
+ path.slice(pos_after_prefix..-pos_before_suffix)
176
+ end
177
+
166
178
  def enqueue(chunk)
167
179
  path = chunk.path
168
- mp = path[@buffer_path_prefix.length..-(@buffer_path_suffix.length+1)]
180
+ identifier_part = chunk_identifier_in_path(path)
169
181
 
170
- m = PATH_MATCH.match(mp)
182
+ m = PATH_MATCH.match(identifier_part)
171
183
  encoded_key = m ? m[1] : ""
172
184
  tsuffix = m[3]
173
185
  npath = "#{@buffer_path_prefix}#{encoded_key}.q#{tsuffix}#{@buffer_path_suffix}"
@@ -189,23 +201,26 @@ module Fluent
189
201
 
190
202
  protected
191
203
 
204
+ # Dots are separator for many cases:
205
+ # we should have to escape dots in keys...
192
206
  def encode_key(key)
193
- URI.escape(key, /[^-_.a-zA-Z0-9]/n)
207
+ @uri_parser.escape(key, /[^-_.a-zA-Z0-9]/n) # //n switch means explicit 'ASCII-8BIT' pattern
194
208
  end
195
209
 
196
210
  def decode_key(encoded_key)
197
- URI.unescape(encoded_key)
211
+ @uri_parser.unescape(encoded_key)
198
212
  end
199
213
 
200
214
  def make_path(encoded_key, bq)
201
215
  now = Time.now.utc
202
- timestamp = ((now.to_i*1000*1000+now.usec) << 12 | rand(0xfff))
216
+ timestamp = ((now.to_i * 1000 * 1000 + now.usec) << 12 | rand(0xfff))
203
217
  tsuffix = timestamp.to_s(16)
204
218
  path = "#{@buffer_path_prefix}#{encoded_key}.#{bq}#{tsuffix}#{@buffer_path_suffix}"
205
219
  return path, tsuffix
206
220
  end
207
221
 
208
222
  def tsuffix_to_unique_id(tsuffix)
223
+ # why *2 ? frsyuki said that I forgot why completely.
209
224
  tsuffix.scan(/../).map {|x| x.to_i(16) }.pack('C*') * 2
210
225
  end
211
226
  end