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 +4 -4
- data/.travis.yml +2 -0
- data/ChangeLog +23 -0
- data/lib/fluent/config/element.rb +2 -0
- data/lib/fluent/output.rb +11 -0
- data/lib/fluent/parser.rb +1 -0
- data/lib/fluent/plugin/filter_record_transformer.rb +117 -40
- data/lib/fluent/plugin/in_forward.rb +38 -10
- data/lib/fluent/plugin/in_tail.rb +34 -3
- data/lib/fluent/process.rb +4 -0
- data/lib/fluent/test/input_test.rb +2 -2
- data/lib/fluent/test/output_test.rb +2 -2
- data/lib/fluent/version.rb +1 -1
- data/test/plugin/test_filter_record_transformer.rb +66 -6
- data/test/plugin/test_in_forward.rb +64 -0
- data/test/plugin/test_in_tail.rb +56 -6
- data/test/test_buffer.rb +1 -1
- data/test/test_parser.rb +9 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4968b695bb1d272edf951c51e3abb784431bc722
|
4
|
+
data.tar.gz: 69f6d6745d9d9a09201477c9f8f84e92734b03eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26de5771b3a3c686cdfc868d633bee44bc4748713327e57cfe51da3d6a2ba4e5cae28d6bc8b03e0b4419241dc60032bf653b750c8c31b11b48adf3058773fd04
|
7
|
+
data.tar.gz: 65245255419ef81be6697494aa4b2264011d7cbf2e89a2d3c6f7f3a767db68c2caa05c7ae267e9bce1988406e684a9f60e162706d841890c547b5ca40c417757
|
data/.travis.yml
CHANGED
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
|
data/lib/fluent/output.rb
CHANGED
@@ -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
|
data/lib/fluent/parser.rb
CHANGED
@@ -41,12 +41,12 @@ module Fluent
|
|
41
41
|
def configure(conf)
|
42
42
|
super
|
43
43
|
|
44
|
-
|
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
|
-
|
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
|
-
|
86
|
-
'tag'
|
87
|
-
'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'
|
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
|
-
|
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}
|
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(
|
122
|
-
@placeholder_expander.prepare_placeholders(
|
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
|
179
|
-
|
180
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
227
|
+
log_if_unknown_placeholder($1)
|
207
228
|
@placeholders[$1]
|
208
229
|
}
|
209
230
|
end
|
210
231
|
|
211
232
|
private
|
212
|
-
|
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 :
|
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
|
-
|
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
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
247
|
-
|
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 `#{
|
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
|
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]
|
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 =
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
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
|
|
data/lib/fluent/process.rb
CHANGED
@@ -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
|
data/lib/fluent/version.rb
CHANGED
@@ -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
|
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
|
|
data/test/plugin/test_in_tail.rb
CHANGED
@@ -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
|
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
|
data/test/test_buffer.rb
CHANGED
data/test/test_parser.rb
CHANGED
@@ -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.
|
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:
|
11
|
+
date: 2016-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: msgpack
|