fluentd 1.15.2-x86-mingw32 → 1.16.1-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.yaml +1 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.yaml +1 -0
  4. data/.github/workflows/linux-test.yaml +2 -2
  5. data/.github/workflows/macos-test.yaml +2 -2
  6. data/.github/workflows/stale-actions.yml +11 -9
  7. data/.github/workflows/windows-test.yaml +2 -2
  8. data/CHANGELOG.md +133 -0
  9. data/CONTRIBUTING.md +1 -1
  10. data/MAINTAINERS.md +5 -3
  11. data/README.md +0 -1
  12. data/SECURITY.md +5 -9
  13. data/fluentd.gemspec +2 -2
  14. data/lib/fluent/command/fluentd.rb +55 -64
  15. data/lib/fluent/config/yaml_parser/loader.rb +18 -1
  16. data/lib/fluent/daemon.rb +2 -4
  17. data/lib/fluent/event.rb +2 -2
  18. data/lib/fluent/file_wrapper.rb +137 -0
  19. data/lib/fluent/log/console_adapter.rb +66 -0
  20. data/lib/fluent/log.rb +35 -5
  21. data/lib/fluent/oj_options.rb +1 -2
  22. data/lib/fluent/plugin/base.rb +5 -7
  23. data/lib/fluent/plugin/buf_file.rb +32 -3
  24. data/lib/fluent/plugin/buf_file_single.rb +32 -3
  25. data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
  26. data/lib/fluent/plugin/buffer.rb +21 -0
  27. data/lib/fluent/plugin/in_tail.rb +1 -6
  28. data/lib/fluent/plugin/in_tcp.rb +47 -2
  29. data/lib/fluent/plugin/out_file.rb +0 -4
  30. data/lib/fluent/plugin/out_forward/ack_handler.rb +19 -4
  31. data/lib/fluent/plugin/out_forward.rb +2 -2
  32. data/lib/fluent/plugin/out_secondary_file.rb +39 -22
  33. data/lib/fluent/plugin/output.rb +49 -12
  34. data/lib/fluent/plugin_helper/http_server/server.rb +2 -1
  35. data/lib/fluent/plugin_helper/server.rb +8 -0
  36. data/lib/fluent/supervisor.rb +157 -232
  37. data/lib/fluent/test/driver/base.rb +11 -5
  38. data/lib/fluent/test/driver/filter.rb +4 -0
  39. data/lib/fluent/test/startup_shutdown.rb +6 -8
  40. data/lib/fluent/version.rb +1 -1
  41. data/templates/new_gem/test/helper.rb.erb +0 -1
  42. data/test/command/test_ctl.rb +1 -1
  43. data/test/command/test_fluentd.rb +168 -22
  44. data/test/command/test_plugin_config_formatter.rb +0 -1
  45. data/test/compat/test_parser.rb +5 -5
  46. data/test/config/test_system_config.rb +0 -8
  47. data/test/log/test_console_adapter.rb +110 -0
  48. data/test/plugin/out_forward/test_ack_handler.rb +39 -0
  49. data/test/plugin/test_base.rb +98 -0
  50. data/test/plugin/test_buf_file.rb +62 -23
  51. data/test/plugin/test_buf_file_single.rb +65 -0
  52. data/test/plugin/test_in_http.rb +2 -3
  53. data/test/plugin/test_in_monitor_agent.rb +2 -3
  54. data/test/plugin/test_in_tail.rb +105 -103
  55. data/test/plugin/test_in_tcp.rb +87 -2
  56. data/test/plugin/test_in_udp.rb +28 -0
  57. data/test/plugin/test_out_file.rb +3 -2
  58. data/test/plugin/test_out_forward.rb +14 -18
  59. data/test/plugin/test_out_http.rb +1 -0
  60. data/test/plugin/test_output.rb +269 -0
  61. data/test/plugin/test_output_as_buffered_compress.rb +32 -18
  62. data/test/plugin/test_parser_regexp.rb +1 -6
  63. data/test/plugin_helper/test_http_server_helper.rb +1 -1
  64. data/test/plugin_helper/test_server.rb +59 -5
  65. data/test/test_config.rb +57 -21
  66. data/test/{plugin/test_file_wrapper.rb → test_file_wrapper.rb} +2 -2
  67. data/test/test_formatter.rb +23 -20
  68. data/test/test_log.rb +85 -40
  69. data/test/test_supervisor.rb +300 -283
  70. metadata +15 -23
  71. data/.drone.yml +0 -35
  72. data/.github/workflows/issue-auto-closer.yml +0 -12
  73. data/.gitlab-ci.yml +0 -103
  74. data/lib/fluent/plugin/file_wrapper.rb +0 -131
  75. data/test/test_logger_initializer.rb +0 -46
