fluentd 0.14.20.rc1 → 0.14.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: 27bc8dbc0114bfd96c3f6eea899abb46036b00b3
4
- data.tar.gz: 731f9f97662048edae1476538510747a6279b52d
3
+ metadata.gz: 419af849ffe16bdb83d13106862b94018d71dca1
4
+ data.tar.gz: 6f1435c4d5f9f1645267bb88f43aa86ee838075e
5
5
  SHA512:
6
- metadata.gz: 7cb206015a95afcfa03a96c455e9f9631e3b2357f63d76d67dd198a9a435d7d6f91bf1dc9f81690b66d885bc458dfcbc2611a139ef46548841d081488e818f05
7
- data.tar.gz: 33c0185e918d5b78810a86226d9f17bead748ca4f24574f3b633143bbbf1d0d30cab99d8a6e950084ff77443899f145d0440562ec8b442c6c9e0d54ca8a8050c
6
+ metadata.gz: 96ff26acaccd6870648fd36ca991e101058296abfa8585366c0b132d94a19c79ef75214527c026b3719061c943d0d0aeaec45c3bb58655b3f08f7da88d5a6aa6
7
+ data.tar.gz: a83370feeeabb3f9a3200f5575ce8ba9f4fe0a57349b9fe73dfd3f6b386492993483412883f91214b8903709a86dcacccac658cf663d1734aa37d5b1e4f5fa21
data/ChangeLog CHANGED
@@ -1,5 +1,25 @@
1
1
  # v0.14
2
2
 
3
+ ## Release v0.14.20 - 2017/07/31
4
+
5
+ ### New features / Enhancements
6
+
7
+ * plugin: Add record_accessor plugin helper
8
+ https://github.com/fluent/fluentd/pull/1637
9
+ * log: Add format and time_format parameters to <system> setting
10
+ https://github.com/fluent/fluentd/pull/1644
11
+
12
+ ### Bug fixes
13
+
14
+ * buf_file: Improve file handling to mitigate broken meta file
15
+ https://github.com/fluent/fluentd/pull/1628
16
+ * in_syslog: Fix the description of resolve_hostname parameter
17
+ https://github.com/fluent/fluentd/pull/1633
18
+ * process: Fix signal handling. Send signal to all workers
19
+ https://github.com/fluent/fluentd/pull/1642
20
+ * output: Fix error message typo
21
+ https://github.com/fluent/fluentd/pull/1643
22
+
3
23
  ## Release v0.14.19 - 2017/07/12
4
24
 
5
25
  ### New features / Enhancements
@@ -28,6 +28,7 @@ Gem::Specification.new do |gem|
28
28
  gem.add_runtime_dependency("tzinfo", ["~> 1.0"])
29
29
  gem.add_runtime_dependency("tzinfo-data", ["~> 1.0"])
30
30
  gem.add_runtime_dependency("strptime", ["~> 0.1.7"])
31
+ gem.add_runtime_dependency("ruby_dig", ["~> 0.0.2"])
31
32
 
32
33
  # build gem for a certain platform. see also Rakefile
33
34
  fake_platform = ENV['GEM_BUILD_FAKE_PLATFORM'].to_s
@@ -96,8 +96,12 @@ module Fluent
96
96
  @level = logger.level + 1
97
97
  @debug_mode = false
98
98
  @log_event_enabled = false
99
- @time_format = '%Y-%m-%d %H:%M:%S %z '
100
99
  @depth_offset = 1
100
+ @format = nil
101
+ @time_format = nil
102
+ @formatter = nil
103
+
104
+ self.format = :text
101
105
  enable_color out.tty?
102
106
  # TODO: This variable name is unclear so we should change to better name.
103
107
  @threads_exclude_events = []
@@ -136,12 +140,14 @@ module Fluent
136
140
  dl_opts[:log_level] = @level - 1
137
141
  logger = ServerEngine::DaemonLogger.new(@out, dl_opts)
138
142
  clone = self.class.new(logger, suppress_repeated_stacktrace: @suppress_repeated_stacktrace, process_type: @process_type, worker_id: @worker_id)
143
+ clone.format = @format
139
144
  clone.time_format = @time_format
140
145
  clone.log_event_enabled = @log_event_enabled
141
146
  # optional headers/attrs are not copied, because new PluginLogger should have another one of it
142
147
  clone
143
148
  end
144
149
 
150
+ attr_reader :format
145
151
  attr_accessor :log_event_enabled
146
152
  attr_accessor :out
147
153
  attr_accessor :level
@@ -154,6 +160,37 @@ module Fluent
154
160
  nil
155
161
  end
156
162
 
163
+ def format=(fmt)
164
+ return if @format == fmt
165
+
166
+ case fmt
167
+ when :text
168
+ @format = :text
169
+ @time_format = '%Y-%m-%d %H:%M:%S %z'
170
+ @formatter = Proc.new { |type, time, level, msg|
171
+ r = caller_line(type, time, @depth_offset, level)
172
+ r << msg
173
+ r
174
+ }
175
+ when :json
176
+ @format = :json
177
+ @time_format = '%Y-%m-%d %H:%M:%S %z'
178
+ @formatter = Proc.new { |type, time, level, msg|
179
+ r = {
180
+ 'time' => time.strftime(@time_format),
181
+ 'level' => LEVEL_TEXT[level],
182
+ 'message' => msg
183
+ }
184
+ if wid = get_worker_id(type)
185
+ r['worker_id'] = wid
186
+ end
187
+ Yajl.dump(r)
188
+ }
189
+ end
190
+
191
+ nil
192
+ end
193
+
157
194
  def reopen!
