fluentd 0.12.19 → 0.12.20

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: 251e0a23a2109a9b8434de8be77676e4acb84b0f
4
- data.tar.gz: 495d9a6ca6baa5266da3c04f11f5b65336252674
3
+ metadata.gz: 4968b695bb1d272edf951c51e3abb784431bc722
4
+ data.tar.gz: 69f6d6745d9d9a09201477c9f8f84e92734b03eb
5
5
  SHA512:
6
- metadata.gz: 9799adf3dbb5078f797aedf12275283b153e8201c1ab6fa4310bf80e9460911f3077499def817a82f2709cbc4a9fda82be1f88b7b0149b17d45b98cf4f8c237a
7
- data.tar.gz: a1ef697d0b1ae85a414b9a3f65aaef5b108fea491c13edf0f9c17ab965d2dfd2606b276d81762c5d59f185aaba25bb74424b40d04048608dcb6b937d69411c2d
6
+ metadata.gz: 26de5771b3a3c686cdfc868d633bee44bc4748713327e57cfe51da3d6a2ba4e5cae28d6bc8b03e0b4419241dc60032bf653b750c8c31b11b48adf3058773fd04
7
+ data.tar.gz: 65245255419ef81be6697494aa4b2264011d7cbf2e89a2d3c6f7f3a767db68c2caa05c7ae267e9bce1988406e684a9f60e162706d841890c547b5ca40c417757
@@ -5,6 +5,7 @@ rvm:
5
5
  - 2.0.0
6
6
  - 2.1
7
7
  - 2.2.3
8
+ - 2.3.0
8
9
  - ruby-head
9
10
  - rbx-2
10
11
 
@@ -30,3 +31,4 @@ matrix:
30
31
  allow_failures:
31
32
  - rvm: ruby-head
32
33
  - rvm: rbx-2
34
+ - rvm: 2.3.0
data/ChangeLog CHANGED
@@ -1,5 +1,28 @@
1
1
  # v0.12
2
2
 
3
+ ## Release 0.12.20 - 2015/01/26
4
+
5
+ ### New features / Enhancement
6
+
7
+ * in_forward: Add skip_invalid_event paramter to check and skip invalid event
8
+ https://github.com/fluent/fluentd/pull/776
9
+ * in_tail: Add multiline_flush_interval parameter for periodic flush with multiline format
10
+ https://github.com/fluent/fluentd/pull/775
11
+ * filter_record_transformer: Improve ruby placeholder performance and adding 'record["key"]' syntax
12
+ https://github.com/fluent/fluentd/pull/766
13
+ * Add on_exit_process hook point to DetachProcessMixin
14
+ https://github.com/fluent/fluentd/pull/757
15
+ * Add descriptions to BufferedOutput and TimeSlicedOutput
16
+ https://github.com/fluent/fluentd/pull/759
17
+ https://github.com/fluent/fluentd/pull/760
18
+
19
+ ### Bug fixes
20
+
21
+ * parser: Don't use BigDecimal in JSON parsing result with Oj
22
+ https://github.com/fluent/fluentd/pull/778
23
+ * config: Fix the regression of unused parameter warning inside sub section
24
+ https://github.com/fluent/fluentd/pull/765
25
+
3
26
  ## Release 0.12.19 - 2015/12/21
4
27
 
5
28
  ### New features / Enhancement
@@ -75,11 +75,13 @@ module Fluent
75
75
  end
76
76
 
77
77
  def has_key?(key)
78
+ @unused_in = false # some sections, e.g. <store> in copy, is not defined by config_section so clear unused flag for better warning message in chgeck_not_fetched.
78
79
  @unused.delete(key)
79
80
  super
80
81
  end
81
82
 
82
83
  def [](key)
84
+ @unused_in = false # ditto
83
85
  @unused.delete(key)
84
86
  super
85
87
  end
@@ -179,14 +179,22 @@ module Fluent
179
179
  @emit_count = 0
180
180
  end
181
181
 
182
+ desc 'The buffer type (memory, file)'
182
183
  config_param :buffer_type, :string, :default => 'memory'
184
+ desc 'The interval between data flushes.'
183
185
  config_param :flush_interval, :time, :default => 60
184
186
  config_param :try_flush_interval, :float, :default => 1
187
+ desc 'If true, the value of `retry_value` is ignored and there is no limit'
185
188
  config_param :disable_retry_limit, :bool, :default => false
189
+ desc 'The limit on the number of retries before buffered data is discarded'
186
190
  config_param :retry_limit, :integer, :default => 17
191
+ desc 'The initial intervals between write retries.'
187
192
  config_param :retry_wait, :time, :default => 1.0
193
+ desc 'The maximum intervals between write retries.'
188
194
  config_param :max_retry_wait, :time, :default => nil
195
+ desc 'The number of threads to flush the buffer.'
189
196
  config_param :num_threads, :integer, :default => 1
197
+ desc 'The interval between data flushes for queued chunk.'
190
198
  config_param :queued_chunk_flush_interval, :time, :default => 1
191
199
 
192
200
  def configure(conf)
@@ -474,8 +482,11 @@ module Fluent
474
482
  #@ignore_old = false # TODO