@@ -35,7 +35,7 @@ class TestFluentdCtl < ::Test::Unit::TestCase
35
35
  Signal.trap(signal) do
36
36
  got_signal = true
37
37
  end
38
- Fluent::Ctl.new([command, "#{$$}"]).call
38
+ Fluent::Ctl.new([command, Process.pid.to_s]).call
39
39
  assert_true(got_signal)
40
40
  end
41
41
  end
@@ -5,20 +5,35 @@ require_relative '../helper'
5
5
 
6
6
  require 'fileutils'
7
7
  require 'timeout'
8
+ require 'securerandom'
9
+ require 'fluent/file_wrapper'
8
10
 
9
11
  class TestFluentdCommand < ::Test::Unit::TestCase
10
- TMP_DIR = File.expand_path(File.dirname(__FILE__) + "/../tmp/command/fluentd#{ENV['TEST_ENV_NUMBER']}")
11
12
  SUPERVISOR_PID_PATTERN = /starting fluentd-[.0-9]+ pid=(\d+)/
12
13
  WORKER_PID_PATTERN = /starting fluentd worker pid=(\d+) /
13
14
 
15
+ def tmp_dir
16
+ File.join(File.dirname(__FILE__), "..", "tmp", "command" "fluentd#{ENV['TEST_ENV_NUMBER']}", SecureRandom.hex(10))
17
+ end
18
+
14
19
  setup do
15
- FileUtils.rm_rf(TMP_DIR)
16
- FileUtils.mkdir_p(TMP_DIR)
20
+ @tmp_dir = tmp_dir
21
+ FileUtils.mkdir_p(@tmp_dir)
17
22
  @supervisor_pid = nil
18
23
  @worker_pids = []
19
24
  ENV["TEST_RUBY_PATH"] = nil
20
25
  end
21
26
 
27
+ teardown do
28
+ begin
29
+ FileUtils.rm_rf(@tmp_dir)
30
+ rescue Errno::EACCES
31
+ # It may occur on Windows because of delete pending state due to delayed GC.
32
+ # Ruby 3.2 or later doesn't ignore Errno::EACCES:
33
+ # https://github.com/ruby/ruby/commit/983115cf3c8f75b1afbe3274f02c1529e1ce3a81
34
+ end
35
+ end
36
+
22
37
  def process_exist?(pid)
23
38
  begin
24
39
  r = Process.waitpid(pid, Process::WNOHANG)
@@ -30,17 +45,17 @@ class TestFluentdCommand < ::Test::Unit::TestCase
30
45
  end
31
46
 
32
47
  def create_conf_file(name, content, ext_enc = 'utf-8')
33
- conf_path = File.join(TMP_DIR, name)
34
- File.open(conf_path, "w:#{ext_enc}:utf-8") do |file|
48
+ conf_path = File.join(@tmp_dir, name)
49
+ Fluent::FileWrapper.open(conf_path, "w:#{ext_enc}:utf-8") do |file|
35
50
  file.write content
36
51
  end
37
52
  conf_path
38
53
  end
39
54
 
40
55
  def create_plugin_file(name, content)
41
- file_path = File.join(TMP_DIR, 'plugin', name)
56
+ file_path = File.join(@tmp_dir, 'plugin', name)
42
57
  FileUtils.mkdir_p(File.dirname(file_path))
43
- File.open(file_path, 'w') do |file|
58
+ Fluent::FileWrapper.open(file_path, 'w') do |file|
44
59
  file.write content
45
60
  end
46
61
  file_path
@@ -56,8 +71,22 @@ class TestFluentdCommand < ::Test::Unit::TestCase
56
71
  end
57
72
  end
58
73
 
