fluentd 1.15.3 → 1.16.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) 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 +24 -0
  7. data/.github/workflows/windows-test.yaml +2 -2
  8. data/CHANGELOG.md +151 -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 +3 -3
  14. data/lib/fluent/command/ctl.rb +2 -2
  15. data/lib/fluent/command/fluentd.rb +55 -53
  16. data/lib/fluent/command/plugin_config_formatter.rb +1 -1
  17. data/lib/fluent/config/dsl.rb +1 -1
  18. data/lib/fluent/config/v1_parser.rb +2 -2
  19. data/lib/fluent/counter/server.rb +1 -1
  20. data/lib/fluent/counter/validator.rb +3 -3
  21. data/lib/fluent/daemon.rb +2 -4
  22. data/lib/fluent/engine.rb +1 -1
  23. data/lib/fluent/event.rb +8 -4
  24. data/lib/fluent/log/console_adapter.rb +66 -0
  25. data/lib/fluent/log.rb +44 -5
  26. data/lib/fluent/match.rb +1 -1
  27. data/lib/fluent/msgpack_factory.rb +6 -1
  28. data/lib/fluent/plugin/base.rb +6 -8
  29. data/lib/fluent/plugin/buf_file.rb +32 -3
  30. data/lib/fluent/plugin/buf_file_single.rb +32 -3
  31. data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
  32. data/lib/fluent/plugin/buffer.rb +21 -0
  33. data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
  34. data/lib/fluent/plugin/in_forward.rb +1 -1
  35. data/lib/fluent/plugin/in_http.rb +8 -8
  36. data/lib/fluent/plugin/in_sample.rb +1 -1
  37. data/lib/fluent/plugin/in_tail/position_file.rb +32 -18
  38. data/lib/fluent/plugin/in_tail.rb +58 -24
  39. data/lib/fluent/plugin/in_tcp.rb +47 -2
  40. data/lib/fluent/plugin/out_exec_filter.rb +2 -2
  41. data/lib/fluent/plugin/out_forward/ack_handler.rb +19 -4
  42. data/lib/fluent/plugin/out_forward.rb +2 -2
  43. data/lib/fluent/plugin/out_secondary_file.rb +39 -22
  44. data/lib/fluent/plugin/output.rb +50 -13
  45. data/lib/fluent/plugin/parser_json.rb +1 -1
  46. data/lib/fluent/plugin_helper/event_loop.rb +2 -2
  47. data/lib/fluent/plugin_helper/http_server/server.rb +2 -1
  48. data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
  49. data/lib/fluent/plugin_helper/server.rb +8 -0
  50. data/lib/fluent/plugin_helper/thread.rb +3 -3
  51. data/lib/fluent/plugin_id.rb +1 -1
  52. data/lib/fluent/supervisor.rb +157 -251
  53. data/lib/fluent/test/driver/base.rb +11 -5
  54. data/lib/fluent/test/driver/filter.rb +4 -0
  55. data/lib/fluent/test/startup_shutdown.rb +6 -8
  56. data/lib/fluent/version.rb +1 -1
  57. data/templates/new_gem/test/helper.rb.erb +0 -1
  58. data/test/command/test_ctl.rb +1 -1
  59. data/test/command/test_fluentd.rb +137 -6
  60. data/test/command/test_plugin_config_formatter.rb +0 -1
  61. data/test/compat/test_parser.rb +5 -5
  62. data/test/config/test_system_config.rb +0 -8
  63. data/test/log/test_console_adapter.rb +110 -0
  64. data/test/plugin/in_tail/test_position_file.rb +31 -1
  65. data/test/plugin/out_forward/test_ack_handler.rb +39 -0
  66. data/test/plugin/test_base.rb +99 -1
  67. data/test/plugin/test_buf_file.rb +62 -23
  68. data/test/plugin/test_buf_file_single.rb +65 -0
  69. data/test/plugin/test_buffer_chunk.rb +11 -0
  70. data/test/plugin/test_in_forward.rb +9 -9
  71. data/test/plugin/test_in_http.rb +2 -3
  72. data/test/plugin/test_in_monitor_agent.rb +2 -3
  73. data/test/plugin/test_in_tail.rb +379 -0
  74. data/test/plugin/test_in_tcp.rb +87 -2
  75. data/test/plugin/test_in_udp.rb +28 -0
  76. data/test/plugin/test_in_unix.rb +2 -2
  77. data/test/plugin/test_multi_output.rb +1 -1
  78. data/test/plugin/test_out_exec_filter.rb +2 -2
  79. data/test/plugin/test_out_file.rb +2 -2
  80. data/test/plugin/test_out_forward.rb +14 -18
  81. data/test/plugin/test_out_http.rb +1 -0
  82. data/test/plugin/test_output.rb +281 -12
  83. data/test/plugin/test_output_as_buffered.rb +44 -44
  84. data/test/plugin/test_output_as_buffered_compress.rb +32 -18
  85. data/test/plugin/test_output_as_buffered_retries.rb +1 -1
  86. data/test/plugin/test_output_as_buffered_secondary.rb +2 -2
  87. data/test/plugin/test_parser_regexp.rb +1 -6
  88. data/test/plugin_helper/test_child_process.rb +2 -2
  89. data/test/plugin_helper/test_http_server_helper.rb +1 -1
  90. data/test/plugin_helper/test_server.rb +60 -6
  91. data/test/test_config.rb +0 -21
  92. data/test/test_formatter.rb +23 -20
  93. data/test/test_log.rb +108 -36
  94. data/test/test_msgpack_factory.rb +32 -0
  95. data/test/test_supervisor.rb +287 -279
  96. metadata +15 -21
  97. data/.drone.yml +0 -35
  98. data/.gitlab-ci.yml +0 -103
  99. data/test/test_logger_initializer.rb +0 -46