475
483
  end
476
484
 
485
+ desc 'The time format used as part of the file name.'
477
486
  config_param :time_slice_format, :string, :default => '%Y%m%d'
487
+ desc 'The amount of time Fluentd will wait for old logs to arrive.'
478
488
  config_param :time_slice_wait, :time, :default => 10*60
489
+ desc 'Parse the time value in the specified timezone'
479
490
  config_param :timezone, :string, :default => nil
480
491
  config_set_default :buffer_type, 'file' # overwrite default buffer_type
481
492
  config_set_default :buffer_chunk_limit, 256*1024*1024 # overwrite default buffer_chunk_limit
@@ -250,6 +250,7 @@ module Fluent
250
250
  begin
251
251
  raise LoadError unless @json_parser == 'oj'
252
252
  require 'oj'
253
+ Oj.default_options = {:bigdecimal_load => :float}
253
254
  @load_proc = Oj.method(:load)
254
255
  @error_class = Oj::ParseError
255
256
  rescue LoadError
@@ -41,12 +41,12 @@ module Fluent
41
41
  def configure(conf)
42
42
  super
43
43
 
44
- @map = {}
44
+ map = {}
45
45
  # <record></record> directive
46
46
  conf.elements.select { |element| element.name == 'record' }.each do |element|
47
47
  element.each_pair do |k, v|
48
48
  element.has_key?(k) # to suppress unread configuration warning
49
- @map[k] = parse_value(v)
49
+ map[k] = parse_value(v)
50
50
  end
51
51
  end
52
52
 
@@ -73,6 +73,7 @@ module Fluent
73
73
  else
74
74
  PlaceholderExpander.new(placeholder_expander_params)
75
75
  end
76
+ @map = @placeholder_expander.preprocess_map(map)
76
77
 
77
78
  @hostname = Socket.gethostname
78
79
  end
@@ -82,17 +83,21 @@ module Fluent
82
83
  tag_parts = tag.split('.')
83
84
  tag_prefix = tag_prefix(tag_parts)
84
85
  tag_suffix = tag_suffix(tag_parts)