59
- def execute_command(cmdline, chdir=TMP_DIR, env = {})
60
- null_stream = File.open(File::NULL, 'w')
74
+ def process_kill(pid)
75
+ if Fluent.windows?
76
+ Process.kill(:KILL, pid) rescue nil
77
+ return
78
+ end
79
+
80
+ begin
81
+ Process.kill(:TERM, pid) rescue nil
82
+ Timeout.timeout(10){ sleep 0.1 while process_exist?(pid) }
83
+ rescue Timeout::Error
84
+ Process.kill(:KILL, pid) rescue nil
85
+ end
86
+ end
87
+
88
+ def execute_command(cmdline, chdir=@tmp_dir, env = {})
89
+ null_stream = Fluent::FileWrapper.open(File::NULL, 'w')
61
90
  gemfile_path = File.expand_path(File.dirname(__FILE__) + "../../../Gemfile")
62
91
 
63
92
  env = { "BUNDLE_GEMFILE" => gemfile_path }.merge(env)
@@ -70,12 +99,12 @@ class TestFluentdCommand < ::Test::Unit::TestCase
70
99
  yield pid, io
71
100
  # p(here: "execute command", pid: pid, worker_pids: @worker_pids)
72
101
  ensure
73
- Process.kill(:KILL, pid) rescue nil
102
+ process_kill(pid)
74
103
  if @supervisor_pid
75
- Process.kill(:KILL, @supervisor_pid) rescue nil
104
+ process_kill(@supervisor_pid)
76
105
  end
77
106
  @worker_pids.each do |cpid|
78
- Process.kill(:KILL, cpid) rescue nil
107
+ process_kill(cpid)
79
108
  end
80
109
  # p(here: "execute command", pid: pid, exist: process_exist?(pid), worker_pids: @worker_pids, exists: @worker_pids.map{|i| process_exist?(i) })
81
110
  Timeout.timeout(10){ sleep 0.1 while process_exist?(pid) }
@@ -97,16 +126,18 @@ class TestFluentdCommand < ::Test::Unit::TestCase
97
126
  end
98
127
  end
99
128
 
100
- def assert_log_matches(cmdline, *pattern_list, patterns_not_match: [], timeout: 10, env: {})
129
+ # ATTENTION: This stops taking logs when all `pattern_list` match or timeout,
130
+ # so `patterns_not_match` can test only logs up to that point.
131
+ def assert_log_matches(cmdline, *pattern_list, patterns_not_match: [], timeout: 20, env: {})
101
132
  matched = false
102
133
  matched_wrongly = false
103
134
  assert_error_msg = ""
104
135
  stdio_buf = ""
105
136
  begin
106
- execute_command(cmdline, TMP_DIR, env) do |pid, stdout|
137
+ execute_command(cmdline, @tmp_dir, env) do |pid, stdout|
107
138
  begin
108
139
  waiting(timeout) do
109
- while process_exist?(pid) && !matched
140
+ while process_exist?(pid)
110
141
  readables, _, _ = IO.select([stdout], nil, nil, 1)
111
142
  next unless readables
112
143
  break if readables.first.eof?
@@ -118,6 +149,18 @@ class TestFluentdCommand < ::Test::Unit::TestCase
118
149
  if pattern_list.all?{|ptn| lines.any?{|line| ptn.is_a?(Regexp) ? ptn.match(line) : line.include?(ptn) } }
119
150
  matched = true
120
151
  end
152
+
153
+ if Fluent.windows?
154
+ # https://github.com/fluent/fluentd/issues/4095
155
+ # On Windows, the initial process is different from the supervisor process,
156
+ # so we need to wait until `SUPERVISOR_PID_PATTERN` appears in the logs to get the pid.
157
+ # (Worker processes will be killed by the supervisor process, so we don't need it-)
158
+ break if matched && SUPERVISOR_PID_PATTERN =~ stdio_buf
159
+ else
160
+ # On Non-Windows, the initial process is the supervisor process,
161
+ # so we don't need to wait `SUPERVISOR_PID_PATTERN`.
162
+ break if matched
163
+ end
121
164
  end
122
165
  end
123
166
  ensure
@@ -131,6 +174,10 @@ class TestFluentdCommand < ::Test::Unit::TestCase
131
174
  end
132
175
  rescue Timeout::Error