@@ -16,6 +16,7 @@
16
16
 
17
17
  require 'fluent/config'
18
18
  require 'fluent/config/element'
19
+ require 'fluent/env'
19
20
  require 'fluent/log'
20
21
  require 'fluent/clock'
21
22
 
@@ -102,11 +103,16 @@ module Fluent
102
103
 
103
104
  def instance_start
104
105
  if @instance.respond_to?(:server_wait_until_start)
105
- @socket_manager_path = ServerEngine::SocketManager::Server.generate_path
106
- if @socket_manager_path.is_a?(String) && File.exist?(@socket_manager_path)
107
- FileUtils.rm_f @socket_manager_path
106
+ if Fluent.windows?
107
+ @socket_manager_server = ServerEngine::SocketManager::Server.open
108
+ @socket_manager_path = @socket_manager_server.path
109
+ else
110
+ @socket_manager_path = ServerEngine::SocketManager::Server.generate_path
111
+ if @socket_manager_path.is_a?(String) && File.exist?(@socket_manager_path)
112
+ FileUtils.rm_f @socket_manager_path
113
+ end
114
+ @socket_manager_server = ServerEngine::SocketManager::Server.open(@socket_manager_path)
108
115
  end
109
- @socket_manager_server = ServerEngine::SocketManager::Server.open(@socket_manager_path)
110
116
  ENV['SERVERENGINE_SOCKETMANAGER_PATH'] = @socket_manager_path.to_s
111
117
  end
112
118
 
@@ -173,7 +179,7 @@ module Fluent
173
179
 
174
180
  if @socket_manager_server
175
181
  @socket_manager_server.close
176
- if @socket_manager_server.is_a?(String) && File.exist?(@socket_manager_path)
182
+ if @socket_manager_path.is_a?(String) && File.exist?(@socket_manager_path)
177
183
  FileUtils.rm_f @socket_manager_path
178
184
  end
179
185
  end
@@ -37,6 +37,10 @@ module Fluent
37
37
  @filtered.map {|_time, record| record }
38
38
  end
39
39
 
40
+ def filtered_time
41
+ @filtered.map {|time, _record| time }
42
+ end
43
+
40
44
  def instance_hook_after_started
41
45
  super
42
46
  filter_hook = ->(time, record) { @filtered << [time, record] }
@@ -21,9 +21,8 @@ module Fluent
21
21
  module Test
22
22
  module StartupShutdown
23
23
  def startup