85
- placeholders = {
86
- 'tag' => tag,
87
- 'tag_parts' => tag_parts,
86
+ placeholder_values = {
87
+ 'tag' => tag,
88
+ 'tag_parts' => tag_parts,
88
89
  'tag_prefix' => tag_prefix,
89
90
  'tag_suffix' => tag_suffix,
90
- 'hostname' => @hostname,
91
+ 'hostname' => @hostname,
91
92
  }
92
93
  last_record = nil
93
94
  es.each do |time, record|
94
95
  last_record = record # for debug log
95
- new_record = reform(time, record, placeholders)
96
+ placeholder_values.merge!({
97
+ 'time' => @placeholder_expander.time_value(time),
98
+ 'record' => record,
99
+ })
100
+ new_record = reform(record, placeholder_values)
96
101
  if @renew_time_key && new_record.has_key?(@renew_time_key)
97
102
  time = new_record[@renew_time_key].to_i
98
103
  end
@@ -102,7 +107,7 @@ module Fluent
102
107
  rescue => e
103
108
  log.warn "failed to reform records", :error_class => e.class, :error => e.message
104
109
  log.warn_backtrace
105
- log.debug "map:#{@map} record:#{last_record} placeholders:#{placeholders}"
110
+ log.debug "map:#{@map} record:#{last_record} placeholder_values:#{placeholder_values}"
106
111
  end
107
112
 
108
113
  private
@@ -118,8 +123,8 @@ module Fluent
118
123
  value_str # emit as string
119
124
  end
120
125
 
121
- def reform(time, record, opts)
122
- @placeholder_expander.prepare_placeholders(time, record, opts)
126
+ def reform(record, placeholder_values)
127
+ @placeholder_expander.prepare_placeholders(placeholder_values)
123
128
 
124
129
  new_record = @renew_record ? {} : record.dup
125
130
  @keep_keys.each {|k| new_record[k] = record[k]} if @keep_keys and @renew_record
@@ -175,17 +180,29 @@ module Fluent
175
180
  @auto_typecast = params[:auto_typecast]
176
181
  end
177
182
 
178
- def prepare_placeholders(time, record, opts)
179
- placeholders = { '${time}' => Time.at(time).to_s }
180
- record.each {|key, value| placeholders.store("${#{key}}", value) }
183
+ def time_value(time)
184
+ Time.at(time).to_s
185
+ end
186
+
187
+ def preprocess_map(value, force_stringify = false)
188
+ value
189
+ end
181
190
 
182
- opts.each do |key, value|
191
+ def prepare_placeholders(placeholder_values)
192
+ placeholders = {}
193
+
194
+ placeholder_values.each do |key, value|
183
195
  if value.kind_of?(Array) # tag_parts, etc
184
196
  size = value.size
185
- value.each_with_index { |v, idx|
197
+ value.each_with_index do |v, idx|
186
198
  placeholders.store("${#{key}[#{idx}]}", v)
187
199
  placeholders.store("${#{key}[#{idx-size}]}", v) # support [-1]
188
- }
200
+ end
201
+ elsif value.kind_of?(Hash) # record, etc
202
+ value.each do |k, v|
203
+ placeholders.store("${#{k}}", v) # foo
204
+ placeholders.store(%Q[${#{key}["#{k}"]}], v) # record["foo"]
205
+ end
189
206
  else # string, interger, float, and others?
190
207
  placeholders.store("${#{key}}", value)
191
208
  end
@@ -194,22 +211,27 @@ module Fluent
194
211
  @placeholders = placeholders
195
212
  end
196
213
 
197
- def expand(str, force_stringify=false)
214
+ # Expand string with placeholders
215
+ #
216
+ # @param [String] str
217
+ # @param [Boolean] force_stringify the value must be string, used for hash key
218
+ def expand(str, force_stringify = false)
198
219
  if @auto_typecast and !force_stringify
199
220
  single_placeholder_matched = str.match(/\A(\${[^}]+}|__[A-Z_]+__)\z/)
200
221
  if single_placeholder_matched
201
- log_unknown_placeholder($1)
222
+ log_if_unknown_placeholder($1)
202
223
  return @placeholders[single_placeholder_matched[1]]
203
224
  end
204
225
  end
205
226
  str.gsub(/(\${[^}]+}|__[A-Z_]+__)/) {
206
- log_unknown_placeholder($1)
227
+ log_if_unknown_placeholder($1)
207
228
  @placeholders[$1]
208
229
  }
209
230
  end
210
231
 
211
232
  private
212
- def log_unknown_placeholder(placeholder)
233
+
234
+ def log_if_unknown_placeholder(placeholder)
213
235
  unless @placeholders.include?(placeholder)
214
236
  log.warn "unknown placeholder `#{placeholder}` found"
215
237
  end
@@ -217,41 +239,96 @@ module Fluent
217
239
  end
218
240
 
219
241
  class RubyPlaceholderExpander
220
- attr_reader :placeholders, :log
242
+ attr_reader :log
221
243
 
222
244
  def initialize(params)
223
245
  @log = params[:log]
224
246
  @auto_typecast = params[:auto_typecast]
247
+ @cleanroom_expander = CleanroomExpander.new
225
248
  end
226
249
 
227
- # Get placeholders as a struct
228
- #
229
- # @param [Time] time the time
230
- # @param [Hash] record the record
231
- # @param [Hash] opts others
232
- def prepare_placeholders(time, record, opts)
233
- struct = UndefOpenStruct.new(record)
234
- struct.time = Time.at(time)
235
- opts.each {|key, value| struct.__send__("#{key}=", value) }
236
- @placeholders = struct
250
+ def time_value(time)
251
+ Time.at(time)
237
252
  end
238
253
 
239
- def expand(_str_for_eval_, force_stringify=false)
240
- if @auto_typecast and !force_stringify
241
- _single_placeholder_matched_ = _str_for_eval_.match(/\A\${([^}]+)}\z/)
242
- if _single_placeholder_matched_
243
- return eval _single_placeholder_matched_[1], @placeholders.instance_eval { binding }
254
+ # Preprocess record map to convert into ruby string expansion
255
+ #
256
+ # @param [Hash|String|Array] value record map config
257
+ # @param [Boolean] force_stringify the value must be string, used for hash key
258
+ def preprocess_map(value, force_stringify = false)
259
+ new_value = nil
260
+ if value.is_a?(String)
261
+ if @auto_typecast and !force_stringify
262
+ if single_placeholder_matched = value.match(/\A\${([^}]+)}\z/) # ${..} => ..
263
+ new_value = single_placeholder_matched[1]
264
+ end
265
+ end
266
+ unless new_value
267
+ new_value = %Q{%Q[#{value.gsub(/\$\{([^}]+)\}/, '#{\1}')}]} # xx${..}xx => %Q[xx#{..}xx]
268
+ end
269
+ elsif value.is_a?(Hash)
270
+ new_value = {}
271
+ value.each_pair do |k, v|
272
+ new_value[preprocess_map(k, true)] = preprocess_map(v)
244
273
  end
274
+ elsif value.is_a?(Array)
275
+ new_value = []
276
+ value.each_with_index do |v, i|
277
+ new_value[i] = preprocess_map(v)
278
+ end
279
+ else
280
+ new_value = value
245
281
  end
246
- _interpolated_for_eval_ = _str_for_eval_.gsub(/\$\{([^}]+)\}/, '#{\1}') # ${..} => #{..}
247
- eval "\"#{_interpolated_for_eval_}\"", @placeholders.instance_eval { binding }
282
+ new_value
283
+ end
284
+
285
+ def prepare_placeholders(placeholder_values)
286
+ @tag = placeholder_values['tag']
287
+ @time = placeholder_values['time']
288
+ @record = placeholder_values['record']
289
+ @tag_parts = placeholder_values['tag_parts']
290
+ @tag_prefix = placeholder_values['tag_prefix']
291
+ @tag_suffix = placeholder_values['tag_suffix']
292
+ @hostname = placeholder_values['hostname']
293
+ end
294
+
295
+ # Expand string with placeholders
296
+ #
297
+ # @param [String] str
298
+ def expand(str, force_stringify = false)
299
+ @cleanroom_expander.expand(
300
+ str,
301
+ @tag,
302
+ @time,
303
+ @record,
304
+ @tag_parts,
305
+ @tag_prefix,
306
+ @tag_suffix,
307
+ @hostname,
308
+ )
248
309
  rescue => e
249
- log.warn "failed to expand `#{_str_for_eval_}`", :error_class => e.class, :error => e.message
310
+ log.warn "failed to expand `#{str}`", :error_class => e.class, :error => e.message
250
311
  log.warn_backtrace
251
312
  nil
252
313
  end
253
314
 
254
- class UndefOpenStruct < OpenStruct
315
+ class CleanroomExpander
316
+ def expand(__str_to_eval__, tag, time, record, tag_parts, tag_prefix, tag_suffix, hostname)
317
+ tags = tag_parts # for old version compatibility
318
+ @record = record # for old version compatibility
319
+ instance_eval(__str_to_eval__)
320
+ end
321
+
322
+ # for old version compatibility
323
+ def method_missing(name)
324
+ key = name.to_s
325
+ if @record.has_key?(key)
326
+ @record[key]
327
+ else
328
+ raise NameError, "undefined local variable or method `#{key}'"
329
+ end
330
+ end
331
+
255
332
  (Object.instance_methods).each do |m|
256
333
  undef_method m unless m.to_s =~ /^__|respond_to_missing\?|object_id|public_methods|instance_eval|method_missing|define_singleton_method|respond_to\?|new_ostruct_member/
257
334
  end
@@ -38,6 +38,8 @@ module Fluent
38
38
  config_param :chunk_size_warn_limit, :size, :default => nil
39
39
  desc 'Received chunk is dropped if it is larger than this value.'
40
40
  config_param :chunk_size_limit, :size, :default => nil
41
+ desc 'Skip an event if incoming event is invalid.'
42
+ config_param :skip_invalid_event, :bool, :default => false
41
43
 
42
44
  def configure(conf)
43
45
  super
@@ -136,7 +138,7 @@ module Fluent
136
138
  return
137
139
  end
138
140
 
139
- tag = msg[0].to_s
141
+ tag = msg[0]
140
142
  entries = msg[1]
141
143
 
142
144
  if @chunk_size_limit && (chunk_size > @chunk_size_limit)
@@ -149,27 +151,37 @@ module Fluent
149
151
  if entries.class == String
150
152
  # PackedForward
151
153
  es = MessagePackEventStream.new(entries)
154
+ es = check_and_skip_invalid_event(tag, es, source) if @skip_invalid_event
152
155
  router.emit_stream(tag, es)
153
156
  option = msg[2]
154
157
 
155
158
  elsif entries.class == Array
156
159
  # Forward
157
- es = MultiEventStream.new
158
- entries.each {|e|
159
- record = e[1]
160
- next if record.nil?
161
- time = e[0].to_i
162
- time = (now ||= Engine.now) if time == 0
163
- es.add(time, record)
164
- }
160
+ es = if @skip_invalid_event
161
+ check_and_skip_invalid_event(tag, entries, source)
162
+ else
163
+ es = MultiEventStream.new
164
+ entries.each { |e|
165
+ record = e[1]
166
+ next if record.nil?
167
+ time = e[0]
168
+ time = (now ||= Engine.now) if time.to_i == 0
169
+ es.add(time, record)
170
+ }
171
+ es
172
+ end
165
173
  router.emit_stream(tag, es)
166
174
  option = msg[2]
167
175
 
168
176
  else
169
177
  # Message
178
+ time = msg[1]
170
179
  record = msg[2]
180
+ if @skip_invalid_event && invalid_event?(tag, time, record)
181
+ log.warn "got invalid event and drop it:", source: source, tag: tag, time: time, record: record
182
+ return msg[3] # retry never succeeded so return ack and drop incoming event.
183
+ end
171
184
  return if record.nil?
172
- time = msg[1]
173
185
  time = Engine.now if time == 0
174
186
  router.emit(tag, time, record)
175
187
  option = msg[3]
@@ -179,6 +191,22 @@ module Fluent
179
191
  option
180
192
  end
181
193
 
194
+ def invalid_event?(tag, time, record)
195
+ !(time.is_a?(Integer) && record.is_a?(Hash) && tag.is_a?(String))
196
+ end
197
+
198
+ def check_and_skip_invalid_event(tag, es, source)
199
+ new_es = MultiEventStream.new
200
+ es.each { |time, record|
201
+ if invalid_event?(tag, time, record)
202
+ log.warn "skip invalid event:", source: source, tag: tag, time: time, record: record
203
+ next
204
+ end
205
+ new_es.add(time, record)
206
+ }
207
+ new_es
208
+ end
209
+
182
210
  class Handler < Coolio::Socket
183
211
  PEERADDR_FAILED = ["?", "?", "name resolusion failed", "?"]
184
212
 
@@ -40,6 +40,8 @@ module Fluent
40
40
  config_param :refresh_interval, :time, :default => 60
41
41
  desc 'The number of reading lines at each IO.'
42
42
  config_param :read_lines_limit, :integer, :default => 1000
43
+ desc 'The interval of flushing the buffer for multiline format'
44
+ config_param :multiline_flush_interval, :time, :default => nil
43
45
 
44
46
  attr_reader :paths
45
47
 
@@ -147,7 +149,8 @@ module Fluent
147
149
  end
148
150
 
149
151
  def setup_watcher(path, pe)
150
- tw = TailWatcher.new(path, @rotate_wait, pe, log, @read_from_head, @read_lines_limit, method(:update_watcher), &method(:receive_lines))
152
+ line_buffer_timer_flusher = (@multiline_mode && @multiline_flush_interval) ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
153
+ tw = TailWatcher.new(path, @rotate_wait, pe, log, @read_from_head, @read_lines_limit, method(:update_watcher), line_buffer_timer_flusher, &method(:receive_lines))
151
154
  tw.attach(@loop)
152
155
  tw
153
156
  end
@@ -277,6 +280,7 @@ module Fluent
277
280
  lb = tail_watcher.line_buffer
278
281
  es = MultiEventStream.new
279
282
  if @parser.has_firstline?
283
+ tail_watcher.line_buffer_timer_flusher.reset_timer if tail_watcher.line_buffer_timer_flusher
280
284
  lines.each { |line|
281
285
  if @parser.firstline?(line)
282
286
  if lb
@@ -308,7 +312,7 @@ module Fluent
308
312
  end
309
313
 
310
314
  class TailWatcher
311
- def initialize(path, rotate_wait, pe, log, read_from_head, read_lines_limit, update_watcher, &receive_lines)
315
+ def initialize(path, rotate_wait, pe, log, read_from_head, read_lines_limit, update_watcher, line_buffer_timer_flusher, &receive_lines)
312
316
  @path = path
313
317
  @rotate_wait = rotate_wait
314
318
  @pe = pe || MemoryPositionEntry.new
@@ -323,10 +327,12 @@ module Fluent
323
327
  @rotate_handler = RotateHandler.new(path, log, &method(:on_rotate))
324
328
  @io_handler = nil
325
329
  @log = log
330
+
331
+ @line_buffer_timer_flusher = line_buffer_timer_flusher
326
332
  end
327
333
 
328
334
  attr_reader :path
329
- attr_accessor :line_buffer
335
+ attr_accessor :line_buffer, :line_buffer_timer_flusher
330
336
  attr_accessor :unwatched # This is used for removing position entry from PositionFile
331
337
 
332
338
  def tag
@@ -358,6 +364,7 @@ module Fluent
358
364
 
359
365
  def on_notify
360
366
  @rotate_handler.on_notify if @rotate_handler
367
+ @line_buffer_timer_flusher.on_notify(self) if @line_buffer_timer_flusher
361
368
  return unless @io_handler
362
369
  @io_handler.on_notify
363
370
  end
@@ -595,6 +602,30 @@ module Fluent
595
602
  @log.error_backtrace
596
603
  end
597
604
  end
605
+
606
+
607
+ class LineBufferTimerFlusher
608
+ def initialize(log, flush_interval, &flush_method)
609
+ @log = log
610
+ @flush_interval = flush_interval
611
+ @flush_method = flush_method
612
+ @start = nil
613
+ end
614
+
615
+ def on_notify(tw)
616
+ if @start && @flush_interval
617
+ if Time.now - @start >= @flush_interval
618
+ @flush_method.call(tw)
619
+ tw.line_buffer = nil
620
+ @start = nil
621
+ end
622
+ end
623
+ end
624
+
625
+ def reset_timer
626
+ @start = Time.now
627
+ end
628
+ end
598
629
  end
599
630
 
600
631
 
@@ -289,6 +289,9 @@ module Fluent
289
289
  def on_detach_process(i)
290
290
  end
291
291
 
292
+ def on_exit_process(i)
293
+ end
294
+
292
295
  private
293
296
 
294
297
  def detach_process_impl(num, &block)
@@ -326,6 +329,7 @@ module Fluent
326
329
  #forward_thread.join # TODO this thread won't stop because parent doesn't close pipe
327
330
  fin.wait
328
331
 
332
+ on_exit_process(i)
329
333
  exit! 0
330
334
  ensure
331
335
  $log.error "unknown error while shutting down this child process", :error=>$!.to_s, :pid=>Process.pid
@@ -102,7 +102,7 @@ module Fluent
102
102
  false
103
103
  end
104
104
 
105
- def run(&block)
105
+ def run(num_waits = 10, &block)
106
106
  m = method(:emit_stream)
107
107
  Engine.define_singleton_method(:emit_stream) {|tag,es|
108
108
  m.call(tag, es)
@@ -110,7 +110,7 @@ module Fluent
110
110
  instance.router.define_singleton_method(:emit_stream) {|tag,es|
111
111
  m.call(tag, es)
112
112
  }
113
- super {
113
+ super(num_waits) {
114
114
  block.call if block
115
115
 
116
116
  if @expected_emits_length || @expects || @run_post_conditions
@@ -69,9 +69,9 @@ module Fluent
69
69
  (@expected_buffer ||= '') << str
70
70
  end
71
71
 
72
- def run(&block)
72
+ def run(num_waits = 10, &block)
73
73
  result = nil
74
- super {
74
+ super(num_waits) {
75
75
  es = ArrayEventStream.new(@entries)
76
76
  buffer = @instance.format_stream(@tag, es)
77
77
 
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '0.12.19'
19
+ VERSION = '0.12.20'
20
20
 
21
21
  end
@@ -340,6 +340,7 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
340
340
  multiple ${source}${source}
341
341
  with_prefix prefix-${source}
342
342
  with_suffix ${source}-suffix
343
+ with_quote source[""]
343
344
  </record>
344
345
  ]
345
346
  msgs = [
@@ -353,23 +354,28 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
353
354
  { :single => "string",
354
355
  :multiple => "stringstring",
355
356
  :with_prefix => "prefix-string",
356
- :with_suffix => "string-suffix" },
357
+ :with_suffix => "string-suffix",
358
+ :with_quote => %Q{source[""]} },
357
359
  { :single => 123.to_s,
358
360
  :multiple => "#{123.to_s}#{123.to_s}",
359
361
  :with_prefix => "prefix-#{123.to_s}",
360
- :with_suffix => "#{123.to_s}-suffix" },
362
+ :with_suffix => "#{123.to_s}-suffix",
363
+ :with_quote => %Q{source[""]} },
361
364
  { :single => [1, 2].to_s,
362
365
  :multiple => "#{[1, 2].to_s}#{[1, 2].to_s}",
363
366
  :with_prefix => "prefix-#{[1, 2].to_s}",
364
- :with_suffix => "#{[1, 2].to_s}-suffix" },
367
+ :with_suffix => "#{[1, 2].to_s}-suffix",
368
+ :with_quote => %Q{source[""]} },
365
369
  { :single => {a:1, b:2}.to_s,
366
370
  :multiple => "#{{a:1, b:2}.to_s}#{{a:1, b:2}.to_s}",
367
371
  :with_prefix => "prefix-#{{a:1, b:2}.to_s}",
368
- :with_suffix => "#{{a:1, b:2}.to_s}-suffix" },
372
+ :with_suffix => "#{{a:1, b:2}.to_s}-suffix",
373
+ :with_quote => %Q{source[""]} },
369
374
  { :single => nil.to_s,
370
375
  :multiple => "#{nil.to_s}#{nil.to_s}",
371
376
  :with_prefix => "prefix-#{nil.to_s}",
372
- :with_suffix => "#{nil.to_s}-suffix" },
377
+ :with_suffix => "#{nil.to_s}-suffix",
378
+ :with_quote => %Q{source[""]} },
373
379
  ]
374
380
  actual_results = []
375
381
  es = emit(config, msgs)
@@ -379,6 +385,7 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
379
385
  :multiple => r["multiple"],
380
386
  :with_prefix => r["with_prefix"],
381
387
  :with_suffix => r["with_suffix"],
388
+ :with_quote => r["with_quote"],
382
389
  }