133
176
  assert_error_msg = "execution timeout"
177
+ # https://github.com/fluent/fluentd/issues/4095
178
+ # On Windows, timeout without `@supervisor_pid` means that the test is invalid,
179
+ # since the supervisor process will survive without being killed correctly.
180
+ flunk("Invalid test: The pid of supervisor could not be taken, which is necessary on Windows.") if Fluent.windows? && @supervisor_pid.nil?
134
181
  rescue => e
135
182
  assert_error_msg = "unexpected error in launching fluentd: #{e.inspect}"
136
183
  else
@@ -162,7 +209,7 @@ class TestFluentdCommand < ::Test::Unit::TestCase
162
209
  assert matched && !matched_wrongly, assert_error_msg
163
210
  end
164
211
 
165
- def assert_fluentd_fails_to_start(cmdline, *pattern_list, timeout: 10)
212
+ def assert_fluentd_fails_to_start(cmdline, *pattern_list, timeout: 20)
166
213
  # empty_list.all?{ ... } is always true
167
214
  matched = false
168
215
  running = false
@@ -198,6 +245,10 @@ class TestFluentdCommand < ::Test::Unit::TestCase
198
245
  end
199
246
  rescue Timeout::Error
200
247
  assert_error_msg = "execution timeout with command out:\n" + stdio_buf
248
+ # https://github.com/fluent/fluentd/issues/4095
249
+ # On Windows, timeout without `@supervisor_pid` means that the test is invalid,
250
+ # since the supervisor process will survive without being killed correctly.
251
+ flunk("Invalid test: The pid of supervisor could not be taken, which is necessary on Windows.") if Fluent.windows? && @supervisor_pid.nil?
201
252
  rescue => e
202
253
  assert_error_msg = "unexpected error in launching fluentd: #{e.inspect}\n" + stdio_buf
203
254
  assert false, assert_error_msg
@@ -269,7 +320,7 @@ CONF
269
320
 
270
321
  sub_test_case 'with system configuration about root directory' do
271
322
  setup do
272
- @root_path = File.join(TMP_DIR, "rootpath")
323
+ @root_path = File.join(@tmp_dir, "rootpath")
273
324
  FileUtils.rm_rf(@root_path)
274
325
  @conf = <<CONF
275
326
  <system>
@@ -308,7 +359,7 @@ CONF
308
359
  end
309
360
 
310
361
  test 'fails to launch fluentd if specified root path is invalid path for directory' do
311
- File.open(@root_path, 'w') do |_|
362
+ Fluent::FileWrapper.open(@root_path, 'w') do |_|
312
363
  # create file and close it
313
364
  end
314
365
  conf_path = create_conf_file('existing_root_dir.conf', @conf)
@@ -508,7 +559,7 @@ CONF
508
559
 
509
560
  assert_fluentd_fails_to_start(
510
561
  create_cmdline(conf_path, "-p", File.dirname(plugin_path)),
511
- "in_buggy.rb:5: syntax error, unexpected end-of-input, expecting"
562
+ "in_buggy.rb:5: syntax error, unexpected end-of-input"
512
563
  )
513
564
  end
514
565
  end
@@ -554,7 +605,7 @@ CONF
554
605
 
555
606
  sub_test_case 'configured to run 2 workers' do
556
607
  setup do
557
- @root_path = File.join(TMP_DIR, "rootpath")
608
+ @root_path = File.join(@tmp_dir, "rootpath")
558
609
  FileUtils.rm_rf(@root_path)
559
610
  FileUtils.mkdir_p(@root_path)
560
611
  end
@@ -961,10 +1012,10 @@ CONF
961
1012
  </match>
962
1013
  CONF
963
1014
  ruby_path = ServerEngine.ruby_bin_path
964
- tmp_ruby_path = File.join(TMP_DIR, "ruby with spaces")
1015
+ tmp_ruby_path = File.join(@tmp_dir, "ruby with spaces")
965
1016
  if Fluent.windows?
966
1017
  tmp_ruby_path << ".bat"
967
- File.open(tmp_ruby_path, "w") do |file|
1018
+ Fluent::FileWrapper.open(tmp_ruby_path, "w") do |file|
968
1019
  file.write "#{ruby_path} %*"
969
1020
  end
970
1021
  else