24
- socket_manager_path = ServerEngine::SocketManager::Server.generate_path
25
- @server = ServerEngine::SocketManager::Server.open(socket_manager_path)
26
- ENV['SERVERENGINE_SOCKETMANAGER_PATH'] = socket_manager_path.to_s
24
+ @server = ServerEngine::SocketManager::Server.open
25
+ ENV['SERVERENGINE_SOCKETMANAGER_PATH'] = @server.path.to_s
27
26
  end
28
27
 
29
28
  def shutdown
@@ -31,15 +30,14 @@ module Fluent
31
30
  end
32
31
 
33
32
  def self.setup
34
- @socket_manager_path = ServerEngine::SocketManager::Server.generate_path
35
- @server = ServerEngine::SocketManager::Server.open(@socket_manager_path)
36
- ENV['SERVERENGINE_SOCKETMANAGER_PATH'] = @socket_manager_path.to_s
33
+ @server = ServerEngine::SocketManager::Server.open
34
+ ENV['SERVERENGINE_SOCKETMANAGER_PATH'] = @server.path.to_s
37
35
  end
38
36
 
39
37
  def self.teardown
40
38
  @server.close
41
- # on Windows, socket_manager_path is a TCP port number
42
- FileUtils.rm_f @socket_manager_path unless Fluent.windows?
39
+ # on Windows, the path is a TCP port number
40
+ FileUtils.rm_f @server.path unless Fluent.windows?
43
41
  end
44
42
  end
45
43
  end
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '1.15.3'
19
+ VERSION = '1.16.2'
20
20
 
21
21
  end
@@ -1,4 +1,3 @@
1
- $LOAD_PATH.unshift(File.expand_path("../../", __FILE__))
2
1
  require "test-unit"
3
2
  require "fluent/test"
4
3
  require "fluent/test/driver/<%= type %>"
@@ -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
@@ -71,6 +71,20 @@ class TestFluentdCommand < ::Test::Unit::TestCase
71
71
  end
72
72
  end
73
73
 
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
+
74
88
  def execute_command(cmdline, chdir=@tmp_dir, env = {})
75
89
  null_stream = Fluent::FileWrapper.open(File::NULL, 'w')
76
90
  gemfile_path = File.expand_path(File.dirname(__FILE__) + "../../../Gemfile")
@@ -85,12 +99,12 @@ class TestFluentdCommand < ::Test::Unit::TestCase
85
99
  yield pid, io
86
100
  # p(here: "execute command", pid: pid, worker_pids: @worker_pids)
87
101
  ensure
88
- Process.kill(:KILL, pid) rescue nil
102
+ process_kill(pid)
89
103
  if @supervisor_pid
90
- Process.kill(:KILL, @supervisor_pid) rescue nil
104
+ process_kill(@supervisor_pid)
91
105
  end
92
106
  @worker_pids.each do |cpid|
93
- Process.kill(:KILL, cpid) rescue nil
107
+ process_kill(cpid)
94
108
  end
95
109
  # p(here: "execute command", pid: pid, exist: process_exist?(pid), worker_pids: @worker_pids, exists: @worker_pids.map{|i| process_exist?(i) })
96
110
  Timeout.timeout(10){ sleep 0.1 while process_exist?(pid) }
@@ -112,7 +126,9 @@ class TestFluentdCommand < ::Test::Unit::TestCase
112
126
  end
113
127
  end
114
128
 
115
- 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: {})
116
132
  matched = false
117
133
  matched_wrongly = false
118
134
  assert_error_msg = ""
@@ -121,7 +137,7 @@ class TestFluentdCommand < ::Test::Unit::TestCase
121
137
  execute_command(cmdline, @tmp_dir, env) do |pid, stdout|
122
138
  begin
123
139
  waiting(timeout) do
124
- while process_exist?(pid) && !matched
140
+ while process_exist?(pid)
125
141
  readables, _, _ = IO.select([stdout], nil, nil, 1)
126
142
  next unless readables
127
143
  break if readables.first.eof?
@@ -133,6 +149,18 @@ class TestFluentdCommand < ::Test::Unit::TestCase
133
149
  if pattern_list.all?{|ptn| lines.any?{|line| ptn.is_a?(Regexp) ? ptn.match(line) : line.include?(ptn) } }