383
390
  end
384
391
  assert_equal(expected_results, actual_results)
@@ -436,6 +443,27 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
436
443
  end
437
444
  assert_equal(expected_results, actual_results)
438
445
  end
446
+
447
+ test %Q[record["key"] with enable_ruby #{enable_ruby}] do
448
+ config = %[
449
+ enable_ruby #{enable_ruby}
450
+ auto_typecast yes
451
+ <record>
452
+ _timestamp ${record["@timestamp"]}
453
+ _foo_bar ${record["foo.bar"]}
454
+ </record>
455
+ ]
456
+ d = create_driver(config)
457
+ record = {
458
+ "foo.bar" => "foo.bar",
459
+ "@timestamp" => 10,
460
+ }
461
+ es = d.run { d.emit(record, @time) }.filtered
462
+ es.each do |t, r|
463
+ assert { r['_timestamp'] == record['@timestamp'] }
464
+ assert { r['_foo_bar'] == record['foo.bar'] }
465
+ end
466
+ end
439
467
  end
440
468
 
441
469
  test 'unknown placeholder (enable_ruby no)' do
@@ -458,7 +486,7 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
458
486
  </record>
459
487
  ]
460
488
  es = emit(config) { |d|
461
- mock(d.instance.log).warn("failed to expand `${unknown['bar']}`", anything)
489
+ mock(d.instance.log).warn("failed to expand `%Q[\#{unknown['bar']}]`", anything)
462
490
  }