158
195
  # do nothing in @logger.reopen! because it's already reopened in Supervisor.load_config
159
196
  @logger.reopen! if @logger
@@ -235,7 +272,7 @@ module Fluent
235
272
  return if skipped_type?(type)
236
273
  args << block.call if block
237
274
  time, msg = event(:trace, args)
238
- puts [@color_trace, caller_line(type, time, @depth_offset, LEVEL_TRACE), msg, @color_reset].join
275
+ puts [@color_trace, @formatter.call(type, time, LEVEL_TRACE, msg), @color_reset].join
239
276
  rescue
240
277
  # logger should not raise an exception. This rescue prevents unexpected behaviour.
241
278
  end
@@ -256,7 +293,7 @@ module Fluent
256
293
  return if skipped_type?(type)
257
294
  args << block.call if block
258
295
  time, msg = event(:debug, args)
259
- puts [@color_debug, caller_line(type, time, @depth_offset, LEVEL_DEBUG), msg, @color_reset].join
296
+ puts [@color_debug, @formatter.call(type, time, LEVEL_DEBUG, msg), @color_reset].join
260
297
  rescue
261
298
  end
262
299
  alias DEBUG debug
@@ -276,7 +313,7 @@ module Fluent
276
313
  return if skipped_type?(type)
277
314
  args << block.call if block
278
315
  time, msg = event(:info, args)
279
- puts [@color_info, caller_line(type, time, @depth_offset, LEVEL_INFO), msg, @color_reset].join
316
+ puts [@color_info, @formatter.call(type, time, LEVEL_INFO, msg), @color_reset].join
280
317
  rescue
281
318
  end
282
319
  alias INFO info
@@ -296,7 +333,7 @@ module Fluent
296
333
  return if skipped_type?(type)
297
334
  args << block.call if block
298
335
  time, msg = event(:warn, args)
299
- puts [@color_warn, caller_line(type, time, @depth_offset, LEVEL_WARN), msg, @color_reset].join
336
+ puts [@color_warn, @formatter.call(type, time, LEVEL_WARN, msg), @color_reset].join
300
337
  rescue
301
338
  end
302
339
  alias WARN warn
@@ -316,7 +353,7 @@ module Fluent
316
353
  return if skipped_type?(type)
317
354
  args << block.call if block
318
355
  time, msg = event(:error, args)
319
- puts [@color_error, caller_line(type, time, @depth_offset, LEVEL_ERROR), msg, @color_reset].join
356
+ puts [@color_error, @formatter.call(type, time, LEVEL_ERROR, msg), @color_reset].join
320
357
  rescue
321
358
  end
322
359
  alias ERROR error
@@ -336,7 +373,7 @@ module Fluent
336
373
  return if skipped_type?(type)
337
374
  args << block.call if block
338
375
  time, msg = event(:fatal, args)
339
- puts [@color_fatal, caller_line(type, time, @depth_offset, LEVEL_FATAL), msg, @color_reset].join
376
+ puts [@color_fatal, @formatter.call(type, time, LEVEL_FATAL, msg), @color_reset].join
340
377
  rescue
341
378
  end
342
379
  alias FATAL fatal
@@ -373,19 +410,47 @@ module Fluent
373
410
  return if @level > level
374
411
 
375
412
  time = Time.now
376
- line = caller_line(type, time, 5, level)
377
- if @suppress_repeated_stacktrace && (Thread.current[:last_repeated_stacktrace] == backtrace)
378
- puts [" ", line, 'suppressed same stacktrace'].join
413
+
414
+ if @format == :text
415
+ line = caller_line(type, time, 5, level)
416
+ if @suppress_repeated_stacktrace && (Thread.current[:last_repeated_stacktrace] == backtrace)
417
+ puts [" ", line, 'suppressed same stacktrace'].join
418
+ else
419
+ backtrace.each { |msg|
420
+ puts [" ", line, msg].join
421
+ }
422
+ Thread.current[:last_repeated_stacktrace] = backtrace if @suppress_repeated_stacktrace
423
+ end
379
424
  else