@@ -1136,4 +1187,99 @@ CONF
1136
1187
  "shared socket for multiple workers is disabled",)
1137
1188
  end
1138
1189
  end
1190
+
1191
+ # TODO: `patterns_not_match` can test only logs up to `pattern_list`,
1192
+ # so we need to fix some meaningless `patterns_not_match` conditions.
1193
+ sub_test_case 'log_level by command line option' do
1194
+ test 'info' do
1195
+ conf = ""
1196
+ conf_path = create_conf_file('empty.conf', conf)
1197
+ assert File.exist?(conf_path)
1198
+ assert_log_matches(create_cmdline(conf_path),
1199
+ "[info]",
1200
+ patterns_not_match: ["[debug]"])
1201
+ end
1202
+
1203
+ test 'debug' do
1204
+ conf = ""
1205
+ conf_path = create_conf_file('empty.conf', conf)
1206
+ assert File.exist?(conf_path)
1207
+ assert_log_matches(create_cmdline(conf_path, "-v"),
1208
+ "[debug]",
1209
+ patterns_not_match: ["[trace]"])
1210
+ end
1211
+
1212
+ data("Trace" => "-vv")
1213
+ data("Invalid low level should be treated as Trace level": "-vvv")
1214
+ test 'trace' do |option|
1215
+ conf = <<CONF
1216
+ <source>
1217
+ @type sample
1218
+ tag test
1219
+ </source>
1220
+ CONF
1221
+ conf_path = create_conf_file('sample.conf', conf)
1222
+ assert File.exist?(conf_path)
1223
+ assert_log_matches(create_cmdline(conf_path, option),
1224
+ "[trace]",)
1225
+ end
1226
+
1227
+ test 'warn' do
1228
+ omit "Can't run on Windows since there is no way to take pid of the supervisor." if Fluent.windows?
1229
+ conf = <<CONF
1230
+ <source>
1231
+ @type sample
1232
+ tag test
1233
+ </source>
1234
+ CONF
1235
+ conf_path = create_conf_file('sample.conf', conf)
1236
+ assert File.exist?(conf_path)
1237
+ assert_log_matches(create_cmdline(conf_path, "-q"),
1238
+ "[warn]",
1239
+ patterns_not_match: ["[info]"])
1240
+ end
1241
+
1242
+ data("Error" => "-qq")
1243
+ data("Fatal should be treated as Error level" => "-qqq")
1244
+ data("Invalid high level should be treated as Error level": "-qqqq")
1245
+ test 'error' do |option|
1246
+ # This test can run on Windows correctly,
1247
+ # since the process will stop automatically with an error.
1248
+ conf = <<CONF
1249
+ <source>
1250
+ @type plugin_not_found
1251
+ tag test
1252
+ </source>
1253
+ CONF
1254
+ conf_path = create_conf_file('plugin_not_found.conf', conf)
1255
+ assert File.exist?(conf_path)
1256
+ assert_log_matches(create_cmdline(conf_path, option),
1257
+ "[error]",
1258
+ patterns_not_match: ["[warn]"])
1259
+ end
1260
+
1261
+ test 'system config one should not be overwritten when cmd line one is not specified' do
1262
+ conf = <<CONF
1263
+ <system>
1264
+ log_level debug
1265
+ </system>
1266
+ CONF
1267
+ conf_path = create_conf_file('debug.conf', conf)
1268
+ assert File.exist?(conf_path)
1269
+ assert_log_matches(create_cmdline(conf_path),
1270
+ "[debug]")
1271
+ end
1272
+ end
1273
+
1274
+ sub_test_case "inline_config" do
1275
+ test "can change log_level by --inline-config" do
1276
+ # Since we can't define multiple `<system>` directives, this use-case is not recommended.
1277
+ # This is just for this test.
1278
+ inline_conf = '<system>\nlog_level debug\n</system>'
1279
+ conf_path = create_conf_file('test.conf', "")
1280
+ assert File.exist?(conf_path)
1281
+ assert_log_matches(create_cmdline(conf_path, "--inline-config", inline_conf),
1282
+ "[debug]")
1283
+ end
1284
+ end
1139
1285
  end
@@ -188,7 +188,6 @@ slow_flush_log_threshold: float: (20.0)
188
188
  retry_exponential_backoff_base: float: (2)