463
491
  es.each do |t, r|
464
492
  assert_nil(r['message'])
@@ -495,4 +523,36 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
495
523
  end
496
524
  end
497
525
  end
526
+
527
+ test "compatibility test (enable_ruby yes)" do
528
+ config = %[
529
+ enable_ruby yes
530
+ auto_typecast yes
531
+ <record>
532
+ _message prefix-${message}-suffix
533
+ _time ${Time.at(time)}
534
+ _number ${number == '-' ? 0 : number}
535
+ _match ${/0x[0-9a-f]+/.match(hex)[0]}
536
+ _timestamp ${__send__("@timestamp")}
537
+ _foo_bar ${__send__('foo.bar')}
538
+ </record>
539
+ ]
540
+ d = create_driver(config)
541
+ record = {
542
+ "number" => "-",
543
+ "hex" => "0x10",
544
+ "foo.bar" => "foo.bar",
545
+ "@timestamp" => 10,
546
+ "message" => "10",
547
+ }
548
+ es = d.run { d.emit(record, @time) }.filtered
549
+ es.each do |t, r|
550
+ assert { r['_message'] == "prefix-#{record['message']}-suffix" }
551
+ assert { r['_time'] == Time.at(@time) }
552
+ assert { r['_number'] == 0 }
553
+ assert { r['_match'] == record['hex'] }
554
+ assert { r['_timestamp'] == record['@timestamp'] }
555
+ assert { r['_foo_bar'] == record['foo.bar'] }
556
+ end
557
+ end
498
558
  end