380
- backtrace.each { |msg|
381
- puts [" ", line, msg].join
425
+ r = {
426
+ 'time' => time.strftime(@time_format),
427
+ 'level' => LEVEL_TEXT[level],
382
428
  }
383
- Thread.current[:last_repeated_stacktrace] = backtrace if @suppress_repeated_stacktrace
429
+ if wid = get_worker_id(type)
430
+ r['worker_id'] = wid
431
+ end
432
+
433
+ if @suppress_repeated_stacktrace && (Thread.current[:last_repeated_stacktrace] == backtrace)
434
+ r['message'] = 'suppressed same stacktrace'
435
+ else
436
+ r['message'] = backtrace.join("\n")
437
+ Thread.current[:last_repeated_stacktrace] = backtrace if @suppress_repeated_stacktrace
438
+ end
439
+
440
+ puts Yajl.dump(r)
384
441
  end
385
442
 
386
443
  nil
387
444
  end
388
445
 
446
+ def get_worker_id(type)
447
+ if type == :default && (@process_type == :worker0 || @process_type == :workers)
448
+ @worker_id
449
+ else
450
+ nil
451
+ end
452
+ end
453
+
389
454
  def event(level, args)
390
455
  time = Time.now
391
456
  message = @optional_header ? @optional_header.dup : ''
@@ -426,7 +491,7 @@ module Fluent
426
491
  else
427
492
  "".freeze
428
493
  end
429
- log_msg = "#{time.strftime(@time_format)}[#{LEVEL_TEXT[level]}]: #{worker_id_part}"
494
+ log_msg = "#{time.strftime(@time_format)} [#{LEVEL_TEXT[level]}]: #{worker_id_part}"
430
495
  if @debug_mode
431
496
  line = caller(depth+1)[0]
432
497
  if match = /^(.+?):(\d+)(?::in `(.*)')?/.match(line)
@@ -450,11 +515,13 @@ module Fluent
450
515
  def initialize(logger)
451
516
  @logger = logger
452
517
  @level = @logger.level
518
+ @format = nil
453
519
  @depth_offset = 2
454
520
  if logger.instance_variable_defined?(:@suppress_repeated_stacktrace)
455
521
  @suppress_repeated_stacktrace = logger.instance_variable_get(:@suppress_repeated_stacktrace)
456
522
  end
457
523
 
524
+ self.format = @logger.format
458
525
  enable_color @logger.enable_color?
459
526
  end
460
527
 
@@ -462,15 +529,21 @@ module Fluent
462
529
  @level = Log.str_to_level(log_level_str)
463
530
  end
464
531
 
532
+ alias orig_format= format=
465
533
  alias orig_enable_color enable_color
466
534
 
535
+ def format=(fmt)
536
+ self.orig_format = fmt
537
+ @logger.format = fmt
538
+ end
539
+
467
540
  def enable_color(b = true)
468
541
  orig_enable_color b
469
542
  @logger.enable_color b
470
543
  end
471
544
 
472
545
  extend Forwardable
473
- def_delegators '@logger', :enable_color?, :enable_debug, :enable_event,
546
+ def_delegators '@logger', :get_worker_id, :enable_color?, :enable_debug, :enable_event,
474
547
  :disable_events, :log_event_enabled, :log_event_enamed=, :time_format, :time_format=,
475
548
  :event, :caller_line, :puts, :write, :<<, :flush, :reset, :out, :out=,
476
549
  :optional_header, :optional_header=, :optional_attrs, :optional_attrs=
@@ -22,6 +22,15 @@ module Fluent::Plugin
22
22
  class GrepFilter < Filter
23
23
  Fluent::Plugin.register_filter('grep', self)
24
24
 
25
+ def initialize
26
+ super
27
+
28
+ @_regexps = {}
29
+ @_excludes = {}
30
+ end
31
+
32
+ helpers :record_accessor
33
+
25
34
  REGEXP_MAX_NUM = 20
26
35
 
27
36
  (1..REGEXP_MAX_NUM).each {|i| config_param :"regexp#{i}", :string, default: nil, deprecated: "Use <regexp> section" }
@@ -52,31 +61,38 @@ module Fluent::Plugin
52
61
  def configure(conf)
53
62
  super
54
63
 
55
- @_regexps = {}
64
+ rs = {}
56
65
  (1..REGEXP_MAX_NUM).each do |i|
57
66
  next unless conf["regexp#{i}"]
58
67
  key, regexp = conf["regexp#{i}"].split(/ /, 2)
59
68
  raise Fluent::ConfigError, "regexp#{i} does not contain 2 parameters" unless regexp
60
- raise Fluent::ConfigError, "regexp#{i} contains a duplicated key, #{key}" if @_regexps[key]
61
- @_regexps[key] = Regexp.compile(regexp)
69
+ raise Fluent::ConfigError, "regexp#{i} contains a duplicated key, #{key}" if rs[key]
70
+ rs[key] = Regexp.compile(regexp)
62
71
  end
63
72
 
64
- @_excludes = {}
73
+ es = {}
65
74
  (1..REGEXP_MAX_NUM).each do |i|
66
75
  next unless conf["exclude#{i}"]
67
76
  key, exclude = conf["exclude#{i}"].split(/ /, 2)
68
77
  raise Fluent::ConfigError, "exclude#{i} does not contain 2 parameters" unless exclude
69
- raise Fluent::ConfigError, "exclude#{i} contains a duplicated key, #{key}" if @_excludes[key]
70
- @_excludes[key] = Regexp.compile(exclude)
78
+ raise Fluent::ConfigError, "exclude#{i} contains a duplicated key, #{key}" if es[key]
79
+ es[key] = Regexp.compile(exclude)
71
80
  end
72
81
 
73
82
  @regexps.each do |e|
74
- raise Fluent::ConfigError, "Duplicate key: #{e.key}" if @_regexps.key?(e.key)
75
- @_regexps[e.key] = e.pattern
83
+ raise Fluent::ConfigError, "Duplicate key: #{e.key}" if rs.key?(e.key)
84
+ rs[e.key] = e.pattern
76
85
  end
77
86
  @excludes.each do |e|
78
- raise Fluent::ConfigError, "Duplicate key: #{e.key}" if @_excludes.key?(e.key)
79
- @_excludes[e.key] = e.pattern
87
+ raise Fluent::ConfigError, "Duplicate key: #{e.key}" if es.key?(e.key)
88
+ es[e.key] = e.pattern
89
+ end
90
+
91
+ rs.each_pair do |k, v|
92
+ @_regexps[record_accessor_create(k)] = v
93
+ end
94
+ es.each_pair do |k, v|
95
+ @_excludes[record_accessor_create(k)] = v
80
96
  end
81
97
  end
82
98
 
@@ -85,10 +101,10 @@ module Fluent::Plugin
85
101
  begin
86
102
  catch(:break_loop) do
87
103
  @_regexps.each do |key, regexp|
88
- throw :break_loop unless ::Fluent::StringUtil.match_regexp(regexp, record[key].to_s)
104
+ throw :break_loop unless ::Fluent::StringUtil.match_regexp(regexp, key.call(record).to_s)
89
105
  end
90
106
  @_excludes.each do |key, exclude|
91
- throw :break_loop if ::Fluent::StringUtil.match_regexp(exclude, record[key].to_s)
107
+ throw :break_loop if ::Fluent::StringUtil.match_regexp(exclude, key.call(record).to_s)
92
108
  end
93
109
  result = record
94
110
  end
@@ -83,8 +83,8 @@ module Fluent::Plugin
83
83
 
84
84
  desc 'The field name of hostname of sender.'
85
85
  config_param :source_hostname_key, :string, default: nil
86
+ desc 'Try to resolve hostname from IP addresses or not.'
86
87
  config_param :resolve_hostname, :bool, default: nil
87
- desc 'Connections will be disconnected right after receiving first message if this value is true.'
88
88
  desc 'The field name of source address of sender.'
89
89
  config_param :source_address_key, :string, default: nil
90
90
  desc 'The field name of the priority.'
@@ -700,7 +700,7 @@ module Fluent
700
700
  rvalue = rvalue.gsub(CHUNK_KEY_PLACEHOLDER_PATTERN, hash)
701
701
  end
702
702
  if rvalue =~ CHUNK_KEY_PLACEHOLDER_PATTERN
703
- log.warn "chunk key placeholder '#{$1}' not replaced. templace:#{str}"
703
+ log.warn "chunk key placeholder '#{$1}' not replaced. template:#{str}"
704
704
  end
705
705
  rvalue
706
706
  end
@@ -27,6 +27,7 @@ require 'fluent/plugin_helper/extract'
27
27
  require 'fluent/plugin_helper/socket'
28
28
  require 'fluent/plugin_helper/server'
29
29
  require 'fluent/plugin_helper/retry_state'
30
+ require 'fluent/plugin_helper/record_accessor'
30
31
  require 'fluent/plugin_helper/compat_parameters'
31
32
 
32
33
  module Fluent
@@ -0,0 +1,172 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'fluent/config/error'
18
+ unless {}.respond_to?(:dig)
19
+ begin
20
+ # backport_dig is faster than ruby_dig so prefer backport_dig.
21
+ require 'backport_dig'
22
+ rescue LoadError
23
+ require 'ruby_dig'
24
+ end
25
+ end
26
+
27
+ module Fluent
28
+ module PluginHelper
29
+ module RecordAccessor
30
+ def record_accessor_create(param)
31
+ Accessor.new(param)
32
+ end
33
+
34
+ class Accessor
35
+ def initialize(param)
36
+ @keys = Accessor.parse_parameter(param)
37
+
38
+ # Call [] for single key to reduce dig overhead
39
+ m = method(@keys.is_a?(Array) ? :call_dig : :call_index)
40
+ (class << self; self; end).module_eval do
41
+ define_method(:call, m)
42
+ end
43
+ end
44
+
45
+ def call(r)
46
+ end
47
+
48
+ # To optimize the performance, use class_eval with pre-expanding @keys
49
+ # See https://gist.github.com/repeatedly/ab553ed260cd080bd01ec71da9427312
50
+ def call_dig(r)
51
+ r.dig(*@keys)
52
+ end
53
+
54
+ def call_index(r)
55
+ r[@keys]
56
+ end
57
+
58
+ def self.parse_parameter(param)
59
+ if param.start_with?('$.')
60
+ parse_dot_notation(param)
61
+ elsif param.start_with?('$[')
62
+ parse_bracket_notation(param)
63
+ else
64
+ param
65
+ end
66
+ end
67
+
68
+ def self.parse_dot_notation(param)
69
+ result = []
70
+ keys = param[2..-1].split('.')
71
+ keys.each { |key|
72
+ if key.include?('[')
73
+ result.concat(parse_dot_array_op(key, param))
74
+ else
75
+ result << key
76
+ end
77
+ }
78
+
79
+ raise Fluent::ConfigError, "empty keys in dot notation" if result.empty?
80
+ validate_dot_keys(result)
81
+
82
+ result
83
+ end
84
+
85
+ def self.validate_dot_keys(keys)
86
+ keys.each { |key|
87
+ next unless key.is_a?(String)
88
+ if /\s+/.match(key)
89
+ raise Fluent::ConfigError, "whitespace character is not allowed in dot notation. Use bracket notation: #{key}"
90
+ end
91
+ }
92
+ end
93
+
94
+ def self.parse_dot_array_op(key, param)
95
+ start = key.index('[')
96
+ result = if start.zero?
97
+ []
98
+ else
99
+ [key[0..start - 1]]
100
+ end
101
+ key = key[start + 1..-1]
102
+ in_bracket = true
103
+
104
+ until key.empty?
105
+ if in_bracket
106
+ if i = key.index(']')
107
+ index_value = key[0..i - 1]
108
+ raise Fluent::ConfigError, "missing array index in '[]'. Invalid syntax: #{param}" if index_value == ']'
109
+ result << Integer(index_value)
110
+ key = key[i + 1..-1]
111
+ in_bracket = false
112
+ else
113
+ raise Fluent::ConfigError, "'[' found but ']' not found. Invalid syntax: #{param}"
114
+ end
115
+ else
116
+ if i = key.index('[')
117
+ key = key[i + 1..-1]
118
+ in_bracket = true
119
+ else
120
+ raise Fluent::ConfigError, "found more characters after ']'. Invalid syntax: #{param}"
121
+ end
122
+ end
123
+ end
124
+
125
+ result
126
+ end
127
+
128
+ def self.parse_bracket_notation(param)
129
+ orig_param = param
130
+ result = []
131
+ param = param[1..-1]
132
+ in_bracket = false
133
+
134
+ until param.empty?
135
+ if in_bracket
136
+ if param[0] == "'"
137
+ if i = param.index("']")
138
+ result << param[1..i - 1]
139
+ param = param[i + 2..-1]
140
+ in_bracket = false
141
+ else
142
+ raise Fluent::ConfigError, "Incomplete bracket. Invalid syntax: #{orig_param}"
143
+ end
144
+ else
145
+ if i = param.index(']')
146
+ index_value = param[0..i - 1]
147
+ raise Fluent::ConfigError, "missing array index in '[]'. Invalid syntax: #{param}" if index_value == ']'
148
+ result << Integer(index_value)
149
+ param = param[i + 1..-1]
150
+ in_bracket = false
151
+ else
152
+ raise Fluent::ConfigError, "'[' found but ']' not found. Invalid syntax: #{orig_param}"
153
+ end
154
+ end
155
+ else
156
+ if i = param.index('[')
157
+ param = param[i + 1..-1]
158
+ in_bracket = true
159
+ else
160
+ raise Fluent::ConfigError, "found more characters after ']'. Invalid syntax: #{orig_param}"
161
+ end
162
+ end
163
+ end
164
+
165
+ raise Fluent::ConfigError, "empty keys in bracket notation" if result.empty?
166
+
167
+ result
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -163,18 +163,22 @@ module Fluent
163
163
  log.reopen!
164
164
  end
165
165
 
166
- if pid = config[:worker_pid]
167
- Process.kill(:USR1, pid)
168
- # don't rescue Erro::ESRSH here (invalid status)
166
+ if config[:worker_pid]
167
+ config[:worker_pid].each do |pid|
168
+ Process.kill(:USR1, pid)
169
+ # don't rescue Erro::ESRSH here (invalid status)
170
+ end
169
171
  end
170
172
  end
171
173
 
172
174
  def kill_worker
173
- if pid = config[:worker_pid]
174
- if Fluent.windows?
175
- Process.kill :KILL, pid
176
- else
177
- Process.kill :TERM, pid
175
+ if config[:worker_pid]
176
+ config[:worker_pid].each do |pid|
177
+ if Fluent.windows?
178
+ Process.kill :KILL, pid
179
+ else
180
+ Process.kill :TERM, pid
181
+ end
178
182
  end
179
183
  end
180
184
  end
@@ -198,7 +202,7 @@ module Fluent
198
202
  end
199
203
 
200
204
  def after_start
201
- config[:worker_pid] = @pm.pid
205
+ (config[:worker_pid] ||= []).push(@pm.pid)
202
206
  end
203
207
  end
204
208
 
@@ -360,6 +364,11 @@ module Fluent
360
364
  self
361
365
  end
362
366
 
367
+ def apply_options(opts)
368
+ $log.format = opts[:format] if opts[:format]
369
+ $log.time_format = opts[:time_format] if opts[:time_format]
370
+ end
371
+
363
372
  def level=(level)
364
373
  @level = level
365
374
  $log.level = level
@@ -439,6 +448,9 @@ module Fluent
439
448
  show_plugin_config if @show_plugin_config
440
449
  read_config
441
450
  set_system_config
451
+ @log.apply_options(format: @system_config.log.format, time_format: @system_config.log.time_format)
452
+
453
+ $log.info :supervisor, "parsing config file is succeeded", path: @config_path
442
454
 
443
455
  if @workers < 1
444
456
  raise Fluent::ConfigError, "invalid number of workers (must be > 0):#{@workers}"
@@ -485,11 +497,12 @@ module Fluent
485
497
  else :workers
486
498
  end
487
499
  @log.init(process_type, worker_id)
488
- Process.setproctitle("worker:#{@process_name}") if @process_name
489
-
490
500
  show_plugin_config if @show_plugin_config
491
501
  read_config
492
502
  set_system_config
503
+ @log.apply_options(format: @system_config.log.format, time_format: @system_config.log.time_format)
504
+
505
+ Process.setproctitle("worker:#{@process_name}") if @process_name
493
506
 
494
507
  if @standalone_worker && @workers != 1
495
508
  raise Fluent::ConfigError, "invalid number of workers (must be 1 or unspecified) with --no-supervisor: #{@workers}"
@@ -715,7 +728,6 @@ module Fluent
715
728
  end
716
729
 
717
730
  def read_config
718
- $log.info :supervisor, "reading config file", path: @config_path
719
731
  @config_fname = File.basename(@config_path)
720
732
  @config_basedir = File.dirname(@config_path)
721
733
  @config_data = File.open(@config_path, "r:utf-8:utf-8") {|f| f.read }
@@ -46,6 +46,10 @@ module Fluent
46
46
  config_param :dir_permission, default: nil do |v|
47
47
  v.to_i(8)
48
48
  end
49
+ config_section :log, required: false, init: true, multi: false do
50
+ config_param :format, :enum, list: [:text, :json], default: :text
51
+ config_param :time_format, :string, default: '%Y-%m-%d %H:%M:%S %z'
52
+ end
49
53
 
50
54
  def self.create(conf)
51
55
  systems = conf.elements(name: 'system')
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '0.14.20.rc1'
19
+ VERSION = '0.14.20'
20
20
 
21
21
  end
@@ -55,6 +55,8 @@ module Fluent::Config
55
55
  assert_nil(sc.emit_error_log_interval)
56
56
  assert_nil(sc.suppress_config_dump)
57
57
  assert_nil(sc.without_source)
58
+ assert_equal(:text, sc.log.format)
59
+ assert_equal('%Y-%m-%d %H:%M:%S %z', sc.log.time_format)
58
60
  assert_equal(1, s.instance_variable_get(:@workers))
59
61
  assert_nil(s.instance_variable_get(:@root_dir))
60
62
  assert_equal(Fluent::Log::LEVEL_INFO, s.instance_variable_get(:@log_level))
@@ -91,6 +93,22 @@ module Fluent::Config
91
93
  assert_not_nil(s.instance_variable_get("@#{key}"))
92
94
  end
93
95
 
96
+ test "log parameters" do
97
+ conf = parse_text(<<-EOS)
98
+ <system>
99
+ <log>
100
+ format json
101
+ time_format %Y
102
+ </log>
103
+ </system>
104
+ EOS
105
+ s = FakeSupervisor.new
106
+ sc = Fluent::SystemConfig.new(conf)
107
+ sc.apply(s)
108
+ assert_equal(:json, sc.log.format)
109
+ assert_equal('%Y', sc.log.time_format)
110
+ end
111
+
94
112
  data(
95
113
  'foo' => ['foo', 'bar'],
96
114
  'hoge' => ['hoge', 'fuga'],
@@ -23,12 +23,16 @@ class GrepFilterTest < Test::Unit::TestCase
23
23
 
24
24
  test "regexpN can contain a space" do
25
25
  d = create_driver(%[regexp1 message foo])
26
- assert_equal(Regexp.compile(/ foo/), d.instance._regexps['message'])
26
+ d.instance._regexps.each_pair { |key, value|
27
+ assert_equal(Regexp.compile(/ foo/), value)
28
+ }
27
29
  end
28
30
 
29
31
  test "excludeN can contain a space" do
30
32
  d = create_driver(%[exclude1 message foo])
31
- assert_equal(Regexp.compile(/ foo/), d.instance._excludes['message'])
33
+ d.instance._excludes.each_pair { |key, value|
34
+ assert_equal(Regexp.compile(/ foo/), value)
35
+ }
32
36
  end
33
37
 
34
38
  sub_test_case "duplicate key" do
@@ -163,6 +167,58 @@ class GrepFilterTest < Test::Unit::TestCase
163
167
  end
164
168
  end
165
169
 
170
+ sub_test_case 'nested keys' do
171
+ def messages
172
+ [
173
+ {"nest1" => {"nest2" => "INFO"}},
174
+ {"nest1" => {"nest2" => "WARN"}},
175
+ {"nest1" => {"nest2" => "WARN"}}
176
+ ]
177
+ end
178
+
179
+ def filter(config, msgs)
180
+ d = create_driver(config)
181
+ d.run {
182
+ msgs.each { |msg|
183
+ d.feed("filter.test", @time, {'foo' => 'bar', 'message' => msg})
184
+ }
185
+ }
186
+ d.filtered_records
187
+ end
188
+
189
+ test 'regexps' do
190
+ conf = %[
191
+ <regexp>
192
+ key $.message.nest1.nest2
193
+ pattern WARN
194
+ </regexp>
195
+ ]
196
+ filtered_records = filter(conf, messages)
197
+ assert_equal(2, filtered_records.size)
198
+ assert_block('only 2 nested logs') do
199
+ filtered_records.all? { |r|
200
+ r['message']['nest1']['nest2'] == 'WARN'
201
+ }
202
+ end
203
+ end
204
+
205
+ test 'excludes' do
206
+ conf = %[
207
+ <exclude>
208
+ key $.message.nest1.nest2
209
+ pattern WARN
210
+ </exclude>
211
+ ]
212
+ filtered_records = filter(conf, messages)
213
+ assert_equal(1, filtered_records.size)
214
+ assert_block('only 2 nested logs') do
215
+ filtered_records.all? { |r|
216
+ r['message']['nest1']['nest2'] == 'INFO'
217
+ }
218
+ end
219
+ end
220
+ end
221
+
166
222
  sub_test_case 'grep non-string jsonable values' do
167
223
  def filter(msg, config = 'regexp1 message 0')
168
224
  d = create_driver(config)
@@ -372,7 +372,7 @@ class OutputTest < Test::Unit::TestCase
372
372
  end
373
373
 
374
374
  sub_test_case '#placeholder_validate!' do
375
- test 'raises configuration error for a templace when timestamp placeholders exist but time key is missing' do
375
+ test 'raises configuration error for a template when timestamp placeholders exist but time key is missing' do
376
376
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', '')]))
377
377
  assert_raise Fluent::ConfigError.new("Parameter 'path: /path/without/timestamp/file.%Y%m%d-%H%M.log' has timestamp placeholders, but chunk key 'time' is not configured") do
378
378
  @i.placeholder_validate!(:path, "/path/without/timestamp/file.%Y%m%d-%H%M.log")
@@ -0,0 +1,108 @@
1
+ require_relative '../helper'
2
+ require 'fluent/plugin_helper/record_accessor'
3
+ require 'fluent/plugin/base'
4
+
5
+ require 'time'
6
+
7
+ class RecordAccessorHelperTest < Test::Unit::TestCase
8
+ class Dummy < Fluent::Plugin::TestBase
9
+ helpers :record_accessor
10
+ end
11
+
12
+ sub_test_case 'parse nested key expression' do
13
+ data('normal' => 'key1',
14
+ 'space' => 'ke y2',
15
+ 'dot key' => 'this.is.key3')
16
+ test 'parse single key' do |param|
17
+ result = Fluent::PluginHelper::RecordAccessor::Accessor.parse_parameter(param)
18
+ assert_equal param, result
19
+ end
20
+
21
+ test "nested bracket keys with dot" do
22
+ result = Fluent::PluginHelper::RecordAccessor::Accessor.parse_parameter("$['key1']['this.is.key3']")
23
+ assert_equal ['key1', 'this.is.key3'], result
24
+ end
25
+
26
+ data('dot' => '$.key1.key2[0]',
27
+ 'bracket' => "$['key1']['key2'][0]")
28
+ test "nested keys ['key1', 'key2', 0]" do |param|
29
+ result = Fluent::PluginHelper::RecordAccessor::Accessor.parse_parameter(param)
30
+ assert_equal ['key1', 'key2', 0], result
31
+ end
32
+
33
+ data('bracket' => "$['key1'][0]['ke y2']")
34
+ test "nested keys ['key1', 0, 'ke y2']" do |param|
35
+ result = Fluent::PluginHelper::RecordAccessor::Accessor.parse_parameter(param)
36
+ assert_equal ['key1', 0, 'ke y2'], result
37
+ end
38
+
39
+ data('dot' => '$.[0].key1.[1].key2',
40
+ 'bracket' => "$[0]['key1'][1]['key2']")
41
+ test "nested keys [0, 'key1', 1, 'key2']" do |param|
42
+ result = Fluent::PluginHelper::RecordAccessor::Accessor.parse_parameter(param)
43
+ assert_equal [0, 'key1', 1, 'key2'], result
44
+ end
45
+
46
+ data("missing ']'" => "$['key1'",
47
+ "missing array index with dot" => "$.hello[]",
48
+ "missing array index with braket" => "$[]",
49
+ "more chars" => "$.key1[0]foo",
50
+ "whitespace char included key in dot notation" => "$.key[0].ke y",
51
+ "empty keys with dot" => "$.",
52
+ "empty keys with bracket" => "$[")
53
+ test 'invalid syntax' do |param|
54
+ assert_raise Fluent::ConfigError do
55
+ Fluent::PluginHelper::RecordAccessor::Accessor.parse_parameter(param)
56
+ end
57
+ end
58
+ end
59
+
60
+ sub_test_case Fluent::PluginHelper::RecordAccessor::Accessor do
61
+ setup do
62
+ @d = Dummy.new
63
+ end
64
+
65
+ data('normal' => 'key1',
66
+ 'space' => 'ke y2',
67
+ 'dot key' => 'this.is.key3')
68
+ test 'access single key' do |param|
69
+ r = {'key1' => 'v1', 'ke y2' => 'v2', 'this.is.key3' => 'v3'}
70
+ accessor = @d.record_accessor_create(param)
71
+ assert_equal r[param], accessor.call(r)
72
+ end
73
+
74
+ test "nested bracket keys with dot" do
75
+ r = {'key1' => {'this.is.key3' => 'value'}}
76
+ accessor = @d.record_accessor_create("$['key1']['this.is.key3']")
77
+ assert_equal 'value', accessor.call(r)
78
+ end
79
+
80
+ data('dot' => '$.key1.key2[0]',
81
+ 'bracket' => "$['key1']['key2'][0]")
82
+ test "nested keys ['key1', 'key2', 0]" do |param|
83
+ r = {'key1' => {'key2' => [1, 2, 3]}}
84
+ accessor = @d.record_accessor_create(param)
85
+ assert_equal 1, accessor.call(r)
86
+ end
87
+
88
+ data('bracket' => "$['key1'][0]['ke y2']")
89
+ test "nested keys ['key1', 0, 'ke y2']" do |param|
90
+ r = {'key1' => [{'ke y2' => "value"}]}
91
+ accessor = @d.record_accessor_create(param)
92
+ assert_equal 'value', accessor.call(r)
93
+ end
94
+
95
+ data("missing ']'" => "$['key1'",
96
+ "missing array index with dot" => "$.hello[]",
97
+ "missing array index with braket" => "$['hello'][]",
98
+ "whitespace char included key in dot notation" => "$.key[0].ke y",
99
+ "more chars" => "$.key1[0]foo",
100
+ "empty keys with dot" => "$.",
101
+ "empty keys with bracket" => "$[")
102
+ test 'invalid syntax' do |param|
103
+ assert_raise Fluent::ConfigError do
104
+ @d.record_accessor_create(param)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -379,6 +379,56 @@ class LogTest < Test::Unit::TestCase
379
379
  assert_equal(Fluent::Log::LEVEL_TRACE, log2.level)
380
380
  end
381
381
 
382
+ def test_format_json
383
+ logdev = @log_device
384
+ logger = ServerEngine::DaemonLogger.new(logdev)
385
+ log = Fluent::Log.new(logger)
386
+ log.format = :json
387
+ log.level = Fluent::Log::LEVEL_TRACE
388
+ log.trace "trace log"
389
+ log.debug "debug log"
390
+ log.info "info log"
391
+ log.warn "warn log"
392
+ log.error "error log"
393
+ log.fatal "fatal log"
394
+ expected = [
395
+ "#{@timestamp_str} [trace]: trace log\n",
396
+ "#{@timestamp_str} [debug]: debug log\n",
397
+ "#{@timestamp_str} [info]: info log\n",
398
+ "#{@timestamp_str} [warn]: warn log\n",
399
+ "#{@timestamp_str} [error]: error log\n",
400
+ "#{@timestamp_str} [fatal]: fatal log\n"
401
+ ]
402
+ assert_equal(expected, log.out.logs.map { |l|
403
+ r = JSON.parse(l)
404
+ "#{r['time']} [#{r['level']}]: #{r['message']}\n"
405
+ })
406
+ end
407
+
408
+ def test_time_format
409
+ logdev = @log_device
410
+ logger = ServerEngine::DaemonLogger.new(logdev)
411
+ log = Fluent::Log.new(logger)
412
+ log.time_format = "%Y"
413
+ log.level = Fluent::Log::LEVEL_TRACE
414
+ log.trace "trace log"
415
+ log.debug "debug log"
416
+ log.info "info log"
417
+ log.warn "warn log"
418
+ log.error "error log"
419
+ log.fatal "fatal log"
420
+ timestamp_str = @timestamp.strftime("%Y")
421
+ expected = [
422
+ "#{timestamp_str} [trace]: trace log\n",
423
+ "#{timestamp_str} [debug]: debug log\n",
424
+ "#{timestamp_str} [info]: info log\n",
425
+ "#{timestamp_str} [warn]: warn log\n",
426
+ "#{timestamp_str} [error]: error log\n",
427
+ "#{timestamp_str} [fatal]: fatal log\n"
428
+ ]
429
+ assert_equal(expected, log.out.logs)
430
+ end
431
+
382
432
  def test_disable_events
383
433
  dl_opts = {}
384
434
  dl_opts[:log_level] = ServerEngine::DaemonLogger::TRACE
@@ -130,6 +130,10 @@ class SupervisorTest < ::Test::Unit::TestCase
130
130
  process_name "process_name"
131
131
  log_level info
132
132
  root_dir #{TMP_ROOT_DIR}
133
+ <log>
134
+ format json
135
+ time_format %Y
136
+ </log>
133
137
  </system>
134
138
  EOC
135
139
  conf = Fluent::Config.parse(conf_data, "(test)", "(test_dir)", true)
@@ -145,6 +149,8 @@ class SupervisorTest < ::Test::Unit::TestCase
145
149
  assert_equal "process_name", sys_conf.process_name
146
150
  assert_equal 2, sys_conf.log_level
147
151
  assert_equal TMP_ROOT_DIR, sys_conf.root_dir
152
+ assert_equal :json, sys_conf.log.format
153
+ assert_equal '%Y', sys_conf.log.time_format
148
154
  end
149
155
 
150
156
  def test_main_process_signal_handlers
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.14.20.rc1
4
+ version: 0.14.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: 2017-07-21 00:00:00.000000000 Z
11
+ date: 2017-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -160,6 +160,20 @@ dependencies:
160
160
  - - "~>"
161
161
  - !ruby/object:Gem::Version
162
162
  version: 0.1.7
163
+ - !ruby/object:Gem::Dependency
164
+ name: ruby_dig
165
+ requirement: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - "~>"
168
+ - !ruby/object:Gem::Version
169
+ version: 0.0.2
170
+ type: :runtime
171
+ prerelease: false
172
+ version_requirements: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - "~>"
175
+ - !ruby/object:Gem::Version
176
+ version: 0.0.2
163
177
  - !ruby/object:Gem::Dependency
164
178
  name: rake
165
179
  requirement: !ruby/object:Gem::Requirement
@@ -503,6 +517,7 @@ files:
503
517
  - lib/fluent/plugin_helper/formatter.rb
504
518
  - lib/fluent/plugin_helper/inject.rb
505
519
  - lib/fluent/plugin_helper/parser.rb
520
+ - lib/fluent/plugin_helper/record_accessor.rb
506
521
  - lib/fluent/plugin_helper/retry_state.rb
507
522
  - lib/fluent/plugin_helper/server.rb
508
523
  - lib/fluent/plugin_helper/socket.rb
@@ -665,6 +680,7 @@ files:
665
680
  - test/plugin_helper/test_formatter.rb
666
681
  - test/plugin_helper/test_inject.rb
667
682
  - test/plugin_helper/test_parser.rb
683
+ - test/plugin_helper/test_record_accessor.rb
668
684
  - test/plugin_helper/test_retry_state.rb
669
685
  - test/plugin_helper/test_server.rb
670
686
  - test/plugin_helper/test_storage.rb
@@ -716,9 +732,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
716
732
  version: '2.1'
717
733
  required_rubygems_version: !ruby/object:Gem::Requirement
718
734
  requirements:
719
- - - ">"
735
+ - - ">="
720
736
  - !ruby/object:Gem::Version
721
- version: 1.3.1
737
+ version: '0'
722
738
  requirements: []
723
739
  rubyforge_project:
724
740
  rubygems_version: 2.6.11
@@ -830,6 +846,7 @@ test_files:
830
846
  - test/plugin_helper/test_formatter.rb
831
847
  - test/plugin_helper/test_inject.rb
832
848
  - test/plugin_helper/test_parser.rb
849
+ - test/plugin_helper/test_record_accessor.rb
833
850
  - test/plugin_helper/test_retry_state.rb
834
851
  - test/plugin_helper/test_server.rb
835
852
  - test/plugin_helper/test_storage.rb