fluentd 1.13.3 → 1.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/{bug_report.yaml → bug_report.yml} +2 -0
- data/.github/ISSUE_TEMPLATE/config.yml +2 -2
- data/.github/ISSUE_TEMPLATE/{feature_request.yaml → feature_request.yml} +1 -0
- data/.github/workflows/stale-actions.yml +11 -9
- data/.github/workflows/test.yml +32 -0
- data/CHANGELOG.md +490 -10
- data/CONTRIBUTING.md +2 -2
- data/MAINTAINERS.md +7 -5
- data/README.md +3 -23
- data/Rakefile +1 -1
- data/SECURITY.md +14 -0
- data/fluentd.gemspec +7 -8
- data/lib/fluent/command/cat.rb +13 -3
- data/lib/fluent/command/ctl.rb +6 -3
- data/lib/fluent/command/fluentd.rb +73 -65
- data/lib/fluent/command/plugin_config_formatter.rb +1 -1
- data/lib/fluent/compat/output.rb +9 -6
- data/lib/fluent/config/dsl.rb +1 -1
- data/lib/fluent/config/error.rb +12 -0
- data/lib/fluent/config/literal_parser.rb +2 -2
- data/lib/fluent/config/parser.rb +1 -1
- data/lib/fluent/config/v1_parser.rb +3 -3
- data/lib/fluent/config/yaml_parser/fluent_value.rb +47 -0
- data/lib/fluent/config/yaml_parser/loader.rb +108 -0
- data/lib/fluent/config/yaml_parser/parser.rb +166 -0
- data/lib/fluent/config/yaml_parser/section_builder.rb +107 -0
- data/lib/fluent/config/yaml_parser.rb +56 -0
- data/lib/fluent/config.rb +14 -1
- data/lib/fluent/counter/server.rb +1 -1
- data/lib/fluent/counter/validator.rb +3 -3
- data/lib/fluent/daemon.rb +2 -4
- data/lib/fluent/engine.rb +1 -1
- data/lib/fluent/env.rb +4 -0
- data/lib/fluent/error.rb +3 -0
- data/lib/fluent/event.rb +8 -4
- data/lib/fluent/event_router.rb +47 -2
- data/lib/fluent/file_wrapper.rb +137 -0
- data/lib/fluent/log/console_adapter.rb +66 -0
- data/lib/fluent/log.rb +44 -5
- data/lib/fluent/match.rb +1 -1
- data/lib/fluent/msgpack_factory.rb +6 -1
- data/lib/fluent/oj_options.rb +1 -2
- data/lib/fluent/plugin/bare_output.rb +49 -8
- data/lib/fluent/plugin/base.rb +26 -9
- data/lib/fluent/plugin/buf_file.rb +34 -5
- data/lib/fluent/plugin/buf_file_single.rb +32 -3
- data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
- data/lib/fluent/plugin/buffer.rb +216 -70
- data/lib/fluent/plugin/filter.rb +35 -1
- data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
- data/lib/fluent/plugin/in_forward.rb +2 -2
- data/lib/fluent/plugin/in_http.rb +39 -10
- data/lib/fluent/plugin/in_monitor_agent.rb +4 -2
- data/lib/fluent/plugin/in_sample.rb +1 -1
- data/lib/fluent/plugin/in_syslog.rb +13 -1
- data/lib/fluent/plugin/in_tail/group_watch.rb +204 -0
- data/lib/fluent/plugin/in_tail/position_file.rb +33 -33
- data/lib/fluent/plugin/in_tail.rb +216 -84
- data/lib/fluent/plugin/in_tcp.rb +47 -2
- data/lib/fluent/plugin/input.rb +39 -1
- data/lib/fluent/plugin/metrics.rb +119 -0
- data/lib/fluent/plugin/metrics_local.rb +96 -0
- data/lib/fluent/plugin/multi_output.rb +43 -6
- data/lib/fluent/plugin/out_copy.rb +1 -1
- data/lib/fluent/plugin/out_exec_filter.rb +2 -2
- data/lib/fluent/plugin/out_file.rb +20 -2
- data/lib/fluent/plugin/out_forward/ack_handler.rb +19 -4
- data/lib/fluent/plugin/out_forward/socket_cache.rb +2 -0
- data/lib/fluent/plugin/out_forward.rb +17 -9
- data/lib/fluent/plugin/out_secondary_file.rb +39 -22
- data/lib/fluent/plugin/output.rb +167 -78
- data/lib/fluent/plugin/parser.rb +3 -4
- data/lib/fluent/plugin/parser_apache2.rb +1 -1
- data/lib/fluent/plugin/parser_json.rb +1 -1
- data/lib/fluent/plugin/parser_syslog.rb +1 -1
- data/lib/fluent/plugin/storage_local.rb +3 -5
- data/lib/fluent/plugin.rb +10 -1
- data/lib/fluent/plugin_helper/child_process.rb +3 -0
- data/lib/fluent/plugin_helper/event_emitter.rb +8 -1
- data/lib/fluent/plugin_helper/event_loop.rb +2 -2
- data/lib/fluent/plugin_helper/http_server/server.rb +2 -1
- data/lib/fluent/plugin_helper/metrics.rb +129 -0
- data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
- data/lib/fluent/plugin_helper/retry_state.rb +14 -4
- data/lib/fluent/plugin_helper/server.rb +35 -6
- data/lib/fluent/plugin_helper/service_discovery.rb +2 -2
- data/lib/fluent/plugin_helper/socket.rb +13 -2
- data/lib/fluent/plugin_helper/thread.rb +3 -3
- data/lib/fluent/plugin_helper.rb +1 -0
- data/lib/fluent/plugin_id.rb +3 -2
- data/lib/fluent/registry.rb +2 -1
- data/lib/fluent/root_agent.rb +6 -0
- data/lib/fluent/rpc.rb +4 -3
- data/lib/fluent/supervisor.rb +283 -259
- data/lib/fluent/system_config.rb +13 -3
- data/lib/fluent/test/driver/base.rb +11 -5
- data/lib/fluent/test/driver/filter.rb +4 -0
- data/lib/fluent/test/startup_shutdown.rb +6 -8
- data/lib/fluent/time.rb +21 -20
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/win32api.rb +38 -0
- data/lib/fluent/winsvc.rb +5 -8
- data/templates/new_gem/test/helper.rb.erb +0 -1
- data/test/command/test_cat.rb +31 -2
- data/test/command/test_ctl.rb +1 -2
- data/test/command/test_fluentd.rb +209 -24
- data/test/command/test_plugin_config_formatter.rb +0 -1
- data/test/compat/test_parser.rb +6 -6
- data/test/config/test_system_config.rb +13 -11
- data/test/config/test_types.rb +1 -1
- data/test/log/test_console_adapter.rb +110 -0
- data/test/plugin/in_tail/test_io_handler.rb +26 -8
- data/test/plugin/in_tail/test_position_file.rb +48 -59
- data/test/plugin/out_forward/test_ack_handler.rb +39 -0
- data/test/plugin/out_forward/test_socket_cache.rb +26 -1
- data/test/plugin/test_bare_output.rb +14 -1
- data/test/plugin/test_base.rb +133 -1
- data/test/plugin/test_buf_file.rb +62 -23
- data/test/plugin/test_buf_file_single.rb +65 -0
- data/test/plugin/test_buffer.rb +267 -3
- data/test/plugin/test_buffer_chunk.rb +11 -0
- data/test/plugin/test_filter.rb +12 -1
- data/test/plugin/test_filter_parser.rb +1 -1
- data/test/plugin/test_filter_stdout.rb +2 -2
- data/test/plugin/test_in_forward.rb +9 -11
- data/test/plugin/test_in_http.rb +65 -3
- data/test/plugin/test_in_monitor_agent.rb +216 -11
- data/test/plugin/test_in_object_space.rb +9 -3
- data/test/plugin/test_in_syslog.rb +35 -0
- data/test/plugin/test_in_tail.rb +1393 -385
- data/test/plugin/test_in_tcp.rb +87 -2
- data/test/plugin/test_in_udp.rb +28 -0
- data/test/plugin/test_in_unix.rb +2 -2
- data/test/plugin/test_input.rb +12 -1
- data/test/plugin/test_metrics.rb +294 -0
- data/test/plugin/test_metrics_local.rb +96 -0
- data/test/plugin/test_multi_output.rb +25 -1
- data/test/plugin/test_out_exec.rb +6 -4
- data/test/plugin/test_out_exec_filter.rb +6 -2
- data/test/plugin/test_out_file.rb +34 -17
- data/test/plugin/test_out_forward.rb +78 -77
- data/test/plugin/test_out_http.rb +1 -0
- data/test/plugin/test_out_stdout.rb +2 -2
- data/test/plugin/test_output.rb +297 -12
- data/test/plugin/test_output_as_buffered.rb +44 -44
- data/test/plugin/test_output_as_buffered_compress.rb +32 -18
- data/test/plugin/test_output_as_buffered_retries.rb +54 -7
- data/test/plugin/test_output_as_buffered_secondary.rb +4 -4
- data/test/plugin/test_parser_regexp.rb +1 -6
- data/test/plugin/test_parser_syslog.rb +1 -1
- data/test/plugin_helper/test_cert_option.rb +1 -1
- data/test/plugin_helper/test_child_process.rb +38 -16
- data/test/plugin_helper/test_event_emitter.rb +29 -0
- data/test/plugin_helper/test_http_server_helper.rb +1 -1
- data/test/plugin_helper/test_metrics.rb +137 -0
- data/test/plugin_helper/test_retry_state.rb +602 -38
- data/test/plugin_helper/test_server.rb +78 -6
- data/test/plugin_helper/test_timer.rb +2 -2
- data/test/test_config.rb +191 -24
- data/test/test_event_router.rb +17 -0
- data/test/test_file_wrapper.rb +53 -0
- data/test/test_formatter.rb +24 -21
- data/test/test_log.rb +122 -40
- data/test/test_msgpack_factory.rb +32 -0
- data/test/test_plugin_classes.rb +102 -0
- data/test/test_root_agent.rb +30 -1
- data/test/test_supervisor.rb +477 -257
- data/test/test_time_parser.rb +22 -0
- metadata +55 -34
- data/.drone.yml +0 -35
- data/.github/workflows/issue-auto-closer.yml +0 -12
- data/.github/workflows/linux-test.yaml +0 -36
- data/.github/workflows/macos-test.yaml +0 -30
- data/.github/workflows/windows-test.yaml +0 -46
- data/.gitlab-ci.yml +0 -103
- data/lib/fluent/plugin/file_wrapper.rb +0 -187
- data/test/plugin/test_file_wrapper.rb +0 -126
- data/test/test_logger_initializer.rb +0 -46
data/test/plugin/test_in_tail.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'fluent/test/driver/input'
|
|
|
3
3
|
require 'fluent/plugin/in_tail'
|
|
4
4
|
require 'fluent/plugin/buffer'
|
|
5
5
|
require 'fluent/system_config'
|
|
6
|
+
require 'fluent/file_wrapper'
|
|
6
7
|
require 'net/http'
|
|
7
8
|
require 'flexmock/test_unit'
|
|
8
9
|
require 'timecop'
|
|
@@ -12,15 +13,21 @@ require 'securerandom'
|
|
|
12
13
|
class TailInputTest < Test::Unit::TestCase
|
|
13
14
|
include FlexMock::TestCase
|
|
14
15
|
|
|
16
|
+
def tmp_dir
|
|
17
|
+
File.join(File.dirname(__FILE__), "..", "tmp", "tail#{ENV['TEST_ENV_NUMBER']}", SecureRandom.hex(10))
|
|
18
|
+
end
|
|
19
|
+
|
|
15
20
|
def setup
|
|
16
21
|
Fluent::Test.setup
|
|
17
|
-
|
|
22
|
+
@tmp_dir = tmp_dir
|
|
23
|
+
cleanup_directory(@tmp_dir)
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def teardown
|
|
21
27
|
super
|
|
22
|
-
cleanup_directory(
|
|
28
|
+
cleanup_directory(@tmp_dir)
|
|
23
29
|
Fluent::Engine.stop
|
|
30
|
+
Timecop.return
|
|
24
31
|
end
|
|
25
32
|
|
|
26
33
|
def cleanup_directory(path)
|
|
@@ -29,89 +36,48 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
29
36
|
return
|
|
30
37
|
end
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
Dir.glob("*", base: path).each do |name|
|
|
34
|
-
begin
|
|
35
|
-
cleanup_file(File.join(path, name))
|
|
36
|
-
rescue
|
|
37
|
-
# expect test driver block release already owned file handle.
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
else
|
|
41
|
-
begin
|
|
42
|
-
FileUtils.rm_f(path, secure:true)
|
|
43
|
-
rescue ArgumentError
|
|
44
|
-
FileUtils.rm_f(path) # For Ruby 2.6 or before.
|
|
45
|
-
end
|
|
46
|
-
if File.exist?(path)
|
|
47
|
-
FileUtils.remove_entry_secure(path, true)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
FileUtils.mkdir_p(path)
|
|
39
|
+
FileUtils.remove_entry_secure(path, true)
|
|
51
40
|
end
|
|
52
41
|
|
|
53
42
|
def cleanup_file(path)
|
|
54
|
-
|
|
55
|
-
# On Windows, when the file or directory is removed and created
|
|
56
|
-
# frequently, there is a case that creating file or directory will
|
|
57
|
-
# fail. This situation is caused by pending file or directory
|
|
58
|
-
# deletion which is mentioned on win32 API document [1]
|
|
59
|
-
# As a workaround, execute rename and remove method.
|
|
60
|
-
#
|
|
61
|
-
# [1] https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#files
|
|
62
|
-
#
|
|
63
|
-
file = File.join(Dir.tmpdir, SecureRandom.hex(10))
|
|
64
|
-
begin
|
|
65
|
-
FileUtils.mv(path, file)
|
|
66
|
-
FileUtils.rm_rf(file, secure: true)
|
|
67
|
-
rescue ArgumentError
|
|
68
|
-
FileUtils.rm_rf(file) # For Ruby 2.6 or before.
|
|
69
|
-
end
|
|
70
|
-
if File.exist?(file)
|
|
71
|
-
# ensure files are closed for Windows, on which deleted files
|
|
72
|
-
# are still visible from filesystem
|
|
73
|
-
GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
|
|
74
|
-
FileUtils.remove_entry_secure(file, true)
|
|
75
|
-
end
|
|
76
|
-
else
|
|
77
|
-
begin
|
|
78
|
-
FileUtils.rm_f(path, secure: true)
|
|
79
|
-
rescue ArgumentError
|
|
80
|
-
FileUtils.rm_f(path) # For Ruby 2.6 or before.
|
|
81
|
-
end
|
|
82
|
-
if File.exist?(path)
|
|
83
|
-
FileUtils.remove_entry_secure(path, true)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
43
|
+
FileUtils.remove_entry_secure(path, true)
|
|
86
44
|
end
|
|
87
45
|
|
|
88
46
|
def create_target_info(path)
|
|
89
47
|
Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
|
|
90
48
|
end
|
|
91
49
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
CONFIG = config_element("ROOT", "", {
|
|
95
|
-
"path" => "#{TMP_DIR}/tail.txt",
|
|
50
|
+
ROOT_CONFIG = config_element("ROOT", "", {
|
|
96
51
|
"tag" => "t1",
|
|
97
52
|
"rotate_wait" => "2s",
|
|
98
53
|
"refresh_interval" => "1s"
|
|
99
|
-
|
|
100
|
-
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
def base_config
|
|
57
|
+
ROOT_CONFIG + config_element("", "", { "path" => "#{@tmp_dir}/tail.txt" })
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def common_config
|
|
61
|
+
base_config + config_element("", "", { "pos_file" => "#{@tmp_dir}/tail.pos" })
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def common_follow_inode_config
|
|
65
|
+
config_element("ROOT", "", {
|
|
66
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
67
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
68
|
+
"tag" => "t1",
|
|
69
|
+
"refresh_interval" => "1s",
|
|
70
|
+
"read_from_head" => "true",
|
|
71
|
+
"format" => "none",
|
|
72
|
+
"rotate_wait" => "1s",
|
|
73
|
+
"follow_inodes" => "true"
|
|
74
|
+
})
|
|
75
|
+
end
|
|
76
|
+
|
|
101
77
|
CONFIG_READ_FROM_HEAD = config_element("", "", { "read_from_head" => true })
|
|
102
|
-
|
|
78
|
+
CONFIG_DISABLE_WATCH_TIMER = config_element("", "", { "enable_watch_timer" => false })
|
|
103
79
|
CONFIG_DISABLE_STAT_WATCHER = config_element("", "", { "enable_stat_watcher" => false })
|
|
104
80
|
CONFIG_OPEN_ON_EVERY_UPDATE = config_element("", "", { "open_on_every_update" => true })
|
|
105
|
-
COMMON_FOLLOW_INODE_CONFIG = config_element("ROOT", "", {
|
|
106
|
-
"path" => "#{TMP_DIR}/tail.txt*",
|
|
107
|
-
"pos_file" => "#{TMP_DIR}/tail.pos",
|
|
108
|
-
"tag" => "t1",
|
|
109
|
-
"refresh_interval" => "1s",
|
|
110
|
-
"read_from_head" => "true",
|
|
111
|
-
"format" => "none",
|
|
112
|
-
"rotate_wait" => "1s",
|
|
113
|
-
"follow_inodes" => "true"
|
|
114
|
-
})
|
|
115
81
|
SINGLE_LINE_CONFIG = config_element("", "", { "format" => "/(?<message>.*)/" })
|
|
116
82
|
PARSE_SINGLE_LINE_CONFIG = config_element("", "", {}, [config_element("parse", "", { "@type" => "/(?<message>.*)/" })])
|
|
117
83
|
MULTILINE_CONFIG = config_element(
|
|
@@ -144,21 +110,67 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
144
110
|
})
|
|
145
111
|
])
|
|
146
112
|
|
|
113
|
+
EX_ROTATE_WAIT = 0
|
|
114
|
+
EX_FOLLOW_INODES = false
|
|
115
|
+
|
|
116
|
+
def ex_config
|
|
117
|
+
config_element("", "", {
|
|
118
|
+
"tag" => "tail",
|
|
119
|
+
"path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
|
|
120
|
+
"format" => "none",
|
|
121
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
122
|
+
"read_from_head" => true,
|
|
123
|
+
"refresh_interval" => 30,
|
|
124
|
+
"rotate_wait" => "#{EX_ROTATE_WAIT}s",
|
|
125
|
+
"follow_inodes" => "#{EX_FOLLOW_INODES}",
|
|
126
|
+
})
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def tailing_group_pattern
|
|
130
|
+
"/#{@tmp_dir}\/(?<podname>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\/[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace>[^_]+)_(?<container>.+)-(?<docker_id>[a-z0-9]{6})\.log$/"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
DEBUG_LOG_LEVEL = config_element("", "", {
|
|
134
|
+
"@log_level" => "debug"
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
def create_group_directive(pattern, rate_period, *rules)
|
|
138
|
+
config_element("", "", {}, [
|
|
139
|
+
config_element("group", "", {
|
|
140
|
+
"pattern" => pattern,
|
|
141
|
+
"rate_period" => rate_period
|
|
142
|
+
}, rules)
|
|
143
|
+
])
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def create_rule_directive(match_named_captures, limit)
|
|
147
|
+
params = {
|
|
148
|
+
"limit" => limit,
|
|
149
|
+
"match" => match_named_captures,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
config_element("rule", "", params)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def create_path_element(path)
|
|
156
|
+
config_element("source", "", { "path" => "#{@tmp_dir}/#{path}" })
|
|
157
|
+
end
|
|
158
|
+
|
|
147
159
|
def create_driver(conf = SINGLE_LINE_CONFIG, use_common_conf = true)
|
|
148
|
-
config = use_common_conf ?
|
|
160
|
+
config = use_common_conf ? common_config + conf : conf
|
|
149
161
|
Fluent::Test::Driver::Input.new(Fluent::Plugin::TailInput).configure(config)
|
|
150
162
|
end
|
|
151
163
|
|
|
152
164
|
sub_test_case "configure" do
|
|
153
165
|
test "plain single line" do
|
|
154
166
|
d = create_driver
|
|
155
|
-
assert_equal
|
|
156
|
-
assert_equal
|
|
157
|
-
assert_equal
|
|
158
|
-
assert_equal
|
|
159
|
-
assert_equal
|
|
160
|
-
assert_equal
|
|
161
|
-
assert_equal
|
|
167
|
+
assert_equal(["#{@tmp_dir}/tail.txt"], d.instance.paths)
|
|
168
|
+
assert_equal("t1", d.instance.tag)
|
|
169
|
+
assert_equal(2, d.instance.rotate_wait)
|
|
170
|
+
assert_equal("#{@tmp_dir}/tail.pos", d.instance.pos_file)
|
|
171
|
+
assert_equal(1000, d.instance.read_lines_limit)
|
|
172
|
+
assert_equal(-1, d.instance.read_bytes_limit_per_second)
|
|
173
|
+
assert_equal(false, d.instance.ignore_repeated_permission_error)
|
|
162
174
|
assert_nothing_raised do
|
|
163
175
|
d.instance.have_read_capability?
|
|
164
176
|
end
|
|
@@ -175,13 +187,13 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
175
187
|
test "multi paths with path_delimiter" do
|
|
176
188
|
c = config_element("ROOT", "", { "path" => "tail.txt|test2|tmp,dev", "tag" => "t1", "path_delimiter" => "|" })
|
|
177
189
|
d = create_driver(c + PARSE_SINGLE_LINE_CONFIG, false)
|
|
178
|
-
assert_equal
|
|
190
|
+
assert_equal(["tail.txt", "test2", "tmp,dev"], d.instance.paths)
|
|
179
191
|
end
|
|
180
192
|
|
|
181
193
|
test "multi paths with same path configured twice" do
|
|
182
194
|
c = config_element("ROOT", "", { "path" => "test1.txt,test2.txt,test1.txt", "tag" => "t1", "path_delimiter" => "," })
|
|
183
195
|
d = create_driver(c + PARSE_SINGLE_LINE_CONFIG, false)
|
|
184
|
-
assert_equal
|
|
196
|
+
assert_equal(["test2.txt","test1.txt"].sort, d.instance.paths.sort)
|
|
185
197
|
end
|
|
186
198
|
|
|
187
199
|
test "multi paths with invaid path_delimiter" do
|
|
@@ -193,13 +205,13 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
193
205
|
|
|
194
206
|
test "follow_inodes w/o pos file" do
|
|
195
207
|
assert_raise(Fluent::ConfigError) do
|
|
196
|
-
create_driver(
|
|
208
|
+
create_driver(base_config + config_element('', '', {'follow_inodes' => 'true'}))
|
|
197
209
|
end
|
|
198
210
|
end
|
|
199
211
|
|
|
200
212
|
sub_test_case "log throttling per file" do
|
|
201
213
|
test "w/o watcher timer is invalid" do
|
|
202
|
-
conf =
|
|
214
|
+
conf = CONFIG_DISABLE_WATCH_TIMER + config_element("ROOT", "", {"read_bytes_limit_per_second" => "8k"})
|
|
203
215
|
assert_raise(Fluent::ConfigError) do
|
|
204
216
|
create_driver(conf)
|
|
205
217
|
end
|
|
@@ -215,7 +227,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
215
227
|
|
|
216
228
|
test "both enable_watch_timer and enable_stat_watcher are false" do
|
|
217
229
|
assert_raise(Fluent::ConfigError) do
|
|
218
|
-
create_driver(
|
|
230
|
+
create_driver(CONFIG_DISABLE_WATCH_TIMER + CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
|
|
219
231
|
end
|
|
220
232
|
end
|
|
221
233
|
|
|
@@ -223,7 +235,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
223
235
|
test "valid" do
|
|
224
236
|
conf = SINGLE_LINE_CONFIG + config_element("", "", { "encoding" => "utf-8" })
|
|
225
237
|
d = create_driver(conf)
|
|
226
|
-
assert_equal
|
|
238
|
+
assert_equal(Encoding::UTF_8, d.instance.encoding)
|
|
227
239
|
end
|
|
228
240
|
|
|
229
241
|
test "invalid" do
|
|
@@ -263,12 +275,171 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
263
275
|
end
|
|
264
276
|
end
|
|
265
277
|
|
|
278
|
+
sub_test_case "configure group" do
|
|
279
|
+
test "<rule> required" do
|
|
280
|
+
conf = create_group_directive('.', '1m') + SINGLE_LINE_CONFIG
|
|
281
|
+
assert_raise(Fluent::ConfigError) do
|
|
282
|
+
create_driver(conf)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
test "valid configuration" do
|
|
287
|
+
rule1 = create_rule_directive({
|
|
288
|
+
"namespace"=> "/namespace-a/",
|
|
289
|
+
"podname"=> "/podname-[b|c]/"
|
|
290
|
+
}, 100)
|
|
291
|
+
rule2 = create_rule_directive({
|
|
292
|
+
"namespace"=> "/namespace-[d|e]/",
|
|
293
|
+
"podname"=> "/podname-f/",
|
|
294
|
+
}, 50)
|
|
295
|
+
rule3 = create_rule_directive({
|
|
296
|
+
"podname"=> "/podname-g/",
|
|
297
|
+
}, -1)
|
|
298
|
+
rule4 = create_rule_directive({
|
|
299
|
+
"namespace"=> "/namespace-h/",
|
|
300
|
+
}, 0)
|
|
301
|
+
|
|
302
|
+
conf = create_group_directive(tailing_group_pattern, '1m', rule1, rule2, rule3, rule4) + SINGLE_LINE_CONFIG
|
|
303
|
+
assert_nothing_raised do
|
|
304
|
+
create_driver(conf)
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
test "limit should be greater than DEFAULT_LIMIT (-1)" do
|
|
309
|
+
rule1 = create_rule_directive({
|
|
310
|
+
"namespace"=> "/namespace-a/",
|
|
311
|
+
"podname"=> "/podname-[b|c]/",
|
|
312
|
+
}, -100)
|
|
313
|
+
rule2 = create_rule_directive({
|
|
314
|
+
"namespace"=> "/namespace-[d|e]/",
|
|
315
|
+
"podname"=> "/podname-f/",
|
|
316
|
+
}, 50)
|
|
317
|
+
conf = create_group_directive(tailing_group_pattern, '1m', rule1, rule2) + SINGLE_LINE_CONFIG
|
|
318
|
+
assert_raise(RuntimeError) do
|
|
319
|
+
create_driver(conf)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
sub_test_case "group rules line limit resolution" do
|
|
325
|
+
test "valid" do
|
|
326
|
+
rule1 = create_rule_directive({
|
|
327
|
+
"namespace"=> "/namespace-a/",
|
|
328
|
+
"podname"=> "/podname-[b|c]/",
|
|
329
|
+
}, 50)
|
|
330
|
+
rule2 = create_rule_directive({
|
|
331
|
+
"podname"=> "/podname-[b|c]/",
|
|
332
|
+
}, 400)
|
|
333
|
+
rule3 = create_rule_directive({
|
|
334
|
+
"namespace"=> "/namespace-a/",
|
|
335
|
+
}, 100)
|
|
336
|
+
|
|
337
|
+
conf = create_group_directive(tailing_group_pattern, '1m', rule3, rule1, rule2) + SINGLE_LINE_CONFIG
|
|
338
|
+
assert_nothing_raised do
|
|
339
|
+
d = create_driver(conf)
|
|
340
|
+
instance = d.instance
|
|
341
|
+
|
|
342
|
+
metadata = {
|
|
343
|
+
"namespace"=> "namespace-a",
|
|
344
|
+
"podname"=> "podname-b",
|
|
345
|
+
}
|
|
346
|
+
assert_equal(50, instance.find_group(metadata).limit)
|
|
347
|
+
|
|
348
|
+
metadata = {
|
|
349
|
+
"namespace" => "namespace-a",
|
|
350
|
+
"podname" => "podname-c",
|
|
351
|
+
}
|
|
352
|
+
assert_equal(50, instance.find_group(metadata).limit)
|
|
353
|
+
|
|
354
|
+
metadata = {
|
|
355
|
+
"namespace" => "namespace-a",
|
|
356
|
+
"podname" => "podname-d",
|
|
357
|
+
}
|
|
358
|
+
assert_equal(100, instance.find_group(metadata).limit)
|
|
359
|
+
|
|
360
|
+
metadata = {
|
|
361
|
+
"namespace" => "namespace-f",
|
|
362
|
+
"podname" => "podname-b",
|
|
363
|
+
}
|
|
364
|
+
assert_equal(400, instance.find_group(metadata).limit)
|
|
365
|
+
|
|
366
|
+
metadata = {
|
|
367
|
+
"podname" => "podname-c",
|
|
368
|
+
}
|
|
369
|
+
assert_equal(400, instance.find_group(metadata).limit)
|
|
370
|
+
|
|
371
|
+
assert_equal(-1, instance.find_group({}).limit)
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
sub_test_case "files should be placed in groups" do
|
|
377
|
+
test "invalid regex pattern places files in default group" do
|
|
378
|
+
rule1 = create_rule_directive({}, 100) ## limits default groups
|
|
379
|
+
conf = ROOT_CONFIG + DEBUG_LOG_LEVEL + create_group_directive(tailing_group_pattern, '1m', rule1) + create_path_element("test*.txt") + SINGLE_LINE_CONFIG
|
|
380
|
+
|
|
381
|
+
d = create_driver(conf, false)
|
|
382
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/test1.txt", 'w')
|
|
383
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/test2.txt", 'w')
|
|
384
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/test3.txt", 'w')
|
|
385
|
+
|
|
386
|
+
d.run do
|
|
387
|
+
## checking default group_watcher's paths
|
|
388
|
+
instance = d.instance
|
|
389
|
+
key = instance.default_group_key
|
|
390
|
+
|
|
391
|
+
assert_equal(3, instance.log.logs.count{|a| a.match?("Cannot find group from metadata, Adding file in the default group\n")})
|
|
392
|
+
assert_equal(3, instance.group_watchers[key].size)
|
|
393
|
+
assert_true(instance.group_watchers[key].include? File.join(@tmp_dir, 'test1.txt'))
|
|
394
|
+
assert_true(instance.group_watchers[key].include? File.join(@tmp_dir, 'test2.txt'))
|
|
395
|
+
assert_true(instance.group_watchers[key].include? File.join(@tmp_dir, 'test3.txt'))
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
test "valid regex pattern places file in their respective groups" do
|
|
400
|
+
rule1 = create_rule_directive({
|
|
401
|
+
"namespace"=> "/test-namespace1/",
|
|
402
|
+
"podname"=> "/test-podname1/",
|
|
403
|
+
}, 100)
|
|
404
|
+
rule2 = create_rule_directive({
|
|
405
|
+
"namespace"=> "/test-namespace1/",
|
|
406
|
+
}, 200)
|
|
407
|
+
rule3 = create_rule_directive({
|
|
408
|
+
"podname"=> "/test-podname2/",
|
|
409
|
+
}, 300)
|
|
410
|
+
rule4 = create_rule_directive({}, 400)
|
|
411
|
+
|
|
412
|
+
path_element = create_path_element("test-podname*.log")
|
|
413
|
+
|
|
414
|
+
conf = ROOT_CONFIG + create_group_directive(tailing_group_pattern, '1m', rule4, rule3, rule2, rule1) + path_element + SINGLE_LINE_CONFIG
|
|
415
|
+
d = create_driver(conf, false)
|
|
416
|
+
|
|
417
|
+
file1 = File.join(@tmp_dir, "test-podname1_test-namespace1_test-container-15fabq.log")
|
|
418
|
+
file2 = File.join(@tmp_dir, "test-podname3_test-namespace1_test-container-15fabq.log")
|
|
419
|
+
file3 = File.join(@tmp_dir, "test-podname2_test-namespace2_test-container-15fabq.log")
|
|
420
|
+
file4 = File.join(@tmp_dir, "test-podname4_test-namespace3_test-container-15fabq.log")
|
|
421
|
+
|
|
422
|
+
d.run do
|
|
423
|
+
Fluent::FileWrapper.open(file1, 'w')
|
|
424
|
+
Fluent::FileWrapper.open(file2, 'w')
|
|
425
|
+
Fluent::FileWrapper.open(file3, 'w')
|
|
426
|
+
Fluent::FileWrapper.open(file4, 'w')
|
|
427
|
+
|
|
428
|
+
instance = d.instance
|
|
429
|
+
assert_equal(100, instance.find_group_from_metadata(file1).limit)
|
|
430
|
+
assert_equal(200, instance.find_group_from_metadata(file2).limit)
|
|
431
|
+
assert_equal(300, instance.find_group_from_metadata(file3).limit)
|
|
432
|
+
assert_equal(400, instance.find_group_from_metadata(file4).limit)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
266
437
|
sub_test_case "singleline" do
|
|
267
438
|
data(flat: SINGLE_LINE_CONFIG,
|
|
268
439
|
parse: PARSE_SINGLE_LINE_CONFIG)
|
|
269
440
|
def test_emit(data)
|
|
270
441
|
config = data
|
|
271
|
-
|
|
442
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
272
443
|
f.puts "test1"
|
|
273
444
|
f.puts "test2"
|
|
274
445
|
}
|
|
@@ -276,7 +447,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
276
447
|
d = create_driver(config)
|
|
277
448
|
|
|
278
449
|
d.run(expect_emits: 1) do
|
|
279
|
-
|
|
450
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
280
451
|
f.puts "test3\ntest4"
|
|
281
452
|
}
|
|
282
453
|
end
|
|
@@ -292,11 +463,11 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
292
463
|
|
|
293
464
|
def test_emit_with_emit_unmatched_lines_true
|
|
294
465
|
config = config_element("", "", { "format" => "/^(?<message>test.*)/", "emit_unmatched_lines" => true })
|
|
295
|
-
|
|
466
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
296
467
|
|
|
297
468
|
d = create_driver(config)
|
|
298
469
|
d.run(expect_emits: 1) do
|
|
299
|
-
|
|
470
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
300
471
|
f.puts "test line 1"
|
|
301
472
|
f.puts "test line 2"
|
|
302
473
|
f.puts "bad line 1"
|
|
@@ -328,7 +499,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
328
499
|
msg = 'test' * 2000 # in_tail reads 8192 bytes at once.
|
|
329
500
|
|
|
330
501
|
d.run(expect_emits: num_events, timeout: 2) do
|
|
331
|
-
|
|
502
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
332
503
|
f.puts msg
|
|
333
504
|
f.puts msg
|
|
334
505
|
}
|
|
@@ -343,7 +514,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
343
514
|
|
|
344
515
|
sub_test_case "log throttling per file" do
|
|
345
516
|
teardown do
|
|
346
|
-
cleanup_file("#{
|
|
517
|
+
cleanup_file("#{@tmp_dir}/tail.txt")
|
|
347
518
|
end
|
|
348
519
|
|
|
349
520
|
sub_test_case "reads_bytes_per_second w/o throttled" do
|
|
@@ -374,7 +545,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
374
545
|
|
|
375
546
|
d = create_driver(config)
|
|
376
547
|
d.run(expect_emits: 2) do
|
|
377
|
-
|
|
548
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
378
549
|
100.times do
|
|
379
550
|
f.puts msg
|
|
380
551
|
end
|
|
@@ -397,7 +568,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
397
568
|
start_time = Fluent::Clock.now
|
|
398
569
|
d = create_driver(config)
|
|
399
570
|
d.run(expect_emits: 2) do
|
|
400
|
-
|
|
571
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
401
572
|
8000.times do
|
|
402
573
|
f.puts msg
|
|
403
574
|
end
|
|
@@ -436,7 +607,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
436
607
|
io_handler
|
|
437
608
|
end
|
|
438
609
|
|
|
439
|
-
|
|
610
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") do |f|
|
|
440
611
|
100.times do
|
|
441
612
|
f.puts msg
|
|
442
613
|
end
|
|
@@ -446,7 +617,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
446
617
|
d.run do
|
|
447
618
|
start_time = Fluent::Clock.now
|
|
448
619
|
while Fluent::Clock.now - start_time < 0.8 do
|
|
449
|
-
|
|
620
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") do |f|
|
|
450
621
|
f.puts msg
|
|
451
622
|
f.flush
|
|
452
623
|
end
|
|
@@ -464,7 +635,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
464
635
|
num_lines = 1024 * 3
|
|
465
636
|
msg = "08bytes"
|
|
466
637
|
|
|
467
|
-
|
|
638
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") do |f|
|
|
468
639
|
f.write("#{msg}\n" * num_lines)
|
|
469
640
|
end
|
|
470
641
|
|
|
@@ -481,8 +652,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
481
652
|
d.run(timeout: 10) do
|
|
482
653
|
while d.events.size < num_lines do
|
|
483
654
|
if d.events.size > 0 && !rotated
|
|
484
|
-
cleanup_file("#{
|
|
485
|
-
FileUtils.touch("#{
|
|
655
|
+
cleanup_file("#{@tmp_dir}/tail.txt")
|
|
656
|
+
FileUtils.touch("#{@tmp_dir}/tail.txt")
|
|
486
657
|
rotated = true
|
|
487
658
|
end
|
|
488
659
|
sleep 0.3
|
|
@@ -500,7 +671,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
500
671
|
num_lines = 1024 * 2
|
|
501
672
|
msg = "08bytes"
|
|
502
673
|
|
|
503
|
-
|
|
674
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") do |f|
|
|
504
675
|
f.write("#{msg}\n" * num_lines)
|
|
505
676
|
end
|
|
506
677
|
|
|
@@ -527,8 +698,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
527
698
|
d.run(timeout: 10) do
|
|
528
699
|
until detached do
|
|
529
700
|
if d.events.size > 0 && !rotated
|
|
530
|
-
cleanup_file("#{
|
|
531
|
-
FileUtils.touch("#{
|
|
701
|
+
cleanup_file("#{@tmp_dir}/tail.txt")
|
|
702
|
+
FileUtils.touch("#{@tmp_dir}/tail.txt")
|
|
532
703
|
rotated = true
|
|
533
704
|
end
|
|
534
705
|
sleep 0.3
|
|
@@ -548,7 +719,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
548
719
|
parse: CONFIG_READ_FROM_HEAD + PARSE_SINGLE_LINE_CONFIG)
|
|
549
720
|
def test_emit_with_read_from_head(data)
|
|
550
721
|
config = data
|
|
551
|
-
|
|
722
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
552
723
|
f.puts "test1"
|
|
553
724
|
f.puts "test2"
|
|
554
725
|
}
|
|
@@ -556,7 +727,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
556
727
|
d = create_driver(config)
|
|
557
728
|
|
|
558
729
|
d.run(expect_emits: 2) do
|
|
559
|
-
|
|
730
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
560
731
|
f.puts "test3"
|
|
561
732
|
f.puts "test4"
|
|
562
733
|
}
|
|
@@ -570,11 +741,11 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
570
741
|
assert_equal({"message" => "test4"}, events[3][2])
|
|
571
742
|
end
|
|
572
743
|
|
|
573
|
-
data(flat:
|
|
574
|
-
parse:
|
|
575
|
-
def
|
|
744
|
+
data(flat: CONFIG_DISABLE_WATCH_TIMER + SINGLE_LINE_CONFIG,
|
|
745
|
+
parse: CONFIG_DISABLE_WATCH_TIMER + PARSE_SINGLE_LINE_CONFIG)
|
|
746
|
+
def test_emit_without_watch_timer(data)
|
|
576
747
|
config = data
|
|
577
|
-
|
|
748
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
578
749
|
f.puts "test1"
|
|
579
750
|
f.puts "test2"
|
|
580
751
|
}
|
|
@@ -582,7 +753,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
582
753
|
d = create_driver(config)
|
|
583
754
|
|
|
584
755
|
d.run(expect_emits: 1) do
|
|
585
|
-
|
|
756
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
586
757
|
f.puts "test3"
|
|
587
758
|
f.puts "test4"
|
|
588
759
|
}
|
|
@@ -596,11 +767,43 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
596
767
|
assert_equal({"message" => "test4"}, events[1][2])
|
|
597
768
|
end
|
|
598
769
|
|
|
770
|
+
# https://github.com/fluent/fluentd/pull/3541#discussion_r740197711
|
|
771
|
+
def test_watch_wildcard_path_without_watch_timer
|
|
772
|
+
omit "need inotify" unless Fluent.linux?
|
|
773
|
+
|
|
774
|
+
config = config_element("ROOT", "", {
|
|
775
|
+
"path" => "#{@tmp_dir}/tail*.txt",
|
|
776
|
+
"tag" => "t1",
|
|
777
|
+
})
|
|
778
|
+
config = config + CONFIG_DISABLE_WATCH_TIMER + SINGLE_LINE_CONFIG
|
|
779
|
+
|
|
780
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
781
|
+
f.puts "test1"
|
|
782
|
+
f.puts "test2"
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
d = create_driver(config, false)
|
|
786
|
+
|
|
787
|
+
d.run(expect_emits: 1, timeout: 1) do
|
|
788
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
789
|
+
f.puts "test3"
|
|
790
|
+
f.puts "test4"
|
|
791
|
+
}
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
assert_equal(
|
|
795
|
+
[
|
|
796
|
+
{"message" => "test3"},
|
|
797
|
+
{"message" => "test4"},
|
|
798
|
+
],
|
|
799
|
+
d.events.collect { |event| event[2] })
|
|
800
|
+
end
|
|
801
|
+
|
|
599
802
|
data(flat: CONFIG_DISABLE_STAT_WATCHER + SINGLE_LINE_CONFIG,
|
|
600
803
|
parse: CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
|
|
601
804
|
def test_emit_with_disable_stat_watcher(data)
|
|
602
805
|
config = data
|
|
603
|
-
|
|
806
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
604
807
|
f.puts "test1"
|
|
605
808
|
f.puts "test2"
|
|
606
809
|
}
|
|
@@ -608,7 +811,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
608
811
|
d = create_driver(config)
|
|
609
812
|
|
|
610
813
|
d.run(expect_emits: 1) do
|
|
611
|
-
|
|
814
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
612
815
|
f.puts "test3"
|
|
613
816
|
f.puts "test4"
|
|
614
817
|
}
|
|
@@ -619,6 +822,23 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
619
822
|
assert_equal({"message" => "test3"}, events[0][2])
|
|
620
823
|
assert_equal({"message" => "test4"}, events[1][2])
|
|
621
824
|
end
|
|
825
|
+
|
|
826
|
+
def test_always_read_from_head_on_detecting_a_new_file
|
|
827
|
+
d = create_driver(SINGLE_LINE_CONFIG)
|
|
828
|
+
|
|
829
|
+
d.run(expect_emits: 1, timeout: 3) do
|
|
830
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
831
|
+
f.puts "test1\ntest2\n"
|
|
832
|
+
}
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
assert_equal(
|
|
836
|
+
[
|
|
837
|
+
{"message" => "test1"},
|
|
838
|
+
{"message" => "test2"},
|
|
839
|
+
],
|
|
840
|
+
d.events.collect { |event| event[2] })
|
|
841
|
+
end
|
|
622
842
|
end
|
|
623
843
|
|
|
624
844
|
class TestWithSystem < self
|
|
@@ -633,11 +853,14 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
633
853
|
|
|
634
854
|
def setup
|
|
635
855
|
omit "NTFS doesn't support UNIX like permissions" if Fluent.windows?
|
|
856
|
+
super
|
|
636
857
|
# Store default permission
|
|
637
858
|
@default_permission = system_config.instance_variable_get(:@file_permission)
|
|
638
859
|
end
|
|
639
860
|
|
|
640
861
|
def teardown
|
|
862
|
+
return if Fluent.windows?
|
|
863
|
+
super
|
|
641
864
|
# Restore default permission
|
|
642
865
|
system_config.instance_variable_set(:@file_permission, @default_permission)
|
|
643
866
|
end
|
|
@@ -651,7 +874,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
651
874
|
system_conf = parse_system(CONFIG_SYSTEM)
|
|
652
875
|
sc = Fluent::SystemConfig.new(system_conf)
|
|
653
876
|
Fluent::Engine.init(sc)
|
|
654
|
-
|
|
877
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
655
878
|
f.puts "test1"
|
|
656
879
|
f.puts "test2"
|
|
657
880
|
}
|
|
@@ -659,7 +882,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
659
882
|
d = create_driver
|
|
660
883
|
|
|
661
884
|
d.run(expect_emits: 1) do
|
|
662
|
-
|
|
885
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
663
886
|
f.puts "test3"
|
|
664
887
|
f.puts "test4"
|
|
665
888
|
}
|
|
@@ -679,16 +902,18 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
679
902
|
end
|
|
680
903
|
|
|
681
904
|
sub_test_case "rotate file" do
|
|
905
|
+
def create_driver(conf = SINGLE_LINE_CONFIG)
|
|
906
|
+
config = common_config + conf
|
|
907
|
+
Fluent::Test::Driver::Input.new(Fluent::Plugin::TailInput).configure(config)
|
|
908
|
+
end
|
|
909
|
+
|
|
682
910
|
data(flat: SINGLE_LINE_CONFIG,
|
|
683
911
|
parse: PARSE_SINGLE_LINE_CONFIG)
|
|
684
912
|
def test_rotate_file(data)
|
|
685
913
|
config = data
|
|
686
914
|
events = sub_test_rotate_file(config, expect_emits: 2)
|
|
687
|
-
assert_equal(
|
|
688
|
-
|
|
689
|
-
assert_equal({"message" => "test4"}, events[1][2])
|
|
690
|
-
assert_equal({"message" => "test5"}, events[2][2])
|
|
691
|
-
assert_equal({"message" => "test6"}, events[3][2])
|
|
915
|
+
assert_equal(3.upto(6).collect { |i| {"message" => "test#{i}"} },
|
|
916
|
+
events.collect { |event| event[2] })
|
|
692
917
|
end
|
|
693
918
|
|
|
694
919
|
data(flat: CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG,
|
|
@@ -696,13 +921,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
696
921
|
def test_rotate_file_with_read_from_head(data)
|
|
697
922
|
config = data
|
|
698
923
|
events = sub_test_rotate_file(config, expect_records: 6)
|
|
699
|
-
assert_equal(6,
|
|
700
|
-
|
|
701
|
-
assert_equal({"message" => "test2"}, events[1][2])
|
|
702
|
-
assert_equal({"message" => "test3"}, events[2][2])
|
|
703
|
-
assert_equal({"message" => "test4"}, events[3][2])
|
|
704
|
-
assert_equal({"message" => "test5"}, events[4][2])
|
|
705
|
-
assert_equal({"message" => "test6"}, events[5][2])
|
|
924
|
+
assert_equal(1.upto(6).collect { |i| {"message" => "test#{i}"} },
|
|
925
|
+
events.collect { |event| event[2] })
|
|
706
926
|
end
|
|
707
927
|
|
|
708
928
|
data(flat: CONFIG_OPEN_ON_EVERY_UPDATE + CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG,
|
|
@@ -710,13 +930,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
710
930
|
def test_rotate_file_with_open_on_every_update(data)
|
|
711
931
|
config = data
|
|
712
932
|
events = sub_test_rotate_file(config, expect_records: 6)
|
|
713
|
-
assert_equal(6,
|
|
714
|
-
|
|
715
|
-
assert_equal({"message" => "test2"}, events[1][2])
|
|
716
|
-
assert_equal({"message" => "test3"}, events[2][2])
|
|
717
|
-
assert_equal({"message" => "test4"}, events[3][2])
|
|
718
|
-
assert_equal({"message" => "test5"}, events[4][2])
|
|
719
|
-
assert_equal({"message" => "test6"}, events[5][2])
|
|
933
|
+
assert_equal(1.upto(6).collect { |i| {"message" => "test#{i}"} },
|
|
934
|
+
events.collect { |event| event[2] })
|
|
720
935
|
end
|
|
721
936
|
|
|
722
937
|
data(flat: SINGLE_LINE_CONFIG,
|
|
@@ -724,26 +939,21 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
724
939
|
def test_rotate_file_with_write_old(data)
|
|
725
940
|
config = data
|
|
726
941
|
events = sub_test_rotate_file(config, expect_emits: 3) { |rotated_file|
|
|
727
|
-
|
|
942
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
728
943
|
rotated_file.puts "test7"
|
|
729
944
|
rotated_file.puts "test8"
|
|
730
945
|
rotated_file.flush
|
|
731
946
|
|
|
732
947
|
sleep 1
|
|
733
|
-
|
|
948
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
734
949
|
f.puts "test5"
|
|
735
950
|
f.puts "test6"
|
|
736
951
|
}
|
|
737
952
|
}
|
|
738
953
|
# This test sometimes fails and it shows a potential bug of in_tail
|
|
739
954
|
# https://github.com/fluent/fluentd/issues/1434
|
|
740
|
-
assert_equal(
|
|
741
|
-
|
|
742
|
-
assert_equal({"message" => "test4"}, events[1][2])
|
|
743
|
-
assert_equal({"message" => "test7"}, events[2][2])
|
|
744
|
-
assert_equal({"message" => "test8"}, events[3][2])
|
|
745
|
-
assert_equal({"message" => "test5"}, events[4][2])
|
|
746
|
-
assert_equal({"message" => "test6"}, events[5][2])
|
|
955
|
+
assert_equal([3, 4, 7, 8, 5, 6].collect { |i| {"message" => "test#{i}"} },
|
|
956
|
+
events.collect { |event| event[2] })
|
|
747
957
|
end
|
|
748
958
|
|
|
749
959
|
data(flat: SINGLE_LINE_CONFIG,
|
|
@@ -755,21 +965,19 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
755
965
|
rotated_file.puts "test8"
|
|
756
966
|
rotated_file.flush
|
|
757
967
|
}
|
|
758
|
-
assert_equal(4,
|
|
759
|
-
|
|
760
|
-
assert_equal({"message" => "test4"}, events[1][2])
|
|
761
|
-
assert_equal({"message" => "test7"}, events[2][2])
|
|
762
|
-
assert_equal({"message" => "test8"}, events[3][2])
|
|
968
|
+
assert_equal([3, 4, 7, 8].collect { |i| {"message" => "test#{i}"} },
|
|
969
|
+
events.collect { |event| event[2] })
|
|
763
970
|
end
|
|
764
971
|
|
|
765
|
-
def sub_test_rotate_file(config = nil, expect_emits: nil, expect_records: nil, timeout:
|
|
766
|
-
file = Fluent::FileWrapper.open("#{
|
|
972
|
+
def sub_test_rotate_file(config = nil, expect_emits: nil, expect_records: nil, timeout: 5)
|
|
973
|
+
file = Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb")
|
|
767
974
|
file.puts "test1"
|
|
768
975
|
file.puts "test2"
|
|
769
976
|
file.flush
|
|
770
977
|
|
|
771
978
|
d = create_driver(config)
|
|
772
979
|
d.run(expect_emits: expect_emits, expect_records: expect_records, timeout: timeout) do
|
|
980
|
+
sleep(0.1) while d.instance.instance_variable_get(:@startup)
|
|
773
981
|
size = d.emit_count
|
|
774
982
|
file.puts "test3"
|
|
775
983
|
file.puts "test4"
|
|
@@ -779,18 +987,18 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
779
987
|
|
|
780
988
|
if Fluent.windows?
|
|
781
989
|
file.close
|
|
782
|
-
FileUtils.mv("#{
|
|
783
|
-
file =
|
|
990
|
+
FileUtils.mv("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail2.txt", force: true)
|
|
991
|
+
file = Fluent::FileWrapper.open("#{@tmp_dir}/tail2.txt", "ab")
|
|
784
992
|
else
|
|
785
|
-
FileUtils.mv("#{
|
|
993
|
+
FileUtils.mv("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail2.txt")
|
|
786
994
|
end
|
|
787
995
|
if block_given?
|
|
788
996
|
yield file
|
|
789
997
|
else
|
|
790
|
-
|
|
998
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
791
999
|
sleep 1
|
|
792
1000
|
|
|
793
|
-
|
|
1001
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
794
1002
|
f.puts "test5"
|
|
795
1003
|
f.puts "test6"
|
|
796
1004
|
}
|
|
@@ -804,10 +1012,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
804
1012
|
end
|
|
805
1013
|
|
|
806
1014
|
def test_truncate_file
|
|
807
|
-
omit "Permission denied error happen on Windows. Need fix" if Fluent.windows?
|
|
808
|
-
|
|
809
1015
|
config = SINGLE_LINE_CONFIG
|
|
810
|
-
|
|
1016
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
811
1017
|
f.puts "test1"
|
|
812
1018
|
f.puts "test2"
|
|
813
1019
|
f.flush
|
|
@@ -816,12 +1022,18 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
816
1022
|
d = create_driver(config)
|
|
817
1023
|
|
|
818
1024
|
d.run(expect_emits: 2) do
|
|
819
|
-
|
|
1025
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
820
1026
|
f.puts "test3\ntest4"
|
|
821
1027
|
f.flush
|
|
822
1028
|
}
|
|
823
1029
|
waiting(2) { sleep 0.1 until d.events.length == 2 }
|
|
824
|
-
|
|
1030
|
+
if Fluent.windows?
|
|
1031
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f|
|
|
1032
|
+
f.puts("test1");
|
|
1033
|
+
}
|
|
1034
|
+
else
|
|
1035
|
+
File.truncate("#{@tmp_dir}/tail.txt", 6)
|
|
1036
|
+
end
|
|
825
1037
|
end
|
|
826
1038
|
|
|
827
1039
|
expected = {
|
|
@@ -840,10 +1052,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
840
1052
|
end
|
|
841
1053
|
|
|
842
1054
|
def test_move_truncate_move_back
|
|
843
|
-
omit "Permission denied error happen on Windows. Need fix" if Fluent.windows?
|
|
844
|
-
|
|
845
1055
|
config = SINGLE_LINE_CONFIG
|
|
846
|
-
|
|
1056
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
847
1057
|
f.puts "test1"
|
|
848
1058
|
f.puts "test2"
|
|
849
1059
|
}
|
|
@@ -852,17 +1062,23 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
852
1062
|
|
|
853
1063
|
d.run(expect_emits: 1) do
|
|
854
1064
|
if Fluent.windows?
|
|
855
|
-
FileUtils.mv("#{
|
|
1065
|
+
FileUtils.mv("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail2.txt", force: true)
|
|
856
1066
|
else
|
|
857
|
-
FileUtils.mv("#{
|
|
1067
|
+
FileUtils.mv("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail2.txt")
|
|
858
1068
|
end
|
|
859
1069
|
sleep(1)
|
|
860
|
-
|
|
1070
|
+
if Fluent.windows?
|
|
1071
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail2.txt", "wb") { |f|
|
|
1072
|
+
f.puts("test1");
|
|
1073
|
+
}
|
|
1074
|
+
else
|
|
1075
|
+
File.truncate("#{@tmp_dir}/tail2.txt", 6)
|
|
1076
|
+
end
|
|
861
1077
|
sleep(1)
|
|
862
1078
|
if Fluent.windows?
|
|
863
|
-
FileUtils.mv("#{
|
|
1079
|
+
FileUtils.mv("#{@tmp_dir}/tail2.txt", "#{@tmp_dir}/tail.txt", force: true)
|
|
864
1080
|
else
|
|
865
|
-
FileUtils.mv("#{
|
|
1081
|
+
FileUtils.mv("#{@tmp_dir}/tail2.txt", "#{@tmp_dir}/tail.txt")
|
|
866
1082
|
end
|
|
867
1083
|
end
|
|
868
1084
|
|
|
@@ -874,17 +1090,17 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
874
1090
|
end
|
|
875
1091
|
|
|
876
1092
|
def test_lf
|
|
877
|
-
|
|
1093
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| }
|
|
878
1094
|
|
|
879
1095
|
d = create_driver
|
|
880
1096
|
|
|
881
1097
|
d.run(expect_emits: 1) do
|
|
882
|
-
|
|
1098
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
883
1099
|
f.print "test3"
|
|
884
1100
|
}
|
|
885
1101
|
sleep 1
|
|
886
1102
|
|
|
887
|
-
|
|
1103
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
888
1104
|
f.puts "test4"
|
|
889
1105
|
}
|
|
890
1106
|
end
|
|
@@ -895,12 +1111,12 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
895
1111
|
end
|
|
896
1112
|
|
|
897
1113
|
def test_whitespace
|
|
898
|
-
|
|
1114
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| }
|
|
899
1115
|
|
|
900
1116
|
d = create_driver
|
|
901
1117
|
|
|
902
1118
|
d.run(expect_emits: 1) do
|
|
903
|
-
|
|
1119
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
904
1120
|
f.puts " " # 4 spaces
|
|
905
1121
|
f.puts " 4 spaces"
|
|
906
1122
|
f.puts "4 spaces "
|
|
@@ -931,7 +1147,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
931
1147
|
d = create_driver(CONFIG_READ_FROM_HEAD + encoding_config)
|
|
932
1148
|
|
|
933
1149
|
d.run(expect_emits: 1) do
|
|
934
|
-
|
|
1150
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
935
1151
|
f.puts "test"
|
|
936
1152
|
}
|
|
937
1153
|
end
|
|
@@ -953,7 +1169,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
953
1169
|
utf8_message = cp932_message.encode(Encoding::UTF_8)
|
|
954
1170
|
|
|
955
1171
|
d.run(expect_emits: 1) do
|
|
956
|
-
|
|
1172
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w:cp932") {|f|
|
|
957
1173
|
f.puts cp932_message
|
|
958
1174
|
}
|
|
959
1175
|
end
|
|
@@ -976,7 +1192,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
976
1192
|
utf8_message = utf16_message.encode(Encoding::UTF_8).strip
|
|
977
1193
|
|
|
978
1194
|
d.run(expect_emits: 1) do
|
|
979
|
-
|
|
1195
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w:utf-16le") { |f|
|
|
980
1196
|
f.write utf16_message
|
|
981
1197
|
}
|
|
982
1198
|
end
|
|
@@ -997,7 +1213,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
997
1213
|
d = create_driver(conf)
|
|
998
1214
|
|
|
999
1215
|
d.run(expect_emits: 1) do
|
|
1000
|
-
|
|
1216
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w") { |f|
|
|
1001
1217
|
f.write "te\x86st\n"
|
|
1002
1218
|
}
|
|
1003
1219
|
end
|
|
@@ -1012,11 +1228,11 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1012
1228
|
parse: PARSE_MULTILINE_CONFIG)
|
|
1013
1229
|
def test_multiline(data)
|
|
1014
1230
|
config = data
|
|
1015
|
-
|
|
1231
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
1016
1232
|
|
|
1017
1233
|
d = create_driver(config)
|
|
1018
1234
|
d.run(expect_emits: 1) do
|
|
1019
|
-
|
|
1235
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
1020
1236
|
f.puts "f test1"
|
|
1021
1237
|
f.puts "s test2"
|
|
1022
1238
|
f.puts "f test3"
|
|
@@ -1040,11 +1256,11 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1040
1256
|
parse: PARSE_MULTILINE_CONFIG)
|
|
1041
1257
|
def test_multiline_with_emit_unmatched_lines_true(data)
|
|
1042
1258
|
config = data + config_element("", "", { "emit_unmatched_lines" => true })
|
|
1043
|
-
|
|
1259
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
1044
1260
|
|
|
1045
1261
|
d = create_driver(config)
|
|
1046
1262
|
d.run(expect_emits: 1) do
|
|
1047
|
-
|
|
1263
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
1048
1264
|
f.puts "f test1"
|
|
1049
1265
|
f.puts "s test2"
|
|
1050
1266
|
f.puts "f test3"
|
|
@@ -1070,11 +1286,11 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1070
1286
|
parse: PARSE_MULTILINE_CONFIG_WITH_NEWLINE)
|
|
1071
1287
|
def test_multiline_with_emit_unmatched_lines2(data)
|
|
1072
1288
|
config = data + config_element("", "", { "emit_unmatched_lines" => true })
|
|
1073
|
-
|
|
1289
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
1074
1290
|
|
|
1075
1291
|
d = create_driver(config)
|
|
1076
1292
|
d.run(expect_emits: 0, timeout: 1) do
|
|
1077
|
-
|
|
1293
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
1078
1294
|
f.puts "s test0"
|
|
1079
1295
|
f.puts "f test1"
|
|
1080
1296
|
f.puts "f test2"
|
|
@@ -1096,15 +1312,15 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1096
1312
|
data(flat: MULTILINE_CONFIG,
|
|
1097
1313
|
parse: PARSE_MULTILINE_CONFIG)
|
|
1098
1314
|
def test_multiline_with_flush_interval(data)
|
|
1099
|
-
|
|
1315
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
1100
1316
|
|
|
1101
1317
|
config = data + config_element("", "", { "multiline_flush_interval" => "2s" })
|
|
1102
1318
|
d = create_driver(config)
|
|
1103
1319
|
|
|
1104
|
-
assert_equal
|
|
1320
|
+
assert_equal(2, d.instance.multiline_flush_interval)
|
|
1105
1321
|
|
|
1106
1322
|
d.run(expect_emits: 1) do
|
|
1107
|
-
|
|
1323
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
1108
1324
|
f.puts "f test1"
|
|
1109
1325
|
f.puts "s test2"
|
|
1110
1326
|
f.puts "f test3"
|
|
@@ -1139,7 +1355,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1139
1355
|
d = create_driver(config + encoding_config)
|
|
1140
1356
|
|
|
1141
1357
|
d.run(expect_emits: 1) do
|
|
1142
|
-
|
|
1358
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f|
|
|
1143
1359
|
f.puts "s test"
|
|
1144
1360
|
}
|
|
1145
1361
|
end
|
|
@@ -1162,7 +1378,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1162
1378
|
cp932_message = "s \x82\xCD\x82\xEB\x81\x5B\x82\xED\x81\x5B\x82\xE9\x82\xC7".force_encoding(Encoding::CP932)
|
|
1163
1379
|
utf8_message = "\x82\xCD\x82\xEB\x81\x5B\x82\xED\x81\x5B\x82\xE9\x82\xC7".encode(Encoding::UTF_8, Encoding::CP932)
|
|
1164
1380
|
d.run(expect_emits: 1) do
|
|
1165
|
-
|
|
1381
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w:cp932") { |f|
|
|
1166
1382
|
f.puts cp932_message
|
|
1167
1383
|
}
|
|
1168
1384
|
end
|
|
@@ -1194,11 +1410,11 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1194
1410
|
)
|
|
1195
1411
|
def test_multiline_with_multiple_formats(data)
|
|
1196
1412
|
config = data
|
|
1197
|
-
|
|
1413
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
1198
1414
|
|
|
1199
1415
|
d = create_driver(config)
|
|
1200
1416
|
d.run(expect_emits: 1) do
|
|
1201
|
-
|
|
1417
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
1202
1418
|
f.puts "f test1"
|
|
1203
1419
|
f.puts "s test2"
|
|
1204
1420
|
f.puts "f test3"
|
|
@@ -1234,8 +1450,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1234
1450
|
])
|
|
1235
1451
|
)
|
|
1236
1452
|
def test_multilinelog_with_multiple_paths(data)
|
|
1237
|
-
files = ["#{
|
|
1238
|
-
files.each { |file|
|
|
1453
|
+
files = ["#{@tmp_dir}/tail1.txt", "#{@tmp_dir}/tail2.txt"]
|
|
1454
|
+
files.each { |file| Fluent::FileWrapper.open(file, "wb") { |f| } }
|
|
1239
1455
|
|
|
1240
1456
|
config = data + config_element("", "", {
|
|
1241
1457
|
"path" => "#{files[0]},#{files[1]}",
|
|
@@ -1244,7 +1460,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1244
1460
|
d = create_driver(config, false)
|
|
1245
1461
|
d.run(expect_emits: 2) do
|
|
1246
1462
|
files.each do |file|
|
|
1247
|
-
|
|
1463
|
+
Fluent::FileWrapper.open(file, 'ab') { |f|
|
|
1248
1464
|
f.puts "f #{file} line should be ignored"
|
|
1249
1465
|
f.puts "s test1"
|
|
1250
1466
|
f.puts "f test2"
|
|
@@ -1279,12 +1495,12 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1279
1495
|
])
|
|
1280
1496
|
)
|
|
1281
1497
|
def test_multiline_without_firstline(data)
|
|
1282
|
-
|
|
1498
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
1283
1499
|
|
|
1284
1500
|
config = data
|
|
1285
1501
|
d = create_driver(config)
|
|
1286
1502
|
d.run(expect_emits: 1) do
|
|
1287
|
-
|
|
1503
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
1288
1504
|
f.puts "foo 1"
|
|
1289
1505
|
f.puts "bar 1"
|
|
1290
1506
|
f.puts "baz 1"
|
|
@@ -1304,35 +1520,22 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1304
1520
|
sub_test_case "path" do
|
|
1305
1521
|
# * path test
|
|
1306
1522
|
# TODO: Clean up tests
|
|
1307
|
-
EX_ROTATE_WAIT = 0
|
|
1308
|
-
EX_FOLLOW_INODES = false
|
|
1309
|
-
|
|
1310
|
-
EX_CONFIG = config_element("", "", {
|
|
1311
|
-
"tag" => "tail",
|
|
1312
|
-
"path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
|
|
1313
|
-
"format" => "none",
|
|
1314
|
-
"pos_file" => "#{TMP_DIR}/tail.pos",
|
|
1315
|
-
"read_from_head" => true,
|
|
1316
|
-
"refresh_interval" => 30,
|
|
1317
|
-
"rotate_wait" => "#{EX_ROTATE_WAIT}s",
|
|
1318
|
-
"follow_inodes" => "#{EX_FOLLOW_INODES}",
|
|
1319
|
-
})
|
|
1320
1523
|
def test_expand_paths
|
|
1321
1524
|
ex_paths = [
|
|
1322
1525
|
create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
|
|
1323
1526
|
create_target_info('test/plugin/data/log/foo/bar.log'),
|
|
1324
1527
|
create_target_info('test/plugin/data/log/test.log')
|
|
1325
1528
|
]
|
|
1326
|
-
plugin = create_driver(
|
|
1529
|
+
plugin = create_driver(ex_config, false).instance
|
|
1327
1530
|
flexstub(Time) do |timeclass|
|
|
1328
1531
|
timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 2, 3, 4, 5))
|
|
1329
|
-
assert_equal
|
|
1532
|
+
assert_equal(ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
|
|
1330
1533
|
end
|
|
1331
1534
|
|
|
1332
1535
|
# Test exclusion
|
|
1333
|
-
exclude_config =
|
|
1536
|
+
exclude_config = ex_config + config_element("", "", { "exclude_path" => %Q(["#{ex_paths.last.path}"]) })
|
|
1334
1537
|
plugin = create_driver(exclude_config, false).instance
|
|
1335
|
-
assert_equal
|
|
1538
|
+
assert_equal(ex_paths - [ex_paths.last], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
|
|
1336
1539
|
end
|
|
1337
1540
|
|
|
1338
1541
|
def test_expand_paths_with_duplicate_configuration
|
|
@@ -1340,10 +1543,10 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1340
1543
|
create_target_info('test/plugin/data/log/foo/bar.log'),
|
|
1341
1544
|
create_target_info('test/plugin/data/log/test.log')
|
|
1342
1545
|
]
|
|
1343
|
-
duplicate_config =
|
|
1546
|
+
duplicate_config = ex_config.dup
|
|
1344
1547
|
duplicate_config["path"]="test/plugin/data/log/**/*.log, test/plugin/data/log/**/*.log"
|
|
1345
|
-
plugin = create_driver(
|
|
1346
|
-
assert_equal
|
|
1548
|
+
plugin = create_driver(ex_config, false).instance
|
|
1549
|
+
assert_equal(expanded_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
|
|
1347
1550
|
end
|
|
1348
1551
|
|
|
1349
1552
|
def test_expand_paths_with_timezone
|
|
@@ -1353,7 +1556,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1353
1556
|
create_target_info('test/plugin/data/log/test.log')
|
|
1354
1557
|
]
|
|
1355
1558
|
['Asia/Taipei', '+08'].each do |tz_type|
|
|
1356
|
-
taipei_config =
|
|
1559
|
+
taipei_config = ex_config + config_element("", "", {"path_timezone" => tz_type})
|
|
1357
1560
|
plugin = create_driver(taipei_config, false).instance
|
|
1358
1561
|
|
|
1359
1562
|
# Test exclude
|
|
@@ -1365,8 +1568,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1365
1568
|
# env : 2010-01-01 19:04:05 (UTC), tail path : 2010-01-02 03:04:05 (Asia/Taipei)
|
|
1366
1569
|
timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 1, 19, 4, 5))
|
|
1367
1570
|
|
|
1368
|
-
assert_equal
|
|
1369
|
-
assert_equal
|
|
1571
|
+
assert_equal(ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
|
|
1572
|
+
assert_equal(ex_paths - [ex_paths.first], exclude_plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
|
|
1370
1573
|
end
|
|
1371
1574
|
end
|
|
1372
1575
|
end
|
|
@@ -1384,35 +1587,45 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1384
1587
|
"tag" => "tail",
|
|
1385
1588
|
"path" => "test/plugin/data/log/**/*",
|
|
1386
1589
|
"format" => "none",
|
|
1387
|
-
"pos_file" => "#{
|
|
1590
|
+
"pos_file" => "#{@tmp_dir}/tail.pos"
|
|
1388
1591
|
})
|
|
1389
1592
|
|
|
1390
1593
|
plugin = create_driver(config, false).instance
|
|
1391
|
-
assert_equal
|
|
1594
|
+
assert_equal(expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
|
|
1392
1595
|
end
|
|
1393
1596
|
|
|
1394
1597
|
def test_unwatched_files_should_be_removed
|
|
1395
1598
|
config = config_element("", "", {
|
|
1396
1599
|
"tag" => "tail",
|
|
1397
|
-
"path" => "#{
|
|
1600
|
+
"path" => "#{@tmp_dir}/*.txt",
|
|
1398
1601
|
"format" => "none",
|
|
1399
|
-
"pos_file" => "#{
|
|
1602
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
1400
1603
|
"read_from_head" => true,
|
|
1401
1604
|
"refresh_interval" => 1,
|
|
1402
1605
|
})
|
|
1403
1606
|
d = create_driver(config, false)
|
|
1404
1607
|
d.end_if { d.instance.instance_variable_get(:@tails).keys.size >= 1 }
|
|
1405
1608
|
d.run(expect_emits: 1, shutdown: false) do
|
|
1406
|
-
|
|
1609
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f| f.puts "test3\n" }
|
|
1407
1610
|
end
|
|
1408
1611
|
|
|
1409
|
-
|
|
1410
|
-
waiting(20) { sleep 0.1 until Dir.glob("#{
|
|
1612
|
+
cleanup_file("#{@tmp_dir}/tail.txt")
|
|
1613
|
+
waiting(20) { sleep 0.1 until Dir.glob("#{@tmp_dir}/*.txt").size == 0 } # Ensure file is deleted on Windows
|
|
1411
1614
|
waiting(5) { sleep 0.1 until d.instance.instance_variable_get(:@tails).keys.size <= 0 }
|
|
1412
1615
|
|
|
1413
|
-
assert_equal
|
|
1414
|
-
|
|
1415
|
-
|
|
1616
|
+
assert_equal(
|
|
1617
|
+
{
|
|
1618
|
+
files: [],
|
|
1619
|
+
tails: []
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
files: Dir.glob("#{@tmp_dir}/*.txt"),
|
|
1623
|
+
tails: d.instance.instance_variable_get(:@tails).keys
|
|
1624
|
+
}
|
|
1625
|
+
)
|
|
1626
|
+
ensure
|
|
1627
|
+
d.instance_shutdown if d && d.instance
|
|
1628
|
+
cleanup_directory(@tmp_dir)
|
|
1416
1629
|
end
|
|
1417
1630
|
|
|
1418
1631
|
def count_timer_object
|
|
@@ -1462,55 +1675,55 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1462
1675
|
"rotate_wait" => "2s"
|
|
1463
1676
|
}) + PARSE_SINGLE_LINE_CONFIG, false)
|
|
1464
1677
|
|
|
1465
|
-
assert_equal
|
|
1466
|
-
assert_equal
|
|
1678
|
+
assert_equal(readable_paths, d.instance.expand_paths.length)
|
|
1679
|
+
assert_equal(result, d.instance.have_read_capability?)
|
|
1467
1680
|
end
|
|
1468
1681
|
end
|
|
1469
1682
|
|
|
1470
1683
|
def test_pos_file_dir_creation
|
|
1471
1684
|
config = config_element("", "", {
|
|
1472
1685
|
"tag" => "tail",
|
|
1473
|
-
"path" => "#{
|
|
1686
|
+
"path" => "#{@tmp_dir}/*.txt",
|
|
1474
1687
|
"format" => "none",
|
|
1475
|
-
"pos_file" => "#{
|
|
1688
|
+
"pos_file" => "#{@tmp_dir}/pos/tail.pos",
|
|
1476
1689
|
"read_from_head" => true,
|
|
1477
1690
|
"refresh_interval" => 1
|
|
1478
1691
|
})
|
|
1479
1692
|
|
|
1480
|
-
assert_path_not_exist("#{
|
|
1693
|
+
assert_path_not_exist("#{@tmp_dir}/pos")
|
|
1481
1694
|
d = create_driver(config, false)
|
|
1482
1695
|
d.run
|
|
1483
|
-
assert_path_exist("#{
|
|
1484
|
-
assert_equal
|
|
1696
|
+
assert_path_exist("#{@tmp_dir}/pos")
|
|
1697
|
+
assert_equal('755', File.stat("#{@tmp_dir}/pos").mode.to_s(8)[-3, 3])
|
|
1485
1698
|
ensure
|
|
1486
|
-
cleanup_directory(
|
|
1699
|
+
cleanup_directory(@tmp_dir)
|
|
1487
1700
|
end
|
|
1488
1701
|
|
|
1489
1702
|
def test_pos_file_dir_creation_with_system_dir_permission
|
|
1490
1703
|
config = config_element("", "", {
|
|
1491
1704
|
"tag" => "tail",
|
|
1492
|
-
"path" => "#{
|
|
1705
|
+
"path" => "#{@tmp_dir}/*.txt",
|
|
1493
1706
|
"format" => "none",
|
|
1494
|
-
"pos_file" => "#{
|
|
1707
|
+
"pos_file" => "#{@tmp_dir}/pos/tail.pos",
|
|
1495
1708
|
"read_from_head" => true,
|
|
1496
1709
|
"refresh_interval" => 1
|
|
1497
1710
|
})
|
|
1498
1711
|
|
|
1499
|
-
assert_path_not_exist("#{
|
|
1712
|
+
assert_path_not_exist("#{@tmp_dir}/pos")
|
|
1500
1713
|
|
|
1501
1714
|
Fluent::SystemConfig.overwrite_system_config({ "dir_permission" => "744" }) do
|
|
1502
1715
|
d = create_driver(config, false)
|
|
1503
1716
|
d.run
|
|
1504
1717
|
end
|
|
1505
1718
|
|
|
1506
|
-
assert_path_exist("#{
|
|
1719
|
+
assert_path_exist("#{@tmp_dir}/pos")
|
|
1507
1720
|
if Fluent.windows?
|
|
1508
|
-
assert_equal
|
|
1721
|
+
assert_equal('755', File.stat("#{@tmp_dir}/pos").mode.to_s(8)[-3, 3])
|
|
1509
1722
|
else
|
|
1510
|
-
assert_equal
|
|
1723
|
+
assert_equal('744', File.stat("#{@tmp_dir}/pos").mode.to_s(8)[-3, 3])
|
|
1511
1724
|
end
|
|
1512
1725
|
ensure
|
|
1513
|
-
cleanup_directory(
|
|
1726
|
+
cleanup_directory(@tmp_dir)
|
|
1514
1727
|
end
|
|
1515
1728
|
|
|
1516
1729
|
def test_z_refresh_watchers
|
|
@@ -1519,30 +1732,36 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1519
1732
|
create_target_info('test/plugin/data/log/foo/bar.log'),
|
|
1520
1733
|
create_target_info('test/plugin/data/log/test.log'),
|
|
1521
1734
|
]
|
|
1522
|
-
plugin = create_driver(
|
|
1735
|
+
plugin = create_driver(ex_config, false).instance
|
|
1523
1736
|
sio = StringIO.new
|
|
1524
1737
|
plugin.instance_eval do
|
|
1525
1738
|
@pf = Fluent::Plugin::TailInput::PositionFile.load(sio, EX_FOLLOW_INODES, {}, logger: $log)
|
|
1526
1739
|
@loop = Coolio::Loop.new
|
|
1740
|
+
opened_file_metrics = Fluent::Plugin::LocalMetrics.new
|
|
1741
|
+
opened_file_metrics.configure(config_element('metrics', '', {}))
|
|
1742
|
+
closed_file_metrics = Fluent::Plugin::LocalMetrics.new
|
|
1743
|
+
closed_file_metrics.configure(config_element('metrics', '', {}))
|
|
1744
|
+
rotated_file_metrics = Fluent::Plugin::LocalMetrics.new
|
|
1745
|
+
rotated_file_metrics.configure(config_element('metrics', '', {}))
|
|
1746
|
+
@metrics = Fluent::Plugin::TailInput::MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
|
|
1527
1747
|
end
|
|
1528
1748
|
|
|
1529
1749
|
Timecop.freeze(2010, 1, 2, 3, 4, 5) do
|
|
1530
1750
|
ex_paths.each do |target_info|
|
|
1531
|
-
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
|
|
1751
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything, anything).once
|
|
1532
1752
|
end
|
|
1533
1753
|
|
|
1534
1754
|
plugin.refresh_watchers
|
|
1535
1755
|
end
|
|
1536
1756
|
|
|
1537
1757
|
path = 'test/plugin/data/2010/01/20100102-030405.log'
|
|
1538
|
-
|
|
1539
|
-
mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)[target_info], target_info.ino)
|
|
1758
|
+
mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)[path], Fluent::FileWrapper.stat(path).ino)
|
|
1540
1759
|
|
|
1541
1760
|
Timecop.freeze(2010, 1, 2, 3, 4, 6) do
|
|
1542
1761
|
path = "test/plugin/data/2010/01/20100102-030406.log"
|
|
1543
1762
|
inode = Fluent::FileWrapper.stat(path).ino
|
|
1544
1763
|
target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, inode)
|
|
1545
|
-
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
|
|
1764
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything, anything).once
|
|
1546
1765
|
plugin.refresh_watchers
|
|
1547
1766
|
|
|
1548
1767
|
flexstub(Fluent::Plugin::TailInput::TailWatcher) do |watcherclass|
|
|
@@ -1556,9 +1775,9 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1556
1775
|
test 'type of pos_file_compaction_interval is time' do
|
|
1557
1776
|
tail = {
|
|
1558
1777
|
"tag" => "tail",
|
|
1559
|
-
"path" => "#{
|
|
1778
|
+
"path" => "#{@tmp_dir}/*.txt",
|
|
1560
1779
|
"format" => "none",
|
|
1561
|
-
"pos_file" => "#{
|
|
1780
|
+
"pos_file" => "#{@tmp_dir}/pos/tail.pos",
|
|
1562
1781
|
"refresh_interval" => 1,
|
|
1563
1782
|
"read_from_head" => true,
|
|
1564
1783
|
'pos_file_compaction_interval' => '24h',
|
|
@@ -1575,7 +1794,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1575
1794
|
DummyWatcher = Struct.new("DummyWatcher", :tag)
|
|
1576
1795
|
|
|
1577
1796
|
def test_tag
|
|
1578
|
-
d = create_driver(
|
|
1797
|
+
d = create_driver(ex_config, false)
|
|
1579
1798
|
d.run {}
|
|
1580
1799
|
plugin = d.instance
|
|
1581
1800
|
mock(plugin.router).emit_stream('tail', anything).once
|
|
@@ -1651,12 +1870,47 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1651
1870
|
mock(plugin.router).emit_stream('pre.foo.bar.log.post', anything).once
|
|
1652
1871
|
plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
|
|
1653
1872
|
end
|
|
1873
|
+
|
|
1874
|
+
data(
|
|
1875
|
+
small: ["128", 128],
|
|
1876
|
+
KiB: ["1k", 1024]
|
|
1877
|
+
)
|
|
1878
|
+
test 'max_line_size' do |(label, size)|
|
|
1879
|
+
config = config_element("", "", {
|
|
1880
|
+
"tag" => "max_line_size",
|
|
1881
|
+
"path" => "#{@tmp_dir}/with_long_lines.txt",
|
|
1882
|
+
"format" => "none",
|
|
1883
|
+
"read_from_head" => true,
|
|
1884
|
+
"max_line_size" => label,
|
|
1885
|
+
"log_level" => "debug"
|
|
1886
|
+
})
|
|
1887
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/with_long_lines.txt", "w+") do |f|
|
|
1888
|
+
f.puts "foo"
|
|
1889
|
+
f.puts "x" * size # 'x' * size + \n > @max_line_size
|
|
1890
|
+
f.puts "bar"
|
|
1891
|
+
end
|
|
1892
|
+
d = create_driver(config, false)
|
|
1893
|
+
timestamp = Time.parse("Mon Nov 29 11:22:33 UTC 2021")
|
|
1894
|
+
Timecop.freeze(timestamp)
|
|
1895
|
+
d.run(expect_records: 2)
|
|
1896
|
+
assert_equal([
|
|
1897
|
+
[{"message" => "foo"},{"message" => "bar"}],
|
|
1898
|
+
[
|
|
1899
|
+
"2021-11-29 11:22:33 +0000 [warn]: received line length is longer than #{size}\n",
|
|
1900
|
+
"2021-11-29 11:22:33 +0000 [debug]: skipped line: #{'x' * size}\n"
|
|
1901
|
+
]
|
|
1902
|
+
],
|
|
1903
|
+
[
|
|
1904
|
+
d.events.collect { |event| event.last },
|
|
1905
|
+
d.logs[-2..]
|
|
1906
|
+
])
|
|
1907
|
+
end
|
|
1654
1908
|
end
|
|
1655
1909
|
|
|
1656
1910
|
# Ensure that no fatal exception is raised when a file is missing and that
|
|
1657
1911
|
# files that do exist are still tailed as expected.
|
|
1658
1912
|
def test_missing_file
|
|
1659
|
-
|
|
1913
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1660
1914
|
f.puts "test1"
|
|
1661
1915
|
f.puts "test2"
|
|
1662
1916
|
}
|
|
@@ -1665,16 +1919,16 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1665
1919
|
# since their interactions with the filesystem differ.
|
|
1666
1920
|
config1 = config_element("", "", {
|
|
1667
1921
|
"tag" => "t1",
|
|
1668
|
-
"path" => "#{
|
|
1922
|
+
"path" => "#{@tmp_dir}/non_existent_file.txt,#{@tmp_dir}/tail.txt",
|
|
1669
1923
|
"format" => "none",
|
|
1670
1924
|
"rotate_wait" => "2s",
|
|
1671
|
-
"pos_file" => "#{
|
|
1925
|
+
"pos_file" => "#{@tmp_dir}/tail.pos"
|
|
1672
1926
|
})
|
|
1673
1927
|
config2 = config1 + config_element("", "", { "read_from_head" => true })
|
|
1674
1928
|
[config1, config2].each do |config|
|
|
1675
1929
|
d = create_driver(config, false)
|
|
1676
1930
|
d.run(expect_emits: 1) do
|
|
1677
|
-
|
|
1931
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
1678
1932
|
f.puts "test3"
|
|
1679
1933
|
f.puts "test4"
|
|
1680
1934
|
}
|
|
@@ -1690,19 +1944,19 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1690
1944
|
|
|
1691
1945
|
sub_test_case 'inode_processing' do
|
|
1692
1946
|
def test_should_delete_file_pos_entry_for_non_existing_file_with_follow_inodes
|
|
1693
|
-
config =
|
|
1947
|
+
config = common_follow_inode_config
|
|
1694
1948
|
|
|
1695
|
-
path = "#{
|
|
1949
|
+
path = "#{@tmp_dir}/tail.txt"
|
|
1696
1950
|
ino = 1
|
|
1697
1951
|
pos = 1234
|
|
1698
|
-
|
|
1952
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "wb") {|f|
|
|
1699
1953
|
f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
|
|
1700
1954
|
}
|
|
1701
1955
|
|
|
1702
1956
|
d = create_driver(config, false)
|
|
1703
1957
|
d.run
|
|
1704
1958
|
|
|
1705
|
-
pos_file =
|
|
1959
|
+
pos_file = Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r")
|
|
1706
1960
|
pos_file.pos = 0
|
|
1707
1961
|
|
|
1708
1962
|
assert_raise(EOFError) do
|
|
@@ -1711,21 +1965,21 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1711
1965
|
end
|
|
1712
1966
|
|
|
1713
1967
|
def test_should_write_latest_offset_after_rotate_wait
|
|
1714
|
-
config =
|
|
1715
|
-
|
|
1968
|
+
config = common_follow_inode_config
|
|
1969
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1716
1970
|
f.puts "test1"
|
|
1717
1971
|
f.puts "test2"
|
|
1718
1972
|
}
|
|
1719
1973
|
|
|
1720
1974
|
d = create_driver(config, false)
|
|
1721
1975
|
d.run(expect_emits: 2, shutdown: false) do
|
|
1722
|
-
|
|
1723
|
-
FileUtils.move("#{
|
|
1976
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
|
1977
|
+
FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
|
|
1724
1978
|
sleep 1
|
|
1725
|
-
|
|
1979
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt" + "1", "ab") {|f| f.puts "test4\n"}
|
|
1726
1980
|
end
|
|
1727
1981
|
|
|
1728
|
-
pos_file =
|
|
1982
|
+
pos_file = Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r")
|
|
1729
1983
|
pos_file.pos = 0
|
|
1730
1984
|
line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
|
1731
1985
|
waiting(5) {
|
|
@@ -1739,47 +1993,28 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1739
1993
|
d.instance_shutdown
|
|
1740
1994
|
end
|
|
1741
1995
|
|
|
1742
|
-
def
|
|
1996
|
+
def test_should_remove_deleted_file
|
|
1743
1997
|
config = config_element("", "", {"format" => "none"})
|
|
1744
1998
|
|
|
1745
|
-
path = "#{
|
|
1999
|
+
path = "#{@tmp_dir}/tail.txt"
|
|
1746
2000
|
ino = 1
|
|
1747
2001
|
pos = 1234
|
|
1748
|
-
|
|
2002
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "wb") {|f|
|
|
1749
2003
|
f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
|
|
1750
2004
|
}
|
|
1751
2005
|
|
|
1752
2006
|
d = create_driver(config)
|
|
1753
|
-
d.run
|
|
1754
|
-
|
|
1755
|
-
pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
|
|
1756
|
-
pos_file.pos = 0
|
|
1757
|
-
|
|
1758
|
-
path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
|
1759
|
-
assert_equal(path, path_pos_ino[1])
|
|
1760
|
-
assert_equal(pos, path_pos_ino[2].to_i(16))
|
|
1761
|
-
assert_equal(ino, path_pos_ino[3].to_i(16))
|
|
1762
|
-
|
|
1763
|
-
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
|
1764
|
-
f.puts "test1"
|
|
1765
|
-
f.puts "test2"
|
|
1766
|
-
}
|
|
1767
|
-
Timecop.travel(Time.now + 10) do
|
|
1768
|
-
sleep 5
|
|
2007
|
+
d.run do
|
|
2008
|
+
pos_file = Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r")
|
|
1769
2009
|
pos_file.pos = 0
|
|
1770
|
-
|
|
1771
|
-
path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
|
1772
|
-
assert_equal(tuple.path, path_pos_ino[1])
|
|
1773
|
-
assert_equal(12, path_pos_ino[2].to_i(16))
|
|
1774
|
-
assert_equal(tuple.ino, path_pos_ino[3].to_i(16))
|
|
2010
|
+
assert_equal([], pos_file.readlines)
|
|
1775
2011
|
end
|
|
1776
|
-
d.instance_shutdown
|
|
1777
2012
|
end
|
|
1778
2013
|
|
|
1779
2014
|
def test_should_mark_file_unwatched_after_limit_recently_modified_and_rotate_wait
|
|
1780
2015
|
config = config_element("ROOT", "", {
|
|
1781
|
-
"path" => "#{
|
|
1782
|
-
"pos_file" => "#{
|
|
2016
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
2017
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
1783
2018
|
"tag" => "t1",
|
|
1784
2019
|
"rotate_wait" => "1s",
|
|
1785
2020
|
"refresh_interval" => "1s",
|
|
@@ -1791,14 +2026,14 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1791
2026
|
|
|
1792
2027
|
d = create_driver(config, false)
|
|
1793
2028
|
|
|
1794
|
-
|
|
2029
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1795
2030
|
f.puts "test1"
|
|
1796
2031
|
f.puts "test2"
|
|
1797
2032
|
}
|
|
1798
|
-
target_info = create_target_info("#{
|
|
2033
|
+
target_info = create_target_info("#{@tmp_dir}/tail.txt")
|
|
1799
2034
|
|
|
1800
2035
|
d.run(expect_emits: 1, shutdown: false) do
|
|
1801
|
-
|
|
2036
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
|
1802
2037
|
end
|
|
1803
2038
|
|
|
1804
2039
|
|
|
@@ -1816,8 +2051,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1816
2051
|
|
|
1817
2052
|
def test_should_read_from_head_on_file_renaming_with_star_in_pattern
|
|
1818
2053
|
config = config_element("ROOT", "", {
|
|
1819
|
-
"path" => "#{
|
|
1820
|
-
"pos_file" => "#{
|
|
2054
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
2055
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
1821
2056
|
"tag" => "t1",
|
|
1822
2057
|
"rotate_wait" => "10s",
|
|
1823
2058
|
"refresh_interval" => "1s",
|
|
@@ -1829,14 +2064,14 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1829
2064
|
|
|
1830
2065
|
d = create_driver(config, false)
|
|
1831
2066
|
|
|
1832
|
-
|
|
2067
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1833
2068
|
f.puts "test1"
|
|
1834
2069
|
f.puts "test2"
|
|
1835
2070
|
}
|
|
1836
2071
|
|
|
1837
2072
|
d.run(expect_emits: 2, shutdown: false) do
|
|
1838
|
-
|
|
1839
|
-
FileUtils.move("#{
|
|
2073
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
|
2074
|
+
FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1")
|
|
1840
2075
|
end
|
|
1841
2076
|
|
|
1842
2077
|
events = d.events
|
|
@@ -1845,20 +2080,20 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1845
2080
|
end
|
|
1846
2081
|
|
|
1847
2082
|
def test_should_not_read_from_head_on_rotation_when_watching_inodes
|
|
1848
|
-
config =
|
|
2083
|
+
config = common_follow_inode_config
|
|
1849
2084
|
|
|
1850
2085
|
d = create_driver(config, false)
|
|
1851
2086
|
|
|
1852
|
-
|
|
2087
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1853
2088
|
f.puts "test1"
|
|
1854
2089
|
f.puts "test2"
|
|
1855
2090
|
}
|
|
1856
2091
|
|
|
1857
2092
|
d.run(expect_emits: 1, shutdown: false) do
|
|
1858
|
-
|
|
2093
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
|
1859
2094
|
end
|
|
1860
2095
|
|
|
1861
|
-
FileUtils.move("#{
|
|
2096
|
+
FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1")
|
|
1862
2097
|
Timecop.travel(Time.now + 10) do
|
|
1863
2098
|
sleep 2
|
|
1864
2099
|
events = d.events
|
|
@@ -1869,23 +2104,23 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1869
2104
|
end
|
|
1870
2105
|
|
|
1871
2106
|
def test_should_mark_file_unwatched_if_same_name_file_created_with_different_inode
|
|
1872
|
-
config =
|
|
2107
|
+
config = common_follow_inode_config
|
|
1873
2108
|
|
|
1874
2109
|
d = create_driver(config, false)
|
|
1875
2110
|
|
|
1876
|
-
|
|
2111
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1877
2112
|
f.puts "test1"
|
|
1878
2113
|
f.puts "test2"
|
|
1879
2114
|
}
|
|
1880
|
-
target_info = create_target_info("#{
|
|
2115
|
+
target_info = create_target_info("#{@tmp_dir}/tail.txt")
|
|
1881
2116
|
|
|
1882
2117
|
d.run(expect_emits: 2, shutdown: false) do
|
|
1883
|
-
|
|
1884
|
-
cleanup_file("#{
|
|
1885
|
-
|
|
2118
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
|
2119
|
+
cleanup_file("#{@tmp_dir}/tail.txt")
|
|
2120
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "test4\n"}
|
|
1886
2121
|
end
|
|
1887
2122
|
|
|
1888
|
-
new_target_info = create_target_info("#{
|
|
2123
|
+
new_target_info = create_target_info("#{@tmp_dir}/tail.txt")
|
|
1889
2124
|
|
|
1890
2125
|
pos_file = d.instance.instance_variable_get(:@pf)
|
|
1891
2126
|
|
|
@@ -1901,56 +2136,65 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1901
2136
|
|
|
1902
2137
|
def test_should_close_watcher_after_rotate_wait
|
|
1903
2138
|
now = Time.now
|
|
1904
|
-
config =
|
|
2139
|
+
config = common_follow_inode_config + config_element('', '', {"rotate_wait" => "1s", "limit_recently_modified" => "1s"})
|
|
1905
2140
|
|
|
1906
2141
|
d = create_driver(config, false)
|
|
2142
|
+
d.instance.instance_eval do
|
|
2143
|
+
opened_file_metrics = Fluent::Plugin::LocalMetrics.new
|
|
2144
|
+
opened_file_metrics.configure(config_element('metrics', '', {}))
|
|
2145
|
+
closed_file_metrics = Fluent::Plugin::LocalMetrics.new
|
|
2146
|
+
closed_file_metrics.configure(config_element('metrics', '', {}))
|
|
2147
|
+
rotated_file_metrics = Fluent::Plugin::LocalMetrics.new
|
|
2148
|
+
rotated_file_metrics.configure(config_element('metrics', '', {}))
|
|
2149
|
+
@metrics = Fluent::Plugin::TailInput::MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
|
|
2150
|
+
end
|
|
1907
2151
|
|
|
1908
|
-
|
|
2152
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1909
2153
|
f.puts "test1"
|
|
1910
2154
|
f.puts "test2"
|
|
1911
2155
|
}
|
|
1912
|
-
target_info = create_target_info("#{
|
|
1913
|
-
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, true, anything, nil, anything).once
|
|
2156
|
+
target_info = create_target_info("#{@tmp_dir}/tail.txt")
|
|
2157
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, true, anything, nil, anything, anything).once
|
|
1914
2158
|
d.run(shutdown: false)
|
|
1915
|
-
assert d.instance.instance_variable_get(:@tails)[target_info]
|
|
2159
|
+
assert d.instance.instance_variable_get(:@tails)[target_info.path]
|
|
1916
2160
|
|
|
1917
2161
|
Timecop.travel(now + 10) do
|
|
1918
2162
|
d.instance.instance_eval do
|
|
1919
|
-
sleep 0.1 until @tails[target_info] == nil
|
|
2163
|
+
sleep 0.1 until @tails[target_info.path] == nil
|
|
1920
2164
|
end
|
|
1921
|
-
assert_nil d.instance.instance_variable_get(:@tails)[target_info]
|
|
2165
|
+
assert_nil d.instance.instance_variable_get(:@tails)[target_info.path]
|
|
1922
2166
|
end
|
|
1923
2167
|
d.instance_shutdown
|
|
1924
2168
|
end
|
|
1925
2169
|
|
|
1926
2170
|
def test_should_create_new_watcher_for_new_file_with_same_name
|
|
1927
2171
|
now = Time.now
|
|
1928
|
-
config =
|
|
2172
|
+
config = common_follow_inode_config + config_element('', '', {"limit_recently_modified" => "2s"})
|
|
1929
2173
|
|
|
1930
2174
|
d = create_driver(config, false)
|
|
1931
2175
|
|
|
1932
|
-
|
|
2176
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1933
2177
|
f.puts "test1"
|
|
1934
2178
|
f.puts "test2"
|
|
1935
2179
|
}
|
|
1936
|
-
path_ino = create_target_info("#{
|
|
2180
|
+
path_ino = create_target_info("#{@tmp_dir}/tail.txt")
|
|
1937
2181
|
|
|
1938
2182
|
d.run(expect_emits: 1, shutdown: false) do
|
|
1939
|
-
|
|
2183
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
|
1940
2184
|
end
|
|
1941
2185
|
|
|
1942
|
-
cleanup_file("#{
|
|
1943
|
-
|
|
2186
|
+
cleanup_file("#{@tmp_dir}/tail.txt")
|
|
2187
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1944
2188
|
f.puts "test3"
|
|
1945
2189
|
f.puts "test4"
|
|
1946
2190
|
}
|
|
1947
|
-
new_path_ino = create_target_info("#{
|
|
2191
|
+
new_path_ino = create_target_info("#{@tmp_dir}/tail.txt")
|
|
1948
2192
|
|
|
1949
2193
|
Timecop.travel(now + 10) do
|
|
1950
2194
|
sleep 3
|
|
1951
2195
|
d.instance.instance_eval do
|
|
1952
|
-
@tails[path_ino] == nil
|
|
1953
|
-
@tails[new_path_ino] != nil
|
|
2196
|
+
@tails[path_ino.path] == nil
|
|
2197
|
+
@tails[new_path_ino.path] != nil
|
|
1954
2198
|
end
|
|
1955
2199
|
end
|
|
1956
2200
|
|
|
@@ -1962,19 +2206,19 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1962
2206
|
end
|
|
1963
2207
|
|
|
1964
2208
|
def test_truncate_file_with_follow_inodes
|
|
1965
|
-
config =
|
|
2209
|
+
config = common_follow_inode_config
|
|
1966
2210
|
|
|
1967
2211
|
d = create_driver(config, false)
|
|
1968
2212
|
|
|
1969
|
-
|
|
2213
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1970
2214
|
f.puts "test1"
|
|
1971
2215
|
f.puts "test2"
|
|
1972
2216
|
}
|
|
1973
2217
|
|
|
1974
2218
|
d.run(expect_emits: 3, shutdown: false) do
|
|
1975
|
-
|
|
2219
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
|
1976
2220
|
sleep 2
|
|
1977
|
-
|
|
2221
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w+b") {|f| f.puts "test4\n"}
|
|
1978
2222
|
end
|
|
1979
2223
|
|
|
1980
2224
|
events = d.events
|
|
@@ -1988,15 +2232,15 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
1988
2232
|
|
|
1989
2233
|
# issue #3464
|
|
1990
2234
|
def test_should_replace_target_info
|
|
1991
|
-
|
|
2235
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
1992
2236
|
f.puts "test1\n"
|
|
1993
2237
|
}
|
|
1994
|
-
target_info = create_target_info("#{
|
|
2238
|
+
target_info = create_target_info("#{@tmp_dir}/tail.txt")
|
|
1995
2239
|
inodes = []
|
|
1996
2240
|
|
|
1997
2241
|
config = config_element("ROOT", "", {
|
|
1998
|
-
"path" => "#{
|
|
1999
|
-
"pos_file" => "#{
|
|
2242
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
2243
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
2000
2244
|
"tag" => "t1",
|
|
2001
2245
|
"refresh_interval" => "60s",
|
|
2002
2246
|
"read_from_head" => "true",
|
|
@@ -2009,21 +2253,21 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2009
2253
|
while d.events.size < 1 do
|
|
2010
2254
|
sleep 0.1
|
|
2011
2255
|
end
|
|
2012
|
-
inodes = d.instance.instance_variable_get(:@tails).
|
|
2013
|
-
|
|
2256
|
+
inodes = d.instance.instance_variable_get(:@tails).values.collect do |tw|
|
|
2257
|
+
tw.ino
|
|
2014
2258
|
end
|
|
2015
2259
|
assert_equal([target_info.ino], inodes)
|
|
2016
2260
|
|
|
2017
|
-
cleanup_file("#{
|
|
2018
|
-
|
|
2261
|
+
cleanup_file("#{@tmp_dir}/tail.txt")
|
|
2262
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "test2\n"}
|
|
2019
2263
|
|
|
2020
2264
|
while d.events.size < 2 do
|
|
2021
2265
|
sleep 0.1
|
|
2022
2266
|
end
|
|
2023
|
-
inodes = d.instance.instance_variable_get(:@tails).
|
|
2024
|
-
|
|
2267
|
+
inodes = d.instance.instance_variable_get(:@tails).values.collect do |tw|
|
|
2268
|
+
tw.ino
|
|
2025
2269
|
end
|
|
2026
|
-
new_target_info = create_target_info("#{
|
|
2270
|
+
new_target_info = create_target_info("#{@tmp_dir}/tail.txt")
|
|
2027
2271
|
assert_not_equal(target_info.ino, new_target_info.ino)
|
|
2028
2272
|
assert_equal([new_target_info.ino], inodes)
|
|
2029
2273
|
end
|
|
@@ -2032,7 +2276,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2032
2276
|
|
|
2033
2277
|
sub_test_case "tail_path" do
|
|
2034
2278
|
def test_tail_path_with_singleline
|
|
2035
|
-
|
|
2279
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
|
|
2036
2280
|
f.puts "test1"
|
|
2037
2281
|
f.puts "test2"
|
|
2038
2282
|
}
|
|
@@ -2040,7 +2284,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2040
2284
|
d = create_driver(SINGLE_LINE_CONFIG + config_element("", "", { "path_key" => "path" }))
|
|
2041
2285
|
|
|
2042
2286
|
d.run(expect_emits: 1) do
|
|
2043
|
-
|
|
2287
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
|
|
2044
2288
|
f.puts "test3"
|
|
2045
2289
|
f.puts "test4"
|
|
2046
2290
|
}
|
|
@@ -2049,12 +2293,12 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2049
2293
|
events = d.events
|
|
2050
2294
|
assert_equal(true, events.length > 0)
|
|
2051
2295
|
events.each do |emit|
|
|
2052
|
-
assert_equal("#{
|
|
2296
|
+
assert_equal("#{@tmp_dir}/tail.txt", emit[2]["path"])
|
|
2053
2297
|
end
|
|
2054
2298
|
end
|
|
2055
2299
|
|
|
2056
2300
|
def test_tail_path_with_multiline_with_firstline
|
|
2057
|
-
|
|
2301
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
2058
2302
|
|
|
2059
2303
|
config = config_element("", "", {
|
|
2060
2304
|
"path_key" => "path",
|
|
@@ -2064,7 +2308,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2064
2308
|
})
|
|
2065
2309
|
d = create_driver(config)
|
|
2066
2310
|
d.run(expect_emits: 1) do
|
|
2067
|
-
|
|
2311
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
2068
2312
|
f.puts "f test1"
|
|
2069
2313
|
f.puts "s test2"
|
|
2070
2314
|
f.puts "f test3"
|
|
@@ -2079,12 +2323,12 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2079
2323
|
events = d.events
|
|
2080
2324
|
assert_equal(4, events.length)
|
|
2081
2325
|
events.each do |emit|
|
|
2082
|
-
assert_equal("#{
|
|
2326
|
+
assert_equal("#{@tmp_dir}/tail.txt", emit[2]["path"])
|
|
2083
2327
|
end
|
|
2084
2328
|
end
|
|
2085
2329
|
|
|
2086
2330
|
def test_tail_path_with_multiline_without_firstline
|
|
2087
|
-
|
|
2331
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
|
|
2088
2332
|
|
|
2089
2333
|
config = config_element("", "", {
|
|
2090
2334
|
"path_key" => "path",
|
|
@@ -2095,7 +2339,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2095
2339
|
})
|
|
2096
2340
|
d = create_driver(config)
|
|
2097
2341
|
d.run(expect_emits: 1) do
|
|
2098
|
-
|
|
2342
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
|
|
2099
2343
|
f.puts "foo 1"
|
|
2100
2344
|
f.puts "bar 1"
|
|
2101
2345
|
f.puts "baz 1"
|
|
@@ -2105,7 +2349,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2105
2349
|
events = d.events
|
|
2106
2350
|
assert(events.length > 0)
|
|
2107
2351
|
events.each do |emit|
|
|
2108
|
-
assert_equal("#{
|
|
2352
|
+
assert_equal("#{@tmp_dir}/tail.txt", emit[2]["path"])
|
|
2109
2353
|
end
|
|
2110
2354
|
end
|
|
2111
2355
|
|
|
@@ -2113,8 +2357,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2113
2357
|
if ENV["APPVEYOR"] && Fluent.windows?
|
|
2114
2358
|
omit "This testcase is unstable on AppVeyor."
|
|
2115
2359
|
end
|
|
2116
|
-
files = ["#{
|
|
2117
|
-
files.each { |file|
|
|
2360
|
+
files = ["#{@tmp_dir}/tail1.txt", "#{@tmp_dir}/tail2.txt"]
|
|
2361
|
+
files.each { |file| Fluent::FileWrapper.open(file, "wb") { |f| } }
|
|
2118
2362
|
|
|
2119
2363
|
config = config_element("", "", {
|
|
2120
2364
|
"path" => "#{files[0]},#{files[1]}",
|
|
@@ -2127,7 +2371,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2127
2371
|
d = create_driver(config, false)
|
|
2128
2372
|
d.run(expect_emits: 2) do
|
|
2129
2373
|
files.each do |file|
|
|
2130
|
-
|
|
2374
|
+
Fluent::FileWrapper.open(file, 'ab') { |f|
|
|
2131
2375
|
f.puts "f #{file} line should be ignored"
|
|
2132
2376
|
f.puts "s test1"
|
|
2133
2377
|
f.puts "f test2"
|
|
@@ -2147,30 +2391,30 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2147
2391
|
|
|
2148
2392
|
def test_limit_recently_modified
|
|
2149
2393
|
now = Time.new(2010, 1, 2, 3, 4, 5)
|
|
2150
|
-
FileUtils.touch("#{
|
|
2151
|
-
FileUtils.touch("#{
|
|
2152
|
-
FileUtils.touch("#{
|
|
2394
|
+
FileUtils.touch("#{@tmp_dir}/tail_unwatch.txt", mtime: (now - 3601))
|
|
2395
|
+
FileUtils.touch("#{@tmp_dir}/tail_watch1.txt", mtime: (now - 3600))
|
|
2396
|
+
FileUtils.touch("#{@tmp_dir}/tail_watch2.txt", mtime: now)
|
|
2153
2397
|
|
|
2154
2398
|
config = config_element('', '', {
|
|
2155
2399
|
'tag' => 'tail',
|
|
2156
|
-
'path' => "#{
|
|
2400
|
+
'path' => "#{@tmp_dir}/*.txt",
|
|
2157
2401
|
'format' => 'none',
|
|
2158
2402
|
'limit_recently_modified' => '3600s'
|
|
2159
2403
|
})
|
|
2160
2404
|
|
|
2161
2405
|
expected_files = [
|
|
2162
|
-
create_target_info("#{
|
|
2163
|
-
create_target_info("#{
|
|
2406
|
+
create_target_info("#{@tmp_dir}/tail_watch1.txt"),
|
|
2407
|
+
create_target_info("#{@tmp_dir}/tail_watch2.txt")
|
|
2164
2408
|
]
|
|
2165
2409
|
|
|
2166
2410
|
Timecop.freeze(now) do
|
|
2167
2411
|
plugin = create_driver(config, false).instance
|
|
2168
|
-
assert_equal
|
|
2412
|
+
assert_equal(expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
|
|
2169
2413
|
end
|
|
2170
2414
|
end
|
|
2171
2415
|
|
|
2172
2416
|
def test_skip_refresh_on_startup
|
|
2173
|
-
FileUtils.touch("#{
|
|
2417
|
+
FileUtils.touch("#{@tmp_dir}/tail.txt")
|
|
2174
2418
|
config = config_element('', '', {
|
|
2175
2419
|
'format' => 'none',
|
|
2176
2420
|
'refresh_interval' => 1,
|
|
@@ -2178,36 +2422,42 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2178
2422
|
})
|
|
2179
2423
|
d = create_driver(config)
|
|
2180
2424
|
d.run(shutdown: false) {}
|
|
2181
|
-
assert_equal
|
|
2425
|
+
assert_equal(0, d.instance.instance_variable_get(:@tails).keys.size)
|
|
2182
2426
|
# detect a file at first execution of in_tail_refresh_watchers timer
|
|
2183
2427
|
waiting(5) { sleep 0.1 until d.instance.instance_variable_get(:@tails).keys.size == 1 }
|
|
2184
2428
|
d.instance_shutdown
|
|
2185
2429
|
end
|
|
2186
2430
|
|
|
2187
2431
|
def test_ENOENT_error_after_setup_watcher
|
|
2188
|
-
path = "#{
|
|
2432
|
+
path = "#{@tmp_dir}/tail.txt"
|
|
2189
2433
|
FileUtils.touch(path)
|
|
2190
2434
|
config = config_element('', '', {
|
|
2191
2435
|
'format' => 'none',
|
|
2192
2436
|
})
|
|
2193
2437
|
d = create_driver(config)
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2438
|
+
file_deleted = false
|
|
2439
|
+
mock.proxy(d.instance).existence_path do |hash|
|
|
2440
|
+
unless file_deleted
|
|
2441
|
+
cleanup_file(path)
|
|
2442
|
+
file_deleted = true
|
|
2443
|
+
end
|
|
2444
|
+
hash
|
|
2445
|
+
end.twice
|
|
2198
2446
|
assert_nothing_raised do
|
|
2199
2447
|
d.run(shutdown: false) {}
|
|
2200
2448
|
end
|
|
2201
|
-
|
|
2202
|
-
|
|
2449
|
+
assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed. Continuing without tailing it.\n") },
|
|
2450
|
+
$log.out.logs.join("\n"))
|
|
2451
|
+
ensure
|
|
2452
|
+
d.instance_shutdown if d && d.instance
|
|
2203
2453
|
end
|
|
2204
2454
|
|
|
2205
2455
|
def test_EACCES_error_after_setup_watcher
|
|
2206
2456
|
omit "Cannot test with root user" if Process::UID.eid == 0
|
|
2207
|
-
path = "#{
|
|
2457
|
+
path = "#{@tmp_dir}/noaccess/tail.txt"
|
|
2208
2458
|
begin
|
|
2209
|
-
FileUtils.mkdir_p("#{
|
|
2210
|
-
FileUtils.chmod(0755, "#{
|
|
2459
|
+
FileUtils.mkdir_p("#{@tmp_dir}/noaccess")
|
|
2460
|
+
FileUtils.chmod(0755, "#{@tmp_dir}/noaccess")
|
|
2211
2461
|
FileUtils.touch(path)
|
|
2212
2462
|
config = config_element('', '', {
|
|
2213
2463
|
'tag' => "tail",
|
|
@@ -2215,25 +2465,26 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2215
2465
|
'format' => 'none',
|
|
2216
2466
|
})
|
|
2217
2467
|
d = create_driver(config, false)
|
|
2218
|
-
mock.proxy(d.instance).
|
|
2219
|
-
FileUtils.chmod(0000, "#{
|
|
2220
|
-
|
|
2221
|
-
end
|
|
2468
|
+
mock.proxy(d.instance).existence_path do |hash|
|
|
2469
|
+
FileUtils.chmod(0000, "#{@tmp_dir}/noaccess")
|
|
2470
|
+
hash
|
|
2471
|
+
end.twice
|
|
2222
2472
|
assert_nothing_raised do
|
|
2223
2473
|
d.run(shutdown: false) {}
|
|
2224
2474
|
end
|
|
2225
|
-
|
|
2226
|
-
|
|
2475
|
+
assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed. Continuing without tailing it.\n") },
|
|
2476
|
+
$log.out.logs.join("\n"))
|
|
2227
2477
|
end
|
|
2228
2478
|
ensure
|
|
2229
|
-
if
|
|
2230
|
-
|
|
2231
|
-
FileUtils.
|
|
2479
|
+
d.instance_shutdown if d && d.instance
|
|
2480
|
+
if File.exist?("#{@tmp_dir}/noaccess")
|
|
2481
|
+
FileUtils.chmod(0755, "#{@tmp_dir}/noaccess")
|
|
2482
|
+
FileUtils.rm_rf("#{@tmp_dir}/noaccess")
|
|
2232
2483
|
end
|
|
2233
2484
|
end unless Fluent.windows?
|
|
2234
2485
|
|
|
2235
2486
|
def test_EACCES
|
|
2236
|
-
path = "#{
|
|
2487
|
+
path = "#{@tmp_dir}/tail.txt"
|
|
2237
2488
|
FileUtils.touch(path)
|
|
2238
2489
|
config = config_element('', '', {
|
|
2239
2490
|
'format' => 'none',
|
|
@@ -2245,13 +2496,14 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2245
2496
|
assert_nothing_raised do
|
|
2246
2497
|
d.run(shutdown: false) {}
|
|
2247
2498
|
end
|
|
2248
|
-
d.instance_shutdown
|
|
2249
2499
|
assert($log.out.logs.any?{|log| log.include?("expand_paths: stat() for #{path} failed with Errno::EACCES. Skip file.\n") })
|
|
2500
|
+
ensure
|
|
2501
|
+
d.instance_shutdown if d && d.instance
|
|
2250
2502
|
end
|
|
2251
2503
|
|
|
2252
2504
|
def test_shutdown_timeout
|
|
2253
|
-
|
|
2254
|
-
|
|
2505
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") do |f|
|
|
2506
|
+
# Should be large enough to take too long time to consume
|
|
2255
2507
|
(1024 * 1024 * 5).times do
|
|
2256
2508
|
f.puts "{\"test\":\"fizzbuzz\"}"
|
|
2257
2509
|
end
|
|
@@ -2263,18 +2515,774 @@ class TailInputTest < Test::Unit::TestCase
|
|
|
2263
2515
|
'format' => 'json',
|
|
2264
2516
|
'skip_refresh_on_startup' => true,
|
|
2265
2517
|
})
|
|
2518
|
+
shutdown_start_time = 0
|
|
2519
|
+
|
|
2266
2520
|
d = create_driver(config)
|
|
2267
2521
|
mock.proxy(d.instance).io_handler(anything, anything) do |io_handler|
|
|
2522
|
+
mock.proxy(io_handler).ready_to_shutdown(anything) do
|
|
2523
|
+
shutdown_start_time = Fluent::Clock.now
|
|
2524
|
+
end
|
|
2268
2525
|
io_handler.shutdown_timeout = 0.5
|
|
2269
2526
|
io_handler
|
|
2270
2527
|
end
|
|
2271
2528
|
|
|
2272
|
-
start_time = Fluent::Clock.now
|
|
2273
2529
|
assert_nothing_raised do
|
|
2274
2530
|
d.run(expect_emits: 1)
|
|
2275
2531
|
end
|
|
2276
2532
|
|
|
2277
|
-
elapsed = Fluent::Clock.now -
|
|
2278
|
-
assert_true(elapsed > 0.5 && elapsed < 2.
|
|
2533
|
+
elapsed = Fluent::Clock.now - shutdown_start_time
|
|
2534
|
+
assert_true(elapsed > 0.5 && elapsed < 2.0,
|
|
2535
|
+
"elapsed time: #{elapsed}")
|
|
2536
|
+
end
|
|
2537
|
+
|
|
2538
|
+
sub_test_case "throttling logs at in_tail level" do
|
|
2539
|
+
data("file test1.log no_limit 5120 text: msg" => ["test1.log", 5120, "msg"],
|
|
2540
|
+
"file test2.log no_limit 1024 text: test" => ["test2.log", 1024, "test"])
|
|
2541
|
+
def test_lines_collected_with_no_throttling(data)
|
|
2542
|
+
file, num_lines, msg = data
|
|
2543
|
+
|
|
2544
|
+
pattern = "/^#{@tmp_dir}\/(?<file>.+)\.log$/"
|
|
2545
|
+
rule = create_rule_directive({
|
|
2546
|
+
"file" => "/test.*/",
|
|
2547
|
+
}, -1)
|
|
2548
|
+
group = create_group_directive(pattern, "1s", rule)
|
|
2549
|
+
path_element = create_path_element(file)
|
|
2550
|
+
|
|
2551
|
+
conf = ROOT_CONFIG + group + path_element + CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG
|
|
2552
|
+
|
|
2553
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/#{file}", 'wb') do |f|
|
|
2554
|
+
num_lines.times do
|
|
2555
|
+
f.puts "#{msg}\n"
|
|
2556
|
+
end
|
|
2557
|
+
end
|
|
2558
|
+
|
|
2559
|
+
|
|
2560
|
+
d = create_driver(conf, false)
|
|
2561
|
+
d.run(timeout: 3) do
|
|
2562
|
+
start_time = Fluent::Clock.now
|
|
2563
|
+
|
|
2564
|
+
assert_equal(num_lines, d.record_count)
|
|
2565
|
+
assert_equal({ "message" => msg }, d.events[0][2])
|
|
2566
|
+
|
|
2567
|
+
prev_count = d.record_count
|
|
2568
|
+
sleep(0.1) while d.emit_count < 1
|
|
2569
|
+
assert_true(Fluent::Clock.now - start_time < 2)
|
|
2570
|
+
## after waiting for 1 (+ jitter) secs, limit will reset
|
|
2571
|
+
## Plugin will start reading but it will encounter EOF Error
|
|
2572
|
+
## since no logs are left to be read
|
|
2573
|
+
## Hence, d.record_count = prev_count
|
|
2574
|
+
tail_watcher_interval = 1.0 # hard coded value in in_tail
|
|
2575
|
+
safety_ratio = 1.2
|
|
2576
|
+
jitter = tail_watcher_interval * safety_ratio
|
|
2577
|
+
sleep(1.0 + jitter)
|
|
2578
|
+
assert_equal(0, d.record_count - prev_count)
|
|
2579
|
+
end
|
|
2580
|
+
end
|
|
2581
|
+
|
|
2582
|
+
test "lines collected with throttling" do
|
|
2583
|
+
file = "podname1_namespace12_container-123456.log"
|
|
2584
|
+
limit = 1000
|
|
2585
|
+
rate_period = 2
|
|
2586
|
+
num_lines = 3000
|
|
2587
|
+
msg = "a" * 8190 # Total size = 8190 bytes + 2 (\n) bytes
|
|
2588
|
+
|
|
2589
|
+
rule = create_rule_directive({
|
|
2590
|
+
"namespace"=> "/namespace.+/",
|
|
2591
|
+
"podname"=> "/podname.+/",
|
|
2592
|
+
}, limit)
|
|
2593
|
+
path_element = create_path_element(file)
|
|
2594
|
+
conf = ROOT_CONFIG + create_group_directive(tailing_group_pattern, "#{rate_period}s", rule) + path_element + SINGLE_LINE_CONFIG + CONFIG_READ_FROM_HEAD
|
|
2595
|
+
|
|
2596
|
+
d = create_driver(conf, false)
|
|
2597
|
+
file_path = "#{@tmp_dir}/#{file}"
|
|
2598
|
+
|
|
2599
|
+
Fluent::FileWrapper.open(file_path, 'wb') do |f|
|
|
2600
|
+
num_lines.times do
|
|
2601
|
+
f.puts msg
|
|
2602
|
+
end
|
|
2603
|
+
end
|
|
2604
|
+
|
|
2605
|
+
d.run(timeout: 15) do
|
|
2606
|
+
sleep_interval = 0.1
|
|
2607
|
+
tail_watcher_interval = 1.0 # hard coded value in in_tail
|
|
2608
|
+
safety_ratio = 1.2
|
|
2609
|
+
lower_jitter = sleep_interval * safety_ratio
|
|
2610
|
+
upper_jitter = (tail_watcher_interval + sleep_interval) * safety_ratio
|
|
2611
|
+
lower_interval = rate_period - lower_jitter
|
|
2612
|
+
upper_interval = rate_period + upper_jitter
|
|
2613
|
+
|
|
2614
|
+
emit_count = 0
|
|
2615
|
+
prev_count = 0
|
|
2616
|
+
|
|
2617
|
+
while emit_count < 3 do
|
|
2618
|
+
start_time = Fluent::Clock.now
|
|
2619
|
+
sleep(sleep_interval) while d.emit_count <= emit_count
|
|
2620
|
+
elapsed_seconds = Fluent::Clock.now - start_time
|
|
2621
|
+
if emit_count > 0
|
|
2622
|
+
assert_true(elapsed_seconds > lower_interval && elapsed_seconds < upper_interval,
|
|
2623
|
+
"elapsed_seconds #{elapsed_seconds} is out of allowed range:\n" +
|
|
2624
|
+
" lower: #{lower_interval} [sec]\n" +
|
|
2625
|
+
" upper: #{upper_interval} [sec]")
|
|
2626
|
+
end
|
|
2627
|
+
assert_equal(limit, d.record_count - prev_count)
|
|
2628
|
+
emit_count = d.emit_count
|
|
2629
|
+
prev_count = d.record_count
|
|
2630
|
+
end
|
|
2631
|
+
|
|
2632
|
+
## When all the lines are read and rate_period seconds are over
|
|
2633
|
+
## limit will reset and since there are no more logs to be read,
|
|
2634
|
+
## number_lines_read will be 0
|
|
2635
|
+
sleep upper_interval
|
|
2636
|
+
gw = d.instance.find_group_from_metadata(file_path)
|
|
2637
|
+
assert_equal(0, gw.current_paths[file_path].number_lines_read)
|
|
2638
|
+
end
|
|
2639
|
+
end
|
|
2640
|
+
end
|
|
2641
|
+
|
|
2642
|
+
sub_test_case "Update watchers for rotation with follow_inodes" do
|
|
2643
|
+
def test_updateTW_before_refreshTW_and_detach_before_refreshTW
|
|
2644
|
+
config = config_element(
|
|
2645
|
+
"ROOT",
|
|
2646
|
+
"",
|
|
2647
|
+
{
|
|
2648
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
2649
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
2650
|
+
"tag" => "t1",
|
|
2651
|
+
"format" => "none",
|
|
2652
|
+
"read_from_head" => "true",
|
|
2653
|
+
"follow_inodes" => "true",
|
|
2654
|
+
# In order to detach the old watcher quickly.
|
|
2655
|
+
"rotate_wait" => "1s",
|
|
2656
|
+
# In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
|
|
2657
|
+
# called by a timer.
|
|
2658
|
+
"refresh_interval" => "1h",
|
|
2659
|
+
# stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
|
|
2660
|
+
# so disable it in order to reproduce the same condition stably.
|
|
2661
|
+
"enable_stat_watcher" => "false",
|
|
2662
|
+
}
|
|
2663
|
+
)
|
|
2664
|
+
d = create_driver(config, false)
|
|
2665
|
+
|
|
2666
|
+
tail_watchers = []
|
|
2667
|
+
stub.proxy(d.instance).setup_watcher do |tw|
|
|
2668
|
+
tail_watchers.append(tw)
|
|
2669
|
+
tw
|
|
2670
|
+
end
|
|
2671
|
+
|
|
2672
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file1 log1"}
|
|
2673
|
+
|
|
2674
|
+
d.run(expect_records: 4, timeout: 10) do
|
|
2675
|
+
# Rotate (If the timing is bad, `TailWatcher::on_notify` might be called between mv and new-file-creation)
|
|
2676
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file1 log2"}
|
|
2677
|
+
FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
|
|
2678
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file2 log1"}
|
|
2679
|
+
|
|
2680
|
+
# `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` updates the TailWatcher:
|
|
2681
|
+
# TailWatcher(path: "tail.txt", inode: inode_0) => TailWatcher(path: "tail.txt", inode: inode_1)
|
|
2682
|
+
# The old TailWathcer is detached here since `rotate_wait` is just `1s`.
|
|
2683
|
+
sleep 3
|
|
2684
|
+
|
|
2685
|
+
# This reproduces the following situation:
|
|
2686
|
+
# Rotation => update_watcher => refresh_watchers
|
|
2687
|
+
# This adds a new TailWatcher: TailWatcher(path: "tail.txt1", inode: inode_0)
|
|
2688
|
+
d.instance.refresh_watchers
|
|
2689
|
+
|
|
2690
|
+
# Append to the new current log file.
|
|
2691
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file2 log2"}
|
|
2692
|
+
end
|
|
2693
|
+
|
|
2694
|
+
inode_0 = tail_watchers[0].ino
|
|
2695
|
+
inode_1 = tail_watchers[1].ino
|
|
2696
|
+
record_values = d.events.collect { |event| event[2]["message"] }.sort
|
|
2697
|
+
position_entries = []
|
|
2698
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
|
|
2699
|
+
f.readlines(chomp: true).each do |line|
|
|
2700
|
+
values = line.split("\t")
|
|
2701
|
+
position_entries.append([values[0], values[1], values[2].to_i(16)])
|
|
2702
|
+
end
|
|
2703
|
+
end
|
|
2704
|
+
|
|
2705
|
+
assert_equal(
|
|
2706
|
+
{
|
|
2707
|
+
record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2"],
|
|
2708
|
+
tail_watcher_paths: ["#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1"],
|
|
2709
|
+
tail_watcher_inodes: [inode_0, inode_1, inode_0],
|
|
2710
|
+
tail_watcher_io_handler_opened_statuses: [false, false, false],
|
|
2711
|
+
position_entries: [
|
|
2712
|
+
["#{@tmp_dir}/tail.txt", "0000000000000016", inode_0],
|
|
2713
|
+
["#{@tmp_dir}/tail.txt", "0000000000000016", inode_1],
|
|
2714
|
+
],
|
|
2715
|
+
},
|
|
2716
|
+
{
|
|
2717
|
+
record_values: record_values,
|
|
2718
|
+
tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
|
|
2719
|
+
tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
|
|
2720
|
+
tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
|
|
2721
|
+
position_entries: position_entries
|
|
2722
|
+
},
|
|
2723
|
+
)
|
|
2724
|
+
end
|
|
2725
|
+
|
|
2726
|
+
def test_updateTW_before_refreshTW_and_detach_after_refreshTW
|
|
2727
|
+
config = config_element(
|
|
2728
|
+
"ROOT",
|
|
2729
|
+
"",
|
|
2730
|
+
{
|
|
2731
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
2732
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
2733
|
+
"tag" => "t1",
|
|
2734
|
+
"format" => "none",
|
|
2735
|
+
"read_from_head" => "true",
|
|
2736
|
+
"follow_inodes" => "true",
|
|
2737
|
+
# In order to detach the old watcher after refresh_watchers.
|
|
2738
|
+
"rotate_wait" => "4s",
|
|
2739
|
+
# In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
|
|
2740
|
+
# called by a timer.
|
|
2741
|
+
"refresh_interval" => "1h",
|
|
2742
|
+
# stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
|
|
2743
|
+
# so disable it in order to reproduce the same condition stably.
|
|
2744
|
+
"enable_stat_watcher" => "false",
|
|
2745
|
+
}
|
|
2746
|
+
)
|
|
2747
|
+
d = create_driver(config, false)
|
|
2748
|
+
|
|
2749
|
+
tail_watchers = []
|
|
2750
|
+
stub.proxy(d.instance).setup_watcher do |tw|
|
|
2751
|
+
tail_watchers.append(tw)
|
|
2752
|
+
tw
|
|
2753
|
+
end
|
|
2754
|
+
|
|
2755
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file1 log1"}
|
|
2756
|
+
|
|
2757
|
+
d.run(expect_records: 4, timeout: 10) do
|
|
2758
|
+
# Rotate (If the timing is bad, `TailWatcher::on_notify` might be called between mv and new-file-creation)
|
|
2759
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file1 log2"}
|
|
2760
|
+
FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
|
|
2761
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file2 log1"}
|
|
2762
|
+
|
|
2763
|
+
# `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` updates the TailWatcher:
|
|
2764
|
+
# TailWatcher(path: "tail.txt", inode: inode_0) => TailWatcher(path: "tail.txt", inode: inode_1)
|
|
2765
|
+
sleep 2
|
|
2766
|
+
|
|
2767
|
+
# This reproduces the following situation:
|
|
2768
|
+
# Rotation => update_watcher => refresh_watchers
|
|
2769
|
+
# This adds a new TailWatcher: TailWatcher(path: "tail.txt1", inode: inode_0)
|
|
2770
|
+
d.instance.refresh_watchers
|
|
2771
|
+
|
|
2772
|
+
# The old TailWathcer is detached here since `rotate_wait` is `4s`.
|
|
2773
|
+
sleep 3
|
|
2774
|
+
|
|
2775
|
+
# Append to the new current log file.
|
|
2776
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file2 log2"}
|
|
2777
|
+
end
|
|
2778
|
+
|
|
2779
|
+
inode_0 = tail_watchers[0].ino
|
|
2780
|
+
inode_1 = tail_watchers[1].ino
|
|
2781
|
+
record_values = d.events.collect { |event| event[2]["message"] }.sort
|
|
2782
|
+
position_entries = []
|
|
2783
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
|
|
2784
|
+
f.readlines(chomp: true).each do |line|
|
|
2785
|
+
values = line.split("\t")
|
|
2786
|
+
position_entries.append([values[0], values[1], values[2].to_i(16)])
|
|
2787
|
+
end
|
|
2788
|
+
end
|
|
2789
|
+
|
|
2790
|
+
assert_equal(
|
|
2791
|
+
{
|
|
2792
|
+
record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2"],
|
|
2793
|
+
tail_watcher_paths: ["#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1"],
|
|
2794
|
+
tail_watcher_inodes: [inode_0, inode_1, inode_0],
|
|
2795
|
+
tail_watcher_io_handler_opened_statuses: [false, false, false],
|
|
2796
|
+
position_entries: [
|
|
2797
|
+
# The recorded path is old, but it is no problem. The path is not used when using follow_inodes.
|
|
2798
|
+
["#{@tmp_dir}/tail.txt", "0000000000000016", inode_0],
|
|
2799
|
+
["#{@tmp_dir}/tail.txt", "0000000000000016", inode_1],
|
|
2800
|
+
],
|
|
2801
|
+
},
|
|
2802
|
+
{
|
|
2803
|
+
record_values: record_values,
|
|
2804
|
+
tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
|
|
2805
|
+
tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
|
|
2806
|
+
tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
|
|
2807
|
+
position_entries: position_entries
|
|
2808
|
+
},
|
|
2809
|
+
)
|
|
2810
|
+
end
|
|
2811
|
+
|
|
2812
|
+
# The scenario where in_tail wrongly detaches TailWatcher.
|
|
2813
|
+
# This is reported in https://github.com/fluent/fluentd/issues/4190.
|
|
2814
|
+
def test_updateTW_after_refreshTW
|
|
2815
|
+
config = config_element(
|
|
2816
|
+
"ROOT",
|
|
2817
|
+
"",
|
|
2818
|
+
{
|
|
2819
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
2820
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
2821
|
+
"tag" => "t1",
|
|
2822
|
+
"format" => "none",
|
|
2823
|
+
"read_from_head" => "true",
|
|
2824
|
+
"follow_inodes" => "true",
|
|
2825
|
+
# In order to detach the old watcher quickly.
|
|
2826
|
+
"rotate_wait" => "1s",
|
|
2827
|
+
# In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
|
|
2828
|
+
# called by a timer.
|
|
2829
|
+
"refresh_interval" => "1h",
|
|
2830
|
+
# stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
|
|
2831
|
+
# so disable it in order to reproduce the same condition stably.
|
|
2832
|
+
"enable_stat_watcher" => "false",
|
|
2833
|
+
}
|
|
2834
|
+
)
|
|
2835
|
+
d = create_driver(config, false)
|
|
2836
|
+
|
|
2837
|
+
tail_watchers = []
|
|
2838
|
+
stub.proxy(d.instance).setup_watcher do |tw|
|
|
2839
|
+
tail_watchers.append(tw)
|
|
2840
|
+
tw
|
|
2841
|
+
end
|
|
2842
|
+
|
|
2843
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file1 log1"}
|
|
2844
|
+
|
|
2845
|
+
d.run(expect_records: 4, timeout: 10) do
|
|
2846
|
+
# Rotate (If the timing is bad, `TailWatcher::on_notify` might be called between mv and new-file-creation)
|
|
2847
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file1 log2"}
|
|
2848
|
+
FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
|
|
2849
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file2 log1"}
|
|
2850
|
+
|
|
2851
|
+
# This reproduces the following situation:
|
|
2852
|
+
# Rotation => refresh_watchers => update_watcher
|
|
2853
|
+
# This add a new TailWatcher: TailWatcher(path: "tail.txt", inode: inode_1)
|
|
2854
|
+
# This overwrites `@tails["tail.txt"]`
|
|
2855
|
+
d.instance.refresh_watchers
|
|
2856
|
+
|
|
2857
|
+
# `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` trys to update the TailWatcher:
|
|
2858
|
+
# TailWatcher(path: "tail.txt", inode: inode_0) => TailWatcher(path: "tail.txt", inode: inode_1)
|
|
2859
|
+
# However, it is already added in `refresh_watcher`, so `update_watcher` doesn't create the new TailWatcher.
|
|
2860
|
+
# The old TailWathcer is detached here since `rotate_wait` is just `1s`.
|
|
2861
|
+
sleep 3
|
|
2862
|
+
|
|
2863
|
+
# This adds a new TailWatcher: TailWatcher(path: "tail.txt1", inode: inode_0)
|
|
2864
|
+
d.instance.refresh_watchers
|
|
2865
|
+
|
|
2866
|
+
# Append to the new current log file.
|
|
2867
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file2 log2"}
|
|
2868
|
+
end
|
|
2869
|
+
|
|
2870
|
+
inode_0 = tail_watchers[0].ino
|
|
2871
|
+
inode_1 = tail_watchers[1].ino
|
|
2872
|
+
record_values = d.events.collect { |event| event[2]["message"] }.sort
|
|
2873
|
+
position_entries = []
|
|
2874
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
|
|
2875
|
+
f.readlines(chomp: true).each do |line|
|
|
2876
|
+
values = line.split("\t")
|
|
2877
|
+
position_entries.append([values[0], values[1], values[2].to_i(16)])
|
|
2878
|
+
end
|
|
2879
|
+
end
|
|
2880
|
+
|
|
2881
|
+
assert_equal(
|
|
2882
|
+
{
|
|
2883
|
+
record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2"],
|
|
2884
|
+
tail_watcher_paths: ["#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1"],
|
|
2885
|
+
tail_watcher_inodes: [inode_0, inode_1, inode_0],
|
|
2886
|
+
tail_watcher_io_handler_opened_statuses: [false, false, false],
|
|
2887
|
+
position_entries: [
|
|
2888
|
+
# The recorded path is old, but it is no problem. The path is not used when using follow_inodes.
|
|
2889
|
+
["#{@tmp_dir}/tail.txt", "0000000000000016", inode_0],
|
|
2890
|
+
["#{@tmp_dir}/tail.txt", "0000000000000016", inode_1],
|
|
2891
|
+
],
|
|
2892
|
+
},
|
|
2893
|
+
{
|
|
2894
|
+
record_values: record_values,
|
|
2895
|
+
tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
|
|
2896
|
+
tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
|
|
2897
|
+
tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
|
|
2898
|
+
position_entries: position_entries
|
|
2899
|
+
},
|
|
2900
|
+
)
|
|
2901
|
+
end
|
|
2902
|
+
|
|
2903
|
+
def test_path_resurrection
|
|
2904
|
+
config = config_element(
|
|
2905
|
+
"ROOT",
|
|
2906
|
+
"",
|
|
2907
|
+
{
|
|
2908
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
2909
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
2910
|
+
"tag" => "t1",
|
|
2911
|
+
"format" => "none",
|
|
2912
|
+
"read_from_head" => "true",
|
|
2913
|
+
"follow_inodes" => "true",
|
|
2914
|
+
# In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
|
|
2915
|
+
# called by a timer.
|
|
2916
|
+
"refresh_interval" => "1h",
|
|
2917
|
+
# https://github.com/fluent/fluentd/pull/4237#issuecomment-1633358632
|
|
2918
|
+
# Because of this problem, log duplication can occur during `rotate_wait`.
|
|
2919
|
+
# Need to set `rotate_wait 0` for a workaround.
|
|
2920
|
+
"rotate_wait" => "0s",
|
|
2921
|
+
}
|
|
2922
|
+
)
|
|
2923
|
+
d = create_driver(config, false)
|
|
2924
|
+
|
|
2925
|
+
tail_watchers = []
|
|
2926
|
+
stub.proxy(d.instance).setup_watcher do |tw|
|
|
2927
|
+
tail_watchers.append(tw)
|
|
2928
|
+
tw
|
|
2929
|
+
end
|
|
2930
|
+
|
|
2931
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file1 log1"}
|
|
2932
|
+
|
|
2933
|
+
d.run(expect_records: 5, timeout: 10) do
|
|
2934
|
+
# Rotate
|
|
2935
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file1 log2"}
|
|
2936
|
+
FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
|
|
2937
|
+
# TailWatcher(path: "tail.txt", inode: inode_0) detects `tail.txt` disappeared.
|
|
2938
|
+
# Call `update_watcher` to stop and discard self.
|
|
2939
|
+
# If not discarding, then it will be a orphan and cause leak and log duplication.
|
|
2940
|
+
#
|
|
2941
|
+
# This reproduces the case where the notify to TailWatcher comes before the new file for the path
|
|
2942
|
+
# is created during rotation.
|
|
2943
|
+
# (stat_watcher notifies faster than a new file is created)
|
|
2944
|
+
# Overall, this is a rotation operation, but from the TailWatcher, it appears as if the file
|
|
2945
|
+
# was resurrected once it disappeared.
|
|
2946
|
+
sleep 2 # On Windows and macOS, StatWatcher doesn't work, so need enough interval for TimeTrigger.
|
|
2947
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file2 log1"}
|
|
2948
|
+
|
|
2949
|
+
# Add new TailWatchers
|
|
2950
|
+
# tail.txt: TailWatcher(path: "tail.txt", inode: inode_1)
|
|
2951
|
+
# tail.txt: TailWatcher(path: "tail.txt1", inode: inode_0)
|
|
2952
|
+
# NOTE: If not discarding the first TailWatcher on notify, this makes it a orphan because
|
|
2953
|
+
# this overwrites the `@tails[tail.txt]` by adding TailWatcher(path: "tail.txt", inode: inode_1)
|
|
2954
|
+
d.instance.refresh_watchers
|
|
2955
|
+
|
|
2956
|
+
# This does nothing.
|
|
2957
|
+
# NOTE: If not discarding the first TailWatcher on notify, this add
|
|
2958
|
+
# tail.txt1: TailWatcher(path: "tail.txt1", inode: inode_0)
|
|
2959
|
+
# because the previous refresh_watcher overwrites `@tails[tail.txt]` and the inode_0 is lost.
|
|
2960
|
+
# This would cause log duplication.
|
|
2961
|
+
d.instance.refresh_watchers
|
|
2962
|
+
|
|
2963
|
+
# Append to the old file
|
|
2964
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt1", "ab") {|f| f.puts "file1 log3"}
|
|
2965
|
+
|
|
2966
|
+
# Append to the new current log file.
|
|
2967
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file2 log2"}
|
|
2968
|
+
end
|
|
2969
|
+
|
|
2970
|
+
inode_0 = Fluent::FileWrapper.stat("#{@tmp_dir}/tail.txt1").ino
|
|
2971
|
+
inode_1 = Fluent::FileWrapper.stat("#{@tmp_dir}/tail.txt").ino
|
|
2972
|
+
record_values = d.events.collect { |event| event[2]["message"] }.sort
|
|
2973
|
+
position_entries = []
|
|
2974
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
|
|
2975
|
+
f.readlines(chomp: true).each do |line|
|
|
2976
|
+
values = line.split("\t")
|
|
2977
|
+
position_entries.append([values[0], values[1], values[2].to_i(16)])
|
|
2978
|
+
end
|
|
2979
|
+
end
|
|
2980
|
+
|
|
2981
|
+
assert_equal(
|
|
2982
|
+
{
|
|
2983
|
+
record_values: ["file1 log1", "file1 log2", "file1 log3", "file2 log1", "file2 log2"],
|
|
2984
|
+
tail_watcher_set: Set[
|
|
2985
|
+
{
|
|
2986
|
+
path: "#{@tmp_dir}/tail.txt",
|
|
2987
|
+
inode: inode_0,
|
|
2988
|
+
io_handler_opened_status: false,
|
|
2989
|
+
},
|
|
2990
|
+
{
|
|
2991
|
+
path: "#{@tmp_dir}/tail.txt",
|
|
2992
|
+
inode: inode_1,
|
|
2993
|
+
io_handler_opened_status: false,
|
|
2994
|
+
},
|
|
2995
|
+
{
|
|
2996
|
+
path: "#{@tmp_dir}/tail.txt1",
|
|
2997
|
+
inode: inode_0,
|
|
2998
|
+
io_handler_opened_status: false,
|
|
2999
|
+
},
|
|
3000
|
+
],
|
|
3001
|
+
position_entries: [
|
|
3002
|
+
["#{@tmp_dir}/tail.txt", "0000000000000021", inode_0],
|
|
3003
|
+
["#{@tmp_dir}/tail.txt", "0000000000000016", inode_1],
|
|
3004
|
+
],
|
|
3005
|
+
},
|
|
3006
|
+
{
|
|
3007
|
+
record_values: record_values,
|
|
3008
|
+
tail_watcher_set: Set.new(tail_watchers.collect { |tw|
|
|
3009
|
+
{
|
|
3010
|
+
path: tw.path,
|
|
3011
|
+
inode: tw.ino,
|
|
3012
|
+
io_handler_opened_status: tw.instance_variable_get(:@io_handler)&.opened? || false,
|
|
3013
|
+
}
|
|
3014
|
+
}),
|
|
3015
|
+
position_entries: position_entries,
|
|
3016
|
+
},
|
|
3017
|
+
)
|
|
3018
|
+
end
|
|
3019
|
+
|
|
3020
|
+
def test_next_rotation_occurs_very_fast_while_old_TW_still_waiting_rotate_wait
|
|
3021
|
+
config = config_element(
|
|
3022
|
+
"ROOT",
|
|
3023
|
+
"",
|
|
3024
|
+
{
|
|
3025
|
+
"path" => "#{@tmp_dir}/tail.txt*",
|
|
3026
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
3027
|
+
"tag" => "t1",
|
|
3028
|
+
"format" => "none",
|
|
3029
|
+
"read_from_head" => "true",
|
|
3030
|
+
"follow_inodes" => "true",
|
|
3031
|
+
"rotate_wait" => "3s",
|
|
3032
|
+
"refresh_interval" => "1h",
|
|
3033
|
+
# stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
|
|
3034
|
+
# so disable it in order to reproduce the same condition stably.
|
|
3035
|
+
"enable_stat_watcher" => "false",
|
|
3036
|
+
}
|
|
3037
|
+
)
|
|
3038
|
+
d = create_driver(config, false)
|
|
3039
|
+
|
|
3040
|
+
tail_watchers = []
|
|
3041
|
+
stub.proxy(d.instance).setup_watcher do |tw|
|
|
3042
|
+
tail_watchers.append(tw)
|
|
3043
|
+
mock.proxy(tw).close.once # Note: Currently, there is no harm in duplicate calls.
|
|
3044
|
+
tw
|
|
3045
|
+
end
|
|
3046
|
+
|
|
3047
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file1 log1"}
|
|
3048
|
+
|
|
3049
|
+
d.run(expect_records: 6, timeout: 15) do
|
|
3050
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file1 log2"}
|
|
3051
|
+
|
|
3052
|
+
sleep 1.5 # Need to be larger than 1s (the interval of watch_timer)
|
|
3053
|
+
|
|
3054
|
+
FileUtils.move("#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt" + "1")
|
|
3055
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file2 log1"}
|
|
3056
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file2 log2"}
|
|
3057
|
+
|
|
3058
|
+
sleep 1.5 # Need to be larger than 1s (the interval of watch_timer)
|
|
3059
|
+
|
|
3060
|
+
# Rotate again (Old TailWatcher waiting rotate_wait also calls update_watcher)
|
|
3061
|
+
[1, 0].each do |i|
|
|
3062
|
+
FileUtils.move("#{@tmp_dir}/tail.txt#{i}", "#{@tmp_dir}/tail.txt#{i + 1}")
|
|
3063
|
+
end
|
|
3064
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file3 log1"}
|
|
3065
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file3 log2"}
|
|
3066
|
+
|
|
3067
|
+
# Wait rotate_wait to confirm that TailWatcher.close is not called in duplicate.
|
|
3068
|
+
# (Note: Currently, there is no harm in duplicate calls)
|
|
3069
|
+
sleep 4
|
|
3070
|
+
end
|
|
3071
|
+
|
|
3072
|
+
inode_0 = tail_watchers[0]&.ino
|
|
3073
|
+
inode_1 = tail_watchers[1]&.ino
|
|
3074
|
+
inode_2 = tail_watchers[2]&.ino
|
|
3075
|
+
record_values = d.events.collect { |event| event[2]["message"] }.sort
|
|
3076
|
+
position_entries = []
|
|
3077
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
|
|
3078
|
+
f.readlines(chomp: true).each do |line|
|
|
3079
|
+
values = line.split("\t")
|
|
3080
|
+
position_entries.append([values[0], values[1], values[2].to_i(16)])
|
|
3081
|
+
end
|
|
3082
|
+
end
|
|
3083
|
+
|
|
3084
|
+
assert_equal(
|
|
3085
|
+
{
|
|
3086
|
+
record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2", "file3 log1", "file3 log2"],
|
|
3087
|
+
tail_watcher_paths: ["#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0"],
|
|
3088
|
+
tail_watcher_inodes: [inode_0, inode_1, inode_2],
|
|
3089
|
+
tail_watcher_io_handler_opened_statuses: [false, false, false],
|
|
3090
|
+
position_entries: [
|
|
3091
|
+
["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_0],
|
|
3092
|
+
["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_1],
|
|
3093
|
+
["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_2],
|
|
3094
|
+
],
|
|
3095
|
+
},
|
|
3096
|
+
{
|
|
3097
|
+
record_values: record_values,
|
|
3098
|
+
tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
|
|
3099
|
+
tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
|
|
3100
|
+
tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
|
|
3101
|
+
position_entries: position_entries
|
|
3102
|
+
},
|
|
3103
|
+
)
|
|
3104
|
+
end
|
|
3105
|
+
end
|
|
3106
|
+
|
|
3107
|
+
sub_test_case "Update watchers for rotation without follow_inodes" do
|
|
3108
|
+
# The scenario where in_tail wrongly unwatches the PositionEntry.
|
|
3109
|
+
# This is reported in https://github.com/fluent/fluentd/issues/3614.
|
|
3110
|
+
def test_refreshTW_during_rotation
|
|
3111
|
+
config = config_element(
|
|
3112
|
+
"ROOT",
|
|
3113
|
+
"",
|
|
3114
|
+
{
|
|
3115
|
+
"path" => "#{@tmp_dir}/tail.txt0",
|
|
3116
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
3117
|
+
"tag" => "t1",
|
|
3118
|
+
"format" => "none",
|
|
3119
|
+
"read_from_head" => "true",
|
|
3120
|
+
# In order to detach the old watcher quickly.
|
|
3121
|
+
"rotate_wait" => "3s",
|
|
3122
|
+
# In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
|
|
3123
|
+
# called by a timer.
|
|
3124
|
+
"refresh_interval" => "1h",
|
|
3125
|
+
# stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
|
|
3126
|
+
# so disable it in order to reproduce the same condition stably.
|
|
3127
|
+
"enable_stat_watcher" => "false",
|
|
3128
|
+
}
|
|
3129
|
+
)
|
|
3130
|
+
d = create_driver(config, false)
|
|
3131
|
+
|
|
3132
|
+
tail_watchers = []
|
|
3133
|
+
stub.proxy(d.instance).setup_watcher do |tw|
|
|
3134
|
+
tail_watchers.append(tw)
|
|
3135
|
+
tw
|
|
3136
|
+
end
|
|
3137
|
+
|
|
3138
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file1 log1"}
|
|
3139
|
+
|
|
3140
|
+
d.run(expect_records: 6, timeout: 15) do
|
|
3141
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file1 log2"}
|
|
3142
|
+
FileUtils.move("#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt" + "1")
|
|
3143
|
+
|
|
3144
|
+
# This reproduces the following situation:
|
|
3145
|
+
# `refresh_watchers` is called during the rotation process and it detects the current file being lost.
|
|
3146
|
+
# Then it stops and unwatches the TailWatcher.
|
|
3147
|
+
d.instance.refresh_watchers
|
|
3148
|
+
|
|
3149
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file2 log1"}
|
|
3150
|
+
|
|
3151
|
+
# `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` trys to add the new TailWatcher.
|
|
3152
|
+
# After `rotate_wait` interval, the PositionEntry is unwatched.
|
|
3153
|
+
# HOWEVER, the new TailWatcher is still using that PositionEntry, so this breaks the PositionFile!!
|
|
3154
|
+
# That PositionEntry is removed from `PositionFile::map`, but it is still working and remaining in the real pos file.
|
|
3155
|
+
sleep 5
|
|
3156
|
+
|
|
3157
|
+
# Append to the new current log file.
|
|
3158
|
+
# The PositionEntry is updated although it does not exist in `PositionFile::map`.
|
|
3159
|
+
# `PositionFile::map`: empty
|
|
3160
|
+
# Real pos file: `.../tail.txt 0000000000000016 (inode)`
|
|
3161
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file2 log2"}
|
|
3162
|
+
|
|
3163
|
+
# Rotate again
|
|
3164
|
+
[1, 0].each do |i|
|
|
3165
|
+
FileUtils.move("#{@tmp_dir}/tail.txt#{i}", "#{@tmp_dir}/tail.txt#{i + 1}")
|
|
3166
|
+
end
|
|
3167
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file3 log1"}
|
|
3168
|
+
|
|
3169
|
+
# `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` trys to update the TailWatcher.
|
|
3170
|
+
sleep 3
|
|
3171
|
+
|
|
3172
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file3 log2"}
|
|
3173
|
+
end
|
|
3174
|
+
|
|
3175
|
+
inode_0 = tail_watchers[0]&.ino
|
|
3176
|
+
inode_1 = tail_watchers[1]&.ino
|
|
3177
|
+
inode_2 = tail_watchers[2]&.ino
|
|
3178
|
+
record_values = d.events.collect { |event| event[2]["message"] }.sort
|
|
3179
|
+
position_entries = []
|
|
3180
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
|
|
3181
|
+
f.readlines(chomp: true).each do |line|
|
|
3182
|
+
values = line.split("\t")
|
|
3183
|
+
position_entries.append([values[0], values[1], values[2].to_i(16)])
|
|
3184
|
+
end
|
|
3185
|
+
end
|
|
3186
|
+
|
|
3187
|
+
assert_equal(
|
|
3188
|
+
{
|
|
3189
|
+
record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2", "file3 log1", "file3 log2"],
|
|
3190
|
+
tail_watcher_paths: ["#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0"],
|
|
3191
|
+
tail_watcher_inodes: [inode_0, inode_1, inode_2],
|
|
3192
|
+
tail_watcher_io_handler_opened_statuses: [false, false, false],
|
|
3193
|
+
position_entries: [
|
|
3194
|
+
# The recorded path is old, but it is no problem. The path is not used when using follow_inodes.
|
|
3195
|
+
["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_2],
|
|
3196
|
+
],
|
|
3197
|
+
},
|
|
3198
|
+
{
|
|
3199
|
+
record_values: record_values,
|
|
3200
|
+
tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
|
|
3201
|
+
tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
|
|
3202
|
+
tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
|
|
3203
|
+
position_entries: position_entries
|
|
3204
|
+
},
|
|
3205
|
+
)
|
|
3206
|
+
end
|
|
3207
|
+
|
|
3208
|
+
def test_next_rotation_occurs_very_fast_while_old_TW_still_waiting_rotate_wait
|
|
3209
|
+
config = config_element(
|
|
3210
|
+
"ROOT",
|
|
3211
|
+
"",
|
|
3212
|
+
{
|
|
3213
|
+
"path" => "#{@tmp_dir}/tail.txt0",
|
|
3214
|
+
"pos_file" => "#{@tmp_dir}/tail.pos",
|
|
3215
|
+
"tag" => "t1",
|
|
3216
|
+
"format" => "none",
|
|
3217
|
+
"read_from_head" => "true",
|
|
3218
|
+
"rotate_wait" => "3s",
|
|
3219
|
+
"refresh_interval" => "1h",
|
|
3220
|
+
}
|
|
3221
|
+
)
|
|
3222
|
+
d = create_driver(config, false)
|
|
3223
|
+
|
|
3224
|
+
tail_watchers = []
|
|
3225
|
+
stub.proxy(d.instance).setup_watcher do |tw|
|
|
3226
|
+
tail_watchers.append(tw)
|
|
3227
|
+
mock.proxy(tw).close.once # Note: Currently, there is no harm in duplicate calls.
|
|
3228
|
+
tw
|
|
3229
|
+
end
|
|
3230
|
+
|
|
3231
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file1 log1"}
|
|
3232
|
+
|
|
3233
|
+
d.run(expect_records: 6, timeout: 15) do
|
|
3234
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file1 log2"}
|
|
3235
|
+
|
|
3236
|
+
sleep 1.5 # Need to be larger than 1s (the interval of watch_timer)
|
|
3237
|
+
|
|
3238
|
+
FileUtils.move("#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt" + "1")
|
|
3239
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file2 log1"}
|
|
3240
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file2 log2"}
|
|
3241
|
+
|
|
3242
|
+
sleep 1.5 # Need to be larger than 1s (the interval of watch_timer)
|
|
3243
|
+
|
|
3244
|
+
# Rotate again (Old TailWatcher waiting rotate_wait also calls update_watcher)
|
|
3245
|
+
[1, 0].each do |i|
|
|
3246
|
+
FileUtils.move("#{@tmp_dir}/tail.txt#{i}", "#{@tmp_dir}/tail.txt#{i + 1}")
|
|
3247
|
+
end
|
|
3248
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file3 log1"}
|
|
3249
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file3 log2"}
|
|
3250
|
+
|
|
3251
|
+
# Wait rotate_wait to confirm that TailWatcher.close is not called in duplicate.
|
|
3252
|
+
# (Note: Currently, there is no harm in duplicate calls)
|
|
3253
|
+
sleep 4
|
|
3254
|
+
end
|
|
3255
|
+
|
|
3256
|
+
inode_0 = tail_watchers[0]&.ino
|
|
3257
|
+
inode_1 = tail_watchers[1]&.ino
|
|
3258
|
+
inode_2 = tail_watchers[2]&.ino
|
|
3259
|
+
record_values = d.events.collect { |event| event[2]["message"] }.sort
|
|
3260
|
+
position_entries = []
|
|
3261
|
+
Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
|
|
3262
|
+
f.readlines(chomp: true).each do |line|
|
|
3263
|
+
values = line.split("\t")
|
|
3264
|
+
position_entries.append([values[0], values[1], values[2].to_i(16)])
|
|
3265
|
+
end
|
|
3266
|
+
end
|
|
3267
|
+
|
|
3268
|
+
assert_equal(
|
|
3269
|
+
{
|
|
3270
|
+
record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2", "file3 log1", "file3 log2"],
|
|
3271
|
+
tail_watcher_paths: ["#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0"],
|
|
3272
|
+
tail_watcher_inodes: [inode_0, inode_1, inode_2],
|
|
3273
|
+
tail_watcher_io_handler_opened_statuses: [false, false, false],
|
|
3274
|
+
position_entries: [
|
|
3275
|
+
["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_2],
|
|
3276
|
+
],
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
record_values: record_values,
|
|
3280
|
+
tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
|
|
3281
|
+
tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
|
|
3282
|
+
tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
|
|
3283
|
+
position_entries: position_entries
|
|
3284
|
+
},
|
|
3285
|
+
)
|
|
3286
|
+
end
|
|
2279
3287
|
end
|
|
2280
3288
|
end
|