@@ -64,6 +64,28 @@ class ForwardInputTest < Test::Unit::TestCase
64
64
  end
65
65
  end
66
66
 
67
+ def test_message_with_skip_invalid_event
68
+ d = create_driver(CONFIG + "skip_invalid_event true")
69
+
70
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
71
+
72
+ d.expect_emit "tag1", time, {"a" => 1}
73
+ d.expect_emit "tag2", time, {"a" => 2}
74
+
75
+ d.run do
76
+ entries = d.expected_emits.map { |tag, time, record| [tag, time, record] }
77
+ # These entries are skipped
78
+ entries << ['tag1', true, {'a' => 3}] << ['tag2', time, 'invalid record']
79
+
80
+ entries.each { |tag, time, record|
81
+ # Without ack, logs are sometimes not saved to logs during test.
82
+ send_data Fluent::Engine.msgpack_factory.packer.write([tag, time, record]).to_s, true
83
+ }
84
+ end
85
+
86
+ assert_equal 2, d.instance.log.logs.count { |line| line =~ /got invalid event and drop it/ }
87
+ end
88
+
67
89
  def test_forward
68
90
  d = create_driver
69
91
 
@@ -81,6 +103,25 @@ class ForwardInputTest < Test::Unit::TestCase
81
103
  end
