fluentd 1.11.3 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  3. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. data/.github/workflows/stale-actions.yml +22 -0
  5. data/.travis.yml +22 -2
  6. data/CHANGELOG.md +66 -0
  7. data/README.md +1 -1
  8. data/appveyor.yml +3 -0
  9. data/bin/fluent-cap-ctl +7 -0
  10. data/bin/fluent-ctl +7 -0
  11. data/fluentd.gemspec +2 -1
  12. data/lib/fluent/capability.rb +87 -0
  13. data/lib/fluent/command/cap_ctl.rb +174 -0
  14. data/lib/fluent/command/ctl.rb +177 -0
  15. data/lib/fluent/command/plugin_config_formatter.rb +2 -1
  16. data/lib/fluent/env.rb +4 -0
  17. data/lib/fluent/plugin.rb +5 -0
  18. data/lib/fluent/plugin/buffer.rb +2 -21
  19. data/lib/fluent/plugin/formatter.rb +24 -0
  20. data/lib/fluent/plugin/formatter_csv.rb +1 -1
  21. data/lib/fluent/plugin/formatter_hash.rb +3 -1
  22. data/lib/fluent/plugin/formatter_json.rb +3 -1
  23. data/lib/fluent/plugin/formatter_ltsv.rb +5 -3
  24. data/lib/fluent/plugin/formatter_out_file.rb +6 -4
  25. data/lib/fluent/plugin/formatter_single_value.rb +4 -2
  26. data/lib/fluent/plugin/formatter_tsv.rb +4 -2
  27. data/lib/fluent/plugin/in_http.rb +23 -2
  28. data/lib/fluent/plugin/in_tail.rb +109 -41
  29. data/lib/fluent/plugin/in_tail/position_file.rb +39 -14
  30. data/lib/fluent/plugin/in_tcp.rb +1 -0
  31. data/lib/fluent/plugin/out_http.rb +20 -2
  32. data/lib/fluent/plugin/output.rb +14 -6
  33. data/lib/fluent/plugin_helper/http_server/compat/server.rb +1 -1
  34. data/lib/fluent/plugin_helper/inject.rb +4 -1
  35. data/lib/fluent/plugin_helper/retry_state.rb +4 -0
  36. data/lib/fluent/supervisor.rb +140 -43
  37. data/lib/fluent/time.rb +1 -0
  38. data/lib/fluent/version.rb +1 -1
  39. data/lib/fluent/winsvc.rb +22 -4
  40. data/test/command/test_binlog_reader.rb +22 -6
  41. data/test/command/test_cap_ctl.rb +100 -0
  42. data/test/command/test_ctl.rb +57 -0
  43. data/test/command/test_plugin_config_formatter.rb +57 -2
  44. data/test/plugin/in_tail/test_position_file.rb +45 -25
  45. data/test/plugin/test_filter_stdout.rb +6 -1
  46. data/test/plugin/test_formatter_hash.rb +6 -3
  47. data/test/plugin/test_formatter_json.rb +14 -4
  48. data/test/plugin/test_formatter_ltsv.rb +13 -5
  49. data/test/plugin/test_formatter_out_file.rb +35 -14
  50. data/test/plugin/test_formatter_single_value.rb +12 -6
  51. data/test/plugin/test_formatter_tsv.rb +12 -4
  52. data/test/plugin/test_in_http.rb +25 -0
  53. data/test/plugin/test_in_tail.rb +430 -30
  54. data/test/plugin/test_out_file.rb +23 -18
  55. data/test/plugin/test_output.rb +12 -0
  56. data/test/plugin/test_parser_syslog.rb +2 -2
  57. data/test/plugin_helper/test_compat_parameters.rb +7 -2
  58. data/test/plugin_helper/test_inject.rb +42 -0
  59. data/test/test_capability.rb +74 -0
  60. data/test/test_formatter.rb +34 -10
  61. data/test/test_output.rb +6 -1
  62. data/test/test_supervisor.rb +119 -1
  63. metadata +33 -4
@@ -769,17 +769,19 @@ module Fluent
769
769
  end