189
189
  retry_max_interval: time: (nil)
190
190
  retry_randomize: bool: (true)
191
- disable_chunk_backup: bool: (false)
192
191
  <secondary>: optional, single
193
192
  @type: string: (nil)
194
193
  <buffer>: optional, single
@@ -38,14 +38,14 @@ class TextParserTest < ::Test::Unit::TestCase
38
38
 
39
39
  def test_parse_with_return
40
40
  parser = Fluent::TextParser.new
41
- parser.configure('format' => 'none')
41
+ parser.configure(config_element('test', '', 'format' => 'none'))
42
42
  _time, record = parser.parse('log message!')
43
43
  assert_equal({'message' => 'log message!'}, record)
44
44
  end
45
45
 
46
46
  def test_parse_with_block
47
47
  parser = Fluent::TextParser.new
48
- parser.configure('format' => 'none')
48
+ parser.configure(config_element('test', '', 'format' => 'none'))
49
49
  parser.parse('log message!') { |time, record|
50
50
  assert_equal({'message' => 'log message!'}, record)
51
51
  }
@@ -53,7 +53,7 @@ class TextParserTest < ::Test::Unit::TestCase
53
53
 
54
54
  def test_multi_event_parser
55
55
  parser = Fluent::TextParser.new
56
- parser.configure('format' => 'multi_event_test')
56
+ parser.configure(config_element('test', '', 'format' => 'multi_event_test'))
57
57
  i = 0
58
58
  parser.parse('log message!') { |time, record|
59
59
  assert_equal('log message!', record['message'])
@@ -67,7 +67,7 @@ class TextParserTest < ::Test::Unit::TestCase
67
67
  assert_nil p1.estimate_current_event
68
68
  assert_nil p1.parser
69
69
 
70
- p1.configure('format' => 'none')
70
+ p1.configure(config_element('test', '', 'format' => 'none'))
71
71
  assert_equal true, p1.parser.estimate_current_event
72
72
 
73
73
  p2 = Fluent::TextParser.new
@@ -76,7 +76,7 @@ class TextParserTest < ::Test::Unit::TestCase
76
76
 
77
77
  p2.estimate_current_event = false
78
78
 
79
- p2.configure('format' => 'none')
79
+ p2.configure(config_element('test', '', 'format' => 'none'))
80
80
  assert_equal false, p2.parser.estimate_current_event
81
81
  end
82
82
 
@@ -5,13 +5,6 @@ require 'fluent/config/section'
5
5
  require 'fluent/system_config'
6
6
 
7
7
  module Fluent::Config
8
- class FakeLoggerInitializer
9
- attr_accessor :level
10
- def initialize
11
- @level = nil
12
- end
13
- end
14
-
15
8
  class FakeSupervisor
16
9
  attr_writer :log_level
17
10
 
@@ -21,7 +14,6 @@ module Fluent::Config
21
14
  wokers: nil,
22
15
  restart_worker_interval: nil,
23
16
  root_dir: nil,
24
- log: FakeLoggerInitializer.new,
25
17
  log_level: Fluent::Log::LEVEL_INFO,
26
18
  suppress_interval: nil,
27
19
  suppress_config_dump: nil,
@@ -0,0 +1,110 @@
1
+ require_relative '../helper'
2
+
3
+ require 'fluent/log'
4
+ require 'fluent/log/console_adapter'
5
+
6
+ class ConsoleAdapterTest < Test::Unit::TestCase
7
+ def setup
8
+ @timestamp = Time.parse("2023-01-01 15:32:41 +0000")
9
+ @timestamp_str = @timestamp.strftime("%Y-%m-%d %H:%M:%S %z")
10
+ Timecop.freeze(@timestamp)
11
+
12
+ @logdev = Fluent::Test::DummyLogDevice.new
13
+ @logger = ServerEngine::DaemonLogger.new(@logdev)
14
+ @fluent_log = Fluent::Log.new(@logger)
15
+ @console_logger = Fluent::Log::ConsoleAdapter.wrap(@fluent_log)
16
+ end
17
+
18
+ def teardown
19
+ Timecop.return
20
+ end
21
+
22
+ def test_expected_log_levels
23
+ assert_equal({debug: 0, info: 1, warn: 2, error: 3, fatal: 4},
24
+ Console::Logger::LEVELS)
25
+ end
26
+
27
+ data(trace: [Fluent::Log::LEVEL_TRACE, :debug],
28
+ debug: [Fluent::Log::LEVEL_DEBUG, :debug],
29
+ info: [Fluent::Log::LEVEL_INFO, :info],
30
+ warn: [Fluent::Log::LEVEL_WARN, :warn],
31
+ error: [Fluent::Log::LEVEL_ERROR, :error],
32
+ fatal: [Fluent::Log::LEVEL_FATAL, :fatal])
33
+ def test_reflect_log_level(data)
34
+ level, expected = data
35
+ @fluent_log.level = level
36
+ console_logger = Fluent::Log::ConsoleAdapter.wrap(@fluent_log)
37
+ assert_equal(Console::Logger::LEVELS[expected],
38
+ console_logger.level)
39
+ end
40
+
41
+ data(debug: :debug,
42
+ info: :info,
43
+ warn: :warn,
44
+ error: :error,
45
+ fatal: :fatal)
46
+ def test_string_subject(level)
47
+ @console_logger.send(level, "subject")
48
+ assert_equal(["#{@timestamp_str} [#{level}]: 0.0s: subject\n"],
49
+ @logdev.logs)
50
+ end
51
+
52
+ data(debug: :debug,
53
+ info: :info,
54
+ warn: :warn,
55
+ error: :error,
56
+ fatal: :fatal)
57
+ def test_args(level)
58
+ @console_logger.send(level, "subject", 1, 2, 3)
59
+ assert_equal([
60
+ "#{@timestamp_str} [#{level}]: 0.0s: subject\n" +
61
+ " | 1\n" +
62
+ " | 2\n" +
63
+ " | 3\n"
64
+ ],
65
+ @logdev.logs)
66
+ end
67
+
68
+ data(debug: :debug,
69
+ info: :info,
70
+ warn: :warn,
71
+ error: :error,
72
+ fatal: :fatal)
73
+ def test_options(level)
74
+ @console_logger.send(level, "subject", kwarg1: "opt1", kwarg2: "opt2")
75
+ assert_equal([
76
+ "#{@timestamp_str} [#{level}]: 0.0s: subject\n" +
77
+ " | {\"kwarg1\":\"opt1\",\"kwarg2\":\"opt2\"}\n"
78
+ ],
79
+ @logdev.logs)
80
+ end
81
+
82
+ data(debug: :debug,
83
+ info: :info,
84
+ warn: :warn,
85
+ error: :error,
86
+ fatal: :fatal)
87
+ def test_block(level)
88
+ @console_logger.send(level, "subject") { "block message" }
89
+ assert_equal([
90
+ "#{@timestamp_str} [#{level}]: 0.0s: subject\n" +
91
+ " | block message\n"
92
+ ],
93
+ @logdev.logs)
94
+ end
95
+
96
+ data(debug: :debug,
97
+ info: :info,
98
+ warn: :warn,
99
+ error: :error,
100
+ fatal: :fatal)
101
+ def test_multiple_entries(level)
102
+ @console_logger.send(level, "subject1")
103
+ @console_logger.send(level, "line2")
104
+ assert_equal([
105
+ "#{@timestamp_str} [#{level}]: 0.0s: subject1\n",
106
+ "#{@timestamp_str} [#{level}]: 0.0s: line2\n"
107
+ ],
108
+ @logdev.logs)
109
+ end
110
+ end
@@ -98,4 +98,43 @@ class AckHandlerTest < Test::Unit::TestCase
98
98
  w.close rescue nil
99
99
  end
100
100
  end
101
+
102
+ # ForwardOutput uses AckHandler in multiple threads, so we need to assume this case.
103
+ # If exclusive control for this case is implemented, this test may not be necessary.
104
+ test 'raises no error when another thread closes a socket' do
105
+ ack_handler = Fluent::Plugin::ForwardOutput::AckHandler.new(timeout: 10, log: $log, read_length: 100)
106
+
107
+ node = flexmock('node', host: '127.0.0.1', port: '1000') # for log
108
+ chunk_id = 'chunk_id 111'
109
+ ack = ack_handler.create_ack(chunk_id, node)
110
+
111
+ r, w = IO.pipe
112
+ begin
113
+ w.write(chunk_id)
114
+ stub(r).recv { |_|
115
+ sleep(1) # To ensure that multiple threads select the socket before closing.
116
+ raise IOError, 'stream closed in another thread' if r.closed?
117
+ MessagePack.pack({ 'ack' => Base64.encode64('chunk_id 111') })
118
+ }
119
+ ack.enqueue(r)
120
+
121
+ threads = []
122
+ 2.times do
123
+ threads << Thread.new do
124
+ ack_handler.collect_response(1) do |cid, n, s, ret|
125
+ s&.close
126
+ end
127
+ end
128
+ end
129
+
130
+ assert_true threads.map{ |t| t.join(10) }.all?
131
+ assert_false(
132
+ $log.out.logs.any? { |log| log.include?('[error]') },
133
+ $log.out.logs.select{ |log| log.include?('[error]') }.join('\n')
134
+ )
135
+ ensure
136
+ r.close rescue nil
137
+ w.close rescue nil
138
+ end
139
+ end
101
140
  end
@@ -146,4 +146,102 @@ class BaseTest < Test::Unit::TestCase
146
146
  end
147
147
  end
148
148
  end
149
+
150
+ test '`ArgumentError` when `conf` is not `Fluent::Config::Element`' do
151
+ assert_raise ArgumentError.new('BUG: type of conf must be Fluent::Config::Element, but Hash is passed.') do
152
+ @p.configure({})
153
+ end
154
+ end
155
+
156
+ sub_test_case 'system_config.workers value after configure' do
157
+ def assert_system_config_workers_value(data)
158
+ conf = config_element()
159
+ conf.set_target_worker_ids(data[:target_worker_ids])
160
+ @p.configure(conf)
161
+ assert{ @p.system_config.workers == data[:expected] }
162
+ end
163
+
164
+ def stub_supervisor_mode
165
+ stub(Fluent::Engine).supervisor_mode { true }
166
+ stub(Fluent::Engine).worker_id { -1 }
167
+ end
168
+
169
+ sub_test_case 'with <system> workers 3 </system>' do
170
+ setup do
171
+ system_config = Fluent::SystemConfig.new
172
+ system_config.workers = 3
173
+ stub(Fluent::Engine).system_config { system_config }
174
+ end
175
+
176
+ data(
177
+ 'without <worker> directive',
178
+ {
179
+ target_worker_ids: [],
180
+ expected: 3
181
+ },
182
+ keep: true
183
+ )
184
+ data(
185
+ 'with <worker 0>',
186
+ {
187
+ target_worker_ids: [0],
188
+ expected: 1
189
+ },
190
+ keep: true
191
+ )
192
+ data(
193
+ 'with <worker 0-1>',
194
+ {
195
+ target_worker_ids: [0, 1],
196
+ expected: 2
197
+ },
198
+ keep: true
199
+ )
200
+ data(
201
+ 'with <worker 0-2>',
202
+ {
203
+ target_worker_ids: [0, 1, 2],
204
+ expected: 3
205
+ },
206
+ keep: true
207
+ )
208
+
209
+ test 'system_config.workers value after configure' do
210
+ assert_system_config_workers_value(data)
211
+ end
212
+
213
+ test 'system_config.workers value after configure with supervisor_mode' do
214
+ stub_supervisor_mode
215
+ assert_system_config_workers_value(data)
216
+ end
217
+ end
218
+
219
+ sub_test_case 'without <system> directive' do
220
+ data(
221
+ 'without <worker> directive',
222
+ {
223
+ target_worker_ids: [],
224
+ expected: 1
225
+ },
226
+ keep: true
227
+ )
228
+ data(
229
+ 'with <worker 0>',
230
+ {
231
+ target_worker_ids: [0],
232
+ expected: 1
233
+ },
234
+ keep: true
235
+ )
236
+
237
+ test 'system_config.workers value after configure' do
238
+ assert_system_config_workers_value(data)
239
+ end
240
+
241
+ test 'system_config.workers value after configure with supervisor_mode' do
242
+ stub_supervisor_mode
243
+ assert_system_config_workers_value(data)
244
+ end
245
+ end
246
+ end
149
247
  end