82
104
  end
83
105
 
106
+ def test_forward_with_skip_invalid_event
107
+ d = create_driver(CONFIG + "skip_invalid_event true")
108
+
109
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
110
+
111
+ d.expect_emit "tag1", time, {"a" => 1}
112
+ d.expect_emit "tag1", time, {"a" => 2}
113
+
114
+ d.run do
115
+ entries = d.expected_emits.map { |tag, time, record| [time, record] }
116
+ # These entries are skipped
117
+ entries << ['invalid time', {'a' => 3}] << [time, 'invalid record']
118
+
119
+ send_data Fluent::Engine.msgpack_factory.packer.write(["tag1", entries]).to_s
120
+ end
121
+
122
+ assert_equal 2, d.instance.log.logs.count { |line| line =~ /skip invalid event/ }
123
+ end
124
+
84
125
  def test_packed_forward
85
126
  d = create_driver
86
127
 
@@ -98,6 +139,29 @@ class ForwardInputTest < Test::Unit::TestCase
98
139
  end
99
140
  end
100
141
 
142
+ def test_packed_forward_with_skip_invalid_event
143
+ d = create_driver(CONFIG + "skip_invalid_event true")
144
+
145
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
146
+
147
+ d.expect_emit "tag1", time, {"a" => 1}
148
+ d.expect_emit "tag1", time, {"a" => 2}
149
+
150
+ d.run do
151
+ entries = d.expected_emits.map { |tag ,time, record| [time, record] }
152
+ # These entries are skipped
153
+ entries << ['invalid time', {'a' => 3}] << [time, 'invalid record']
154
+
155
+ packed_entries = ''
156
+ entries.each { |time, record|
157
+ Fluent::Engine.msgpack_factory.packer(packed_entries).write([time, record]).flush
158
+ }
159
+ send_data Fluent::Engine.msgpack_factory.packer.write(["tag1", packed_entries]).to_s
160
+ end
161
+
162
+ assert_equal 2, d.instance.log.logs.count { |line| line =~ /skip invalid event/ }
163
+ end
164
+
101
165
  def test_message_json
102
166
  d = create_driver
103
167
 
@@ -12,6 +12,11 @@ class TailInputTest < Test::Unit::TestCase
12
12
  FileUtils.mkdir_p(TMP_DIR)
13
13
  end
14
14
 
15
+ def teardown
16
+ super
17
+ Fluent::Engine.stop
18
+ end
19
+
15
20
  TMP_DIR = File.dirname(__FILE__) + "/../tmp/tail#{ENV['TEST_ENV_NUMBER']}"
16
21
 
17
22
  CONFIG = %[
@@ -288,16 +293,61 @@ class TailInputTest < Test::Unit::TestCase
288
293
  f.puts "s test8"
289
294
  }
290
295
  sleep 1
296
+
297
+ emits = d.emits
298
+ assert(emits.length == 3)
299
+ assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, emits[0][2])
300
+ assert_equal({"message1" => "test5"}, emits[1][2])
301
+ assert_equal({"message1" => "test6", "message2" => "test7"}, emits[2][2])
302
+
303
+ sleep 3
304
+ emits = d.emits
305
+ assert(emits.length == 3)
291
306
  end
292
307
 