770
770
  end
771
771
 
772
- if rvalue =~ CHUNK_KEY_PLACEHOLDER_PATTERN
773
- log.warn "chunk key placeholder '#{$1}' not replaced. template:#{str}"
774
- end
775
-
776
- rvalue.sub(CHUNK_ID_PLACEHOLDER_PATTERN) {
772
+ rvalue = rvalue.sub(CHUNK_ID_PLACEHOLDER_PATTERN) {
777
773
  if chunk_passed
778
774
  dump_unique_id_hex(chunk.unique_id)
779
775
  else
780
776
  log.warn "${chunk_id} is not allowed in this plugin. Pass Chunk instead of metadata in extract_placeholders's 2nd argument"
781
777
  end
782
778
  }
779
+
780
+ if rvalue =~ CHUNK_KEY_PLACEHOLDER_PATTERN
781
+ log.warn "chunk key placeholder '#{$1}' not replaced. template:#{str}"
782
+ end
783
+
784
+ rvalue
783
785
  end
784
786
  end
785
787
 
@@ -1250,7 +1252,13 @@ module Fluent
1250
1252
  log.debug "buffer queue cleared"
1251
1253
  @retry = nil
1252
1254
  else
1253
- @retry.step
1255
+ # Ensure that the current time is greater than or equal to @retry.next_time to avoid the situation when
1256
+ # @retry.step is called almost as many times as the number of flush threads in a short time.
1257
+ if Time.now >= @retry.next_time
1258
+ @retry.step
1259
+ else
1260
+ @retry.recalc_next_time # to prevent all flush threads from retrying at the same time
1261
+ end
1254
1262
  if error
1255
1263
  if using_secondary
1256
1264
  msg = "failed to flush the buffer with secondary output."
@@ -81,7 +81,7 @@ module Fluent
81
81
 
82
82
  def build_handler
83
83
  @methods.group_by(&:first).each do |(path, rest)|
84
- klass = Fluent::PluginHelper::HttpServer::Compat::WebrickHandler.build(Hash[rest.map { |e| [e[1], e[2]] }])
84
+ klass = Fluent::PluginHelper::HttpServer::Compat::WebrickHandler.build(**Hash[rest.map { |e| [e[1], e[2]] }])
85
85
  @server.mount(path, klass)
86
86
  end
87
87
  end
@@ -76,7 +76,7 @@ module Fluent
76
76
  config_param :time_key, :string, default: nil
77
77
 
78
78
  # To avoid defining :time_type twice
79
- config_param :time_type, :enum, list: [:float, :unixtime, :string], default: :float
79
+ config_param :time_type, :enum, list: [:float, :unixtime, :unixtime_millis, :unixtime_micros, :unixtime_nanos, :string], default: :float
80
80
 
81
81
  Fluent::TimeMixin::TIME_PARAMETERS.each do |name, type, opts|
82
82
  config_param(name, type, **opts)
@@ -132,6 +132,9 @@ module Fluent
132
132
  if @_inject_time_key
133
133
  @_inject_time_formatter = case @inject_config.time_type
134
134
  when :float then ->(time){ time.to_r.truncate(+6).to_f } # microsecond floating point value
135
+ when :unixtime_millis then ->(time) { time.respond_to?(:nsec) ? time.to_i * 1_000 + time.nsec / 1_000_000 : (time * 1_000).floor }
136
+ when :unixtime_micros then ->(time) { time.respond_to?(:nsec) ? time.to_i * 1_000_000 + time.nsec / 1_000 : (time * 1_000_000).floor }
137
+ when :unixtime_nanos then ->(time) { time.respond_to?(:nsec) ? time.to_i * 1_000_000_000 + time.nsec : (time * 1_000_000_000).floor }
135
138
  when :unixtime then ->(time){ time.to_i }
136
139
  else
137
140
  localtime = @inject_config.localtime && !@inject_config.utc
@@ -127,6 +127,10 @@ module Fluent
127
127
  nil
128
128
  end
129
129
 
130
+ def recalc_next_time
131
+ @next_time = calc_next_time
132
+ end
133
+
130
134
  def limit?
131
135
  if @forever
132
136
  false
@@ -53,11 +53,11 @@ module Fluent
53
53
  @enable_get_dump = config[:enable_get_dump]
54
54
  run_rpc_server
55
55
  end
56
- install_supervisor_signal_handlers
57
56
 
58
- if config[:signame]
59
- @signame = config[:signame]
57
+ if Fluent.windows?
60
58
  install_windows_event_handler
59
+ else
60
+ install_supervisor_signal_handlers
61
61
  end
62
62
 
63
63
  if counter = config[:counter_server]
@@ -70,6 +70,7 @@ module Fluent
70
70
  end
71
71
 
72
72
  def after_run
73
+ stop_windows_event_thread if Fluent.windows?
73
74
  stop_rpc_server if @rpc_endpoint
74
75
  stop_counter_server if @counter
75
76
  Fluent::Supervisor.cleanup_resources
@@ -92,7 +93,8 @@ module Fluent
92
93
  @rpc_server.mount_proc('/api/processes.flushBuffersAndKillWorkers') { |req, res|
93
94
  $log.debug "fluentd RPC got /api/processes.flushBuffersAndKillWorkers request"
94
95
  if Fluent.windows?
95
- $log.warn "operation 'flushBuffersAndKillWorkers' is not supported on Windows now."
96
+ supervisor_sigusr1_handler
97
+ stop(true)
96
98
  else
97
99
  Process.kill :USR1, $$
98
100
  Process.kill :TERM, $$
@@ -101,7 +103,9 @@ module Fluent
101
103
  }
102
104
  @rpc_server.mount_proc('/api/plugins.flushBuffers') { |req, res|
103
105
  $log.debug "fluentd RPC got /api/plugins.flushBuffers request"
104
- unless Fluent.windows?
106
+ if Fluent.windows?
107
+ supervisor_sigusr1_handler
108
+ else
105
109
  Process.kill :USR1, $$
106
110
  end
107
111
  nil
@@ -125,7 +129,9 @@ module Fluent
125
129
 
126
130
  @rpc_server.mount_proc('/api/config.gracefulReload') { |req, res|
127
131
  $log.debug "fluentd RPC got /api/config.gracefulReload request"
128
- unless Fluent.windows?
132
+ if Fluent.windows?
133
+ supervisor_sigusr2_handler
134
+ else
129
135
  Process.kill :USR2, $$
130
136
  end
131
137
 
@@ -159,38 +165,101 @@ module Fluent
159
165
  end
160
166
 
161
167
  def install_supervisor_signal_handlers
168
+ return if Fluent.windows?
169
+
162
170
  trap :HUP do
163
171
  $log.debug "fluentd supervisor process get SIGHUP"
164
172
  supervisor_sighup_handler
165
- end unless Fluent.windows?
173
+ end
166
174
 
167
175
  trap :USR1 do
168
176
  $log.debug "fluentd supervisor process get SIGUSR1"
169
177
  supervisor_sigusr1_handler
170
- end unless Fluent.windows?
178
+ end
171
179
 
172
180
  trap :USR2 do
173
181
  $log.debug 'fluentd supervisor process got SIGUSR2'
174
182
  supervisor_sigusr2_handler
175
- end unless Fluent.windows?
183
+ end
184
+ end
185
+
186
+ if Fluent.windows?
187
+ # Override some methods of ServerEngine::MultiSpawnWorker
188
+ # Since Fluentd's Supervisor doesn't use ServerEngine's HUP, USR1 and USR2
189
+ # handlers (see install_supervisor_signal_handlers), they should be
190
+ # disabled also on Windows, just send commands to workers instead.
191
+ def restart(graceful)
192
+ @monitors.each do |m|
193
+ m.send_command(graceful ? "GRACEFUL_RESTART\n" : "IMMEDIATE_RESTART\n")
194
+ end
195
+ end
196
+
197
+ def reload
198
+ @monitors.each do |m|
199
+ m.send_command("RELOAD\n")
200
+ end
201
+ end
176
202
  end
177
203
 
178
204
  def install_windows_event_handler
205
+ return unless Fluent.windows?
206
+
207
+ @pid_signame = "fluentd_#{$$}"
208
+ @signame = config[:signame]
209
+
179
210
  Thread.new do
180
- ev = Win32::Event.new(@signame)
211
+ ipc = Win32::Ipc.new(nil)
212
+ events = [
213
+ Win32::Event.new("#{@pid_signame}_STOP_EVENT_THREAD"),
214
+ Win32::Event.new("#{@pid_signame}"),
215
+ Win32::Event.new("#{@pid_signame}_HUP"),
216
+ Win32::Event.new("#{@pid_signame}_USR1"),
217
+ Win32::Event.new("#{@pid_signame}_USR2"),
218
+ ]
219
+ if @signame
220
+ signame_events = [
221
+ Win32::Event.new("#{@signame}"),
222
+ Win32::Event.new("#{@signame}_HUP"),
223
+ Win32::Event.new("#{@signame}_USR1"),
224
+ Win32::Event.new("#{@signame}_USR2"),
225
+ ]
226
+ events.concat(signame_events)
227
+ end
181
228
  begin
182
- ev.reset
183
- until WaitForSingleObject(ev.handle, 0) == WAIT_OBJECT_0
184
- sleep 1
229
+ loop do
230
+ idx = ipc.wait_any(events, Windows::Synchronize::INFINITE)
231
+ if idx > 0 && idx <= events.length
232
+ $log.debug("Got Win32 event \"#{events[idx - 1].name}\"")
233
+ else
234
+ $log.warn("Unexpected reutrn value of Win32::Ipc#wait_any: #{idx}")
235
+ end
236
+ case idx
237
+ when 2, 6
238
+ stop(true)
239
+ when 3, 7
240
+ supervisor_sighup_handler
241
+ when 4, 8
242
+ supervisor_sigusr1_handler
243
+ when 5, 9
244
+ supervisor_sigusr2_handler
245
+ when 1
246
+ break
247
+ end
185
248
  end
186
- kill_worker
187
- stop(true)
188
249
  ensure
189
- ev.close
250
+ events.each { |event| event.close }
190
251
  end
191
252
  end
192
253
  end
193
254
 
255
+ def stop_windows_event_thread
256
+ if Fluent.windows?
257
+ ev = Win32::Event.open("#{@pid_signame}_STOP_EVENT_THREAD")
258
+ ev.set
259
+ ev.close
260
+ end
261
+ end
262
+
194
263
  def supervisor_sighup_handler
195
264
  kill_worker
196
265
  end
@@ -265,9 +334,25 @@ module Fluent
265
334
  def send_signal_to_workers(signal)
266
335
  return unless config[:worker_pid]
267
336
 
268
- config[:worker_pid].each_value do |pid|
269
- # don't rescue Errno::ESRCH here (invalid status)
270
- Process.kill(signal, pid)
337
+ if Fluent.windows?
338
+ send_command_to_workers(signal)
339
+ else
340
+ config[:worker_pid].each_value do |pid|
341
+ # don't rescue Errno::ESRCH here (invalid status)
342
+ Process.kill(signal, pid)
343
+ end
344
+ end
345
+ end
346
+
347
+ def send_command_to_workers(signal)
348
+ # Use SeverEngine's CommandSender on Windows
349
+ case signal
350
+ when :HUP
351
+ restart(false)
352
+ when :USR1
353
+ restart(true)
354
+ when :USR2
355
+ reload
271
356
  end
272
357
  end
273
358
  end
@@ -746,33 +831,45 @@ module Fluent
746
831
  end
747
832
  end
748
833
 
749
- trap :USR1 do
750
- flush_buffer
751
- end unless Fluent.windows?
834
+ if Fluent.windows?
835
+ install_main_process_command_handlers
836
+ else
837
+ trap :USR1 do
838
+ flush_buffer
839
+ end
752
840
 
753
- trap :USR2 do
754
- reload_config
755
- end unless Fluent.windows?
841
+ trap :USR2 do
842
+ reload_config
843
+ end
844
+ end
845
+ end
756
846
 
757
- if Fluent.windows?
758
- command_pipe = STDIN.dup
759
- STDIN.reopen(File::NULL, "rb")
760
- command_pipe.binmode
761
- command_pipe.sync = true
847
+ def install_main_process_command_handlers
848
+ command_pipe = $stdin.dup
849
+ $stdin.reopen(File::NULL, "rb")
850
+ command_pipe.binmode
851
+ command_pipe.sync = true
762
852
 
763
- Thread.new do
764
- loop do
765
- cmd = command_pipe.gets.chomp
766
- case cmd
767
- when "GRACEFUL_STOP", "IMMEDIATE_STOP"
768
- $log.debug "fluentd main process get #{cmd} command"
769
- @finished = true
770
- $log.debug "getting start to shutdown main process"
771
- Fluent::Engine.stop
772
- break
773
- else
774
- $log.warn "fluentd main process get unknown command [#{cmd}]"
775
- end
853
+ Thread.new do
854
+ loop do
855
+ cmd = command_pipe.gets
856
+ break unless cmd
857
+
858
+ case cmd.chomp!
859
+ when "GRACEFUL_STOP", "IMMEDIATE_STOP"
860
+ $log.debug "fluentd main process get #{cmd} command"
861
+ @finished = true
862
+ $log.debug "getting start to shutdown main process"
863
+ Fluent::Engine.stop
864
+ break
865
+ when "GRACEFUL_RESTART"
866
+ $log.debug "fluentd main process get #{cmd} command"
867
+ flush_buffer
868
+ when "RELOAD"
869
+ $log.debug "fluentd main process get #{cmd} command"
870
+ reload_config
871
+ else
872
+ $log.warn "fluentd main process get unknown command [#{cmd}]"
776
873
  end
777
874
  end
778
875
  end
@@ -50,6 +50,7 @@ module Fluent
50
50
  def to_int
51
51
  @sec
52
52
  end
53
+ alias :to_i :to_int
53
54
 
54
55
  def to_f
55
56
  @sec + @nsec / 1_000_000_000.0
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '1.11.3'
19
+ VERSION = '1.12.0'
20
20
 
21
21
  end
@@ -61,7 +61,6 @@ begin
61
61
  end
62
62
 
63
63
  def service_main
64
-
65
64
  @pid = service_main_start(@service_name)
66
65
  while running?
67
66
  sleep 10
@@ -69,13 +68,32 @@ begin
69
68
  end
70
69
 
71
70
  def service_stop
72
- ev = Win32::Event.open(@service_name)
73
- ev.set
74
- ev.close
71
+ set_event(@service_name)
75
72
  if @pid > 0
76
73
  Process.waitpid(@pid)
77
74
  end
78
75
  end
76
+
77
+ def service_paramchange
78
+ set_event("#{@service_name}_USR2")
79
+ end
80
+
81
+ def service_user_defined_control(code)
82
+ case code
83
+ when 128
84
+ set_event("#{@service_name}_HUP")
85
+ when 129
86
+ set_event("#{@service_name}_USR1")
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def set_event(event_name)
93
+ ev = Win32::Event.open(event_name)
94
+ ev.set
95
+ ev.close
96
+ end
79
97
  end
80
98
 
81
99
  FluentdService.new(opts[:service_name]).mainloop
@@ -82,6 +82,14 @@ class TestBaseCommand < ::Test::Unit::TestCase
82
82
  end
83
83
 
84
84
  class TestHead < TestBaseCommand
85
+ setup do
86
+ @default_newline = if Fluent.windows?
87
+ "\r\n"
88
+ else
89
+ "\n"
90
+ end
91
+ end
92
+
85
93
  sub_test_case 'initialize' do
86
94
  data(
87
95
  'file is not passed' => %w(),
@@ -138,7 +146,7 @@ class TestHead < TestBaseCommand
138
146
  create_message_packed_file(@file_name, [event_time(@t).to_i] * 6, [@record] * 6)
139
147
  head = BinlogReaderCommand::Head.new(argv)
140
148
  out = capture_stdout { head.call }
141
- assert_equal "2011-01-02T13:14:15+00:00\t#{TMP_DIR}/#{@file_name}\t#{Yajl.dump(@record)}\n" * 5, out
149
+ assert_equal "2011-01-02T13:14:15+00:00\t#{TMP_DIR}/#{@file_name}\t#{Yajl.dump(@record)}#{@default_newline}" * 5, out
142
150
  end
143
151
  end
144
152
 
@@ -149,7 +157,7 @@ class TestHead < TestBaseCommand
149
157
  create_message_packed_file(@file_name, [event_time(@t).to_i] * 6, [@record] * 6)
150
158
  head = BinlogReaderCommand::Head.new(argv)
151
159
  out = capture_stdout { head.call }
152
- assert_equal "2011-01-02T13:14:15+00:00\t#{TMP_DIR}/#{@file_name}\t#{Yajl.dump(@record)}\n", out
160
+ assert_equal "2011-01-02T13:14:15+00:00\t#{TMP_DIR}/#{@file_name}\t#{Yajl.dump(@record)}#{@default_newline}", out
153
161
  end
154
162
  end
155
163
 
@@ -169,7 +177,7 @@ class TestHead < TestBaseCommand
169
177
  create_message_packed_file(@file_name, [event_time(@t).to_i], [@record])
170
178
  head = BinlogReaderCommand::Head.new(argv)
171
179
  out = capture_stdout { head.call }
172
- assert_equal "#{Yajl.dump(@record)}\n", out
180
+ assert_equal "#{Yajl.dump(@record)}#{@default_newline}", out
173
181
  end
174
182
  end
175
183
 
@@ -198,6 +206,14 @@ class TestHead < TestBaseCommand
198
206
  end
199
207
 
200
208
  class TestCat < TestBaseCommand
209
+ setup do
210
+ @default_newline = if Fluent.windows?
211
+ "\r\n"
212
+ else
213
+ "\n"
214
+ end
215
+ end
216
+
201
217
  sub_test_case 'initialize' do
202
218
  data(
203
219
  'file is not passed' => [],
@@ -254,7 +270,7 @@ class TestCat < TestBaseCommand
254
270
  create_message_packed_file(@file_name, [event_time(@t).to_i] * 6, [@record] * 6)
255
271
  head = BinlogReaderCommand::Cat.new(argv)
256
272
  out = capture_stdout { head.call }
257
- assert_equal "2011-01-02T13:14:15+00:00\t#{TMP_DIR}/#{@file_name}\t#{Yajl.dump(@record)}\n" * 6, out
273
+ assert_equal "2011-01-02T13:14:15+00:00\t#{TMP_DIR}/#{@file_name}\t#{Yajl.dump(@record)}#{@default_newline}" * 6, out
258
274
  end
259
275
  end
260
276
 
@@ -265,7 +281,7 @@ class TestCat < TestBaseCommand
265
281
  create_message_packed_file(@file_name, [event_time(@t).to_i] * 6, [@record] * 6)
266
282
  head = BinlogReaderCommand::Cat.new(argv)
267
283
  out = capture_stdout { head.call }
268
- assert_equal "2011-01-02T13:14:15+00:00\t#{TMP_DIR}/#{@file_name}\t#{Yajl.dump(@record)}\n", out
284
+ assert_equal "2011-01-02T13:14:15+00:00\t#{TMP_DIR}/#{@file_name}\t#{Yajl.dump(@record)}#{@default_newline}", out
269
285
  end
270
286
  end
271
287
 
@@ -276,7 +292,7 @@ class TestCat < TestBaseCommand
276
292
  create_message_packed_file(@file_name, [event_time(@t).to_i], [@record])
277
293
  head = BinlogReaderCommand::Cat.new(argv)
278
294
  out = capture_stdout { head.call }
279
- assert_equal "#{Yajl.dump(@record)}\n", out
295
+ assert_equal "#{Yajl.dump(@record)}#{@default_newline}", out
280
296
  end
281
297
  end
282
298