134
150
  matched = true
135
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
136
164
  end
137
165
  end
138
166
  ensure
@@ -146,6 +174,10 @@ class TestFluentdCommand < ::Test::Unit::TestCase
146
174
  end
147
175
  rescue Timeout::Error
148
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?
149
181
  rescue => e
150
182
  assert_error_msg = "unexpected error in launching fluentd: #{e.inspect}"
151
183
  else
@@ -177,7 +209,7 @@ class TestFluentdCommand < ::Test::Unit::TestCase
177
209
  assert matched && !matched_wrongly, assert_error_msg
178
210
  end
179
211
 
180
- def assert_fluentd_fails_to_start(cmdline, *pattern_list, timeout: 10)
212
+ def assert_fluentd_fails_to_start(cmdline, *pattern_list, timeout: 20)
181
213
  # empty_list.all?{ ... } is always true
182
214
  matched = false
183
215
  running = false
@@ -213,6 +245,10 @@ class TestFluentdCommand < ::Test::Unit::TestCase
213
245
  end
214
246
  rescue Timeout::Error
215
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?
216
252
  rescue => e
217
253
  assert_error_msg = "unexpected error in launching fluentd: #{e.inspect}\n" + stdio_buf
218
254
  assert false, assert_error_msg
@@ -1151,4 +1187,99 @@ CONF
1151
1187
  "shared socket for multiple workers is disabled",)
1152
1188
  end
1153
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
1154
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
@@ -26,6 +26,10 @@ class IntailPositionFileTest < Test::Unit::TestCase
26
26
  "valid_path" => Fluent::Plugin::TailInput::TargetInfo.new("valid_path", 1),
27
27
  "inode23bit" => Fluent::Plugin::TailInput::TargetInfo.new("inode23bit", 0),
28
28
  }
29
+ TEST_CONTENT_INODES = {
30
+ 1 => Fluent::Plugin::TailInput::TargetInfo.new("valid_path", 1),
31
+ 0 => Fluent::Plugin::TailInput::TargetInfo.new("inode23bit", 0),
32
+ }
29
33
 
30
34
  def write_data(f, content)
31
35
  f.write(content)
@@ -221,7 +225,7 @@ class IntailPositionFileTest < Test::Unit::TestCase
221
225
  end
222
226
 
223
227
  sub_test_case '#unwatch' do
224
- test 'deletes entry by path' do
228
+ test 'unwatch entry by path' do
225
229
  write_data(@file, TEST_CONTENT)
226
230
  pf = Fluent::Plugin::TailInput::PositionFile.load(@file, false, {}, logger: $log)
227
231
  inode1 = File.stat(@file).ino
@@ -239,6 +243,32 @@ class IntailPositionFileTest < Test::Unit::TestCase
239
243
 
240
244
  assert_not_equal p1, p2
241
245
  end
246
+
247
+ test 'unwatch entries by inode' do
248
+ write_data(@file, TEST_CONTENT)
249
+ pf = Fluent::Plugin::TailInput::PositionFile.load(@file, true, TEST_CONTENT_INODES, logger: $log)
250
+
251
+ existing_targets = TEST_CONTENT_INODES.select do |inode, target_info|
252
+ inode == 1
253
+ end
254
+ pe_to_unwatch = pf[TEST_CONTENT_INODES[0]]
255
+
256
+ pf.unwatch_removed_targets(existing_targets)
257
+
258
+ assert_equal(
259
+ {
260
+ map_keys: [TEST_CONTENT_INODES[1].ino],
261
+ unwatched_pe_pos: Fluent::Plugin::TailInput::PositionFile::UNWATCHED_POSITION,
262
+ },
263
+ {
264
+ map_keys: pf.instance_variable_get(:@map).keys,
265
+ unwatched_pe_pos: pe_to_unwatch.read_pos,
266
+ }
267
+ )
268
+
269
+ unwatched_pe_retaken = pf[TEST_CONTENT_INODES[0]]
270
+ assert_not_equal pe_to_unwatch, unwatched_pe_retaken
271
+ end
242
272
  end
243
273
 
244
274
  sub_test_case 'FilePositionEntry' do
@@ -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