293
308
  emits = d.emits
294
- assert(emits.length > 0)
295
- assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, emits[0][2])
296
- assert_equal({"message1" => "test5"}, emits[1][2])
297
- assert_equal({"message1" => "test6", "message2" => "test7"}, emits[2][2])
309
+ assert(emits.length == 4)
298
310
  assert_equal({"message1" => "test8"}, emits[3][2])
299
311
  end
300
312
 
313
+ def test_multiline_with_flush_interval
314
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
315
+
316
+ d = create_driver %[
317
+ format multiline
318
+ format1 /^s (?<message1>[^\\n]+)(\\nf (?<message2>[^\\n]+))?(\\nf (?<message3>.*))?/
319
+ format_firstline /^[s]/
320
+ multiline_flush_interval 2s
321
+ ]
322
+
323
+ assert_equal 2, d.instance.multiline_flush_interval
324
+
325
+ d.run do
326
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
327
+ f.puts "f test1"
328
+ f.puts "s test2"
329
+ f.puts "f test3"
330
+ f.puts "f test4"
331
+ f.puts "s test5"
332
+ f.puts "s test6"
333
+ f.puts "f test7"
334
+ f.puts "s test8"
335
+ }
336
+ sleep 1
337
+
338
+ emits = d.emits
339
+ assert(emits.length == 3)
340
+ assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, emits[0][2])
341
+ assert_equal({"message1" => "test5"}, emits[1][2])
342
+ assert_equal({"message1" => "test6", "message2" => "test7"}, emits[2][2])
343
+
344
+ sleep 3
345
+ emits = d.emits
346
+ assert(emits.length == 4)
347
+ assert_equal({"message1" => "test8"}, emits[3][2])
348
+ end
349
+ end
350
+
301
351
  def test_multiline_with_multiple_formats
302
352
  File.open("#{TMP_DIR}/tail.txt", "w") { |f| }
303
353
 
@@ -434,7 +484,7 @@ class TailInputTest < Test::Unit::TestCase
434
484
 
435
485
  flexstub(Fluent::NewTailInput::TailWatcher) do |watcherclass|
436
486
  EX_PATHS.each do |path|
437
- watcherclass.should_receive(:new).with(path, EX_RORATE_WAIT, Fluent::NewTailInput::FilePositionEntry, any, true, 1000, any, any).once.and_return do
487
+ watcherclass.should_receive(:new).with(path, EX_RORATE_WAIT, Fluent::NewTailInput::FilePositionEntry, any, true, 1000, any, any, any).once.and_return do
438
488
  flexmock('TailWatcher') { |watcher|
439
489
  watcher.should_receive(:attach).once
440
490
  watcher.should_receive(:unwatched=).zero_or_more_times
@@ -450,7 +500,7 @@ class TailInputTest < Test::Unit::TestCase
450
500
  end
451
501
 
452
502
  flexstub(Fluent::NewTailInput::TailWatcher) do |watcherclass|
453
- watcherclass.should_receive(:new).with('test/plugin/data/2010/01/20100102-030406.log', EX_RORATE_WAIT, Fluent::NewTailInput::FilePositionEntry, any, true, 1000, any, any).once.and_return do
503
+ watcherclass.should_receive(:new).with('test/plugin/data/2010/01/20100102-030406.log', EX_RORATE_WAIT, Fluent::NewTailInput::FilePositionEntry, any, true, 1000, any, any, any).once.and_return do
454
504
  flexmock('TailWatcher') do |watcher|
455
505
  watcher.should_receive(:attach).once
456
506
  watcher.should_receive(:unwatched=).zero_or_more_times
@@ -574,7 +574,7 @@ module FluentBufferTest
574
574
 
575
575
  begin
576
576
  # with block, emit events to full queue causes sleep loop
577
- timeout(1) {
577
+ Timeout.timeout(1) {
578
578
  assert db.emit('key', data, chain)
579
579
  }
580
580
  flunk("timeout must happen")
@@ -85,7 +85,7 @@ module ParserTest
85
85
  def test_parse_with_invalid_argument
86
86
  parser = TextParser::TimeParser.new(nil)
87
87
 
88
- [[], {}, nil, true, 10000].each { |v|
88
+ [[], {}, nil, true, 10000, //, ->{}, '', :symbol].each { |v|
89
89
  assert_raise Fluent::ParserError do
90
90
  parser.parse(v)
91
91
  end
@@ -386,6 +386,14 @@ module ParserTest
386
386
  }
387
387
  end
388
388
 
389
+ data('oj' => 'oj', 'yajl' => 'yajl')
390
+ def test_parse_with_large_float(data)
391
+ @parser.configure('json_parser' => data)
392
+ @parser.parse('{"num":999999999999999999999999999999.99999}') { |time, record|
393
+ assert_equal(Float, record['num'].class)
394
+ }
395
+ end
396
+
389
397
  data('oj' => 'oj', 'yajl' => 'yajl')
390
398
  def test_parse_without_time(data)
391
399
  time_at_start = Time.now.to_i
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.12.19
4
+ version: 0.12.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sadayuki Furuhashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-21 00:00:00.000000000 Z
11
+ date: 2016-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack