fluentd 1.11.3-x86-mingw32 → 1.12.2-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.deepsource.toml +13 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.github/workflows/linux-test.yaml +36 -0
- data/.github/workflows/macos-test.yaml +30 -0
- data/.github/workflows/stale-actions.yml +22 -0
- data/.github/workflows/windows-test.yaml +30 -0
- data/CHANGELOG.md +138 -0
- data/MAINTAINERS.md +5 -2
- data/README.md +2 -2
- data/bin/fluent-cap-ctl +7 -0
- data/bin/fluent-ctl +7 -0
- data/fluentd.gemspec +4 -3
- data/lib/fluent/capability.rb +87 -0
- data/lib/fluent/command/bundler_injection.rb +1 -1
- data/lib/fluent/command/ca_generate.rb +6 -3
- data/lib/fluent/command/cap_ctl.rb +174 -0
- data/lib/fluent/command/cat.rb +0 -1
- data/lib/fluent/command/ctl.rb +177 -0
- data/lib/fluent/command/fluentd.rb +4 -0
- data/lib/fluent/command/plugin_config_formatter.rb +18 -2
- data/lib/fluent/compat/parser.rb +2 -2
- data/lib/fluent/config/section.rb +2 -2
- data/lib/fluent/config/types.rb +2 -2
- data/lib/fluent/env.rb +4 -0
- data/lib/fluent/event.rb +3 -13
- data/lib/fluent/load.rb +0 -1
- data/lib/fluent/plugin.rb +5 -0
- data/lib/fluent/plugin/buffer.rb +2 -21
- data/lib/fluent/plugin/formatter.rb +24 -0
- data/lib/fluent/plugin/formatter_csv.rb +1 -1
- data/lib/fluent/plugin/formatter_hash.rb +3 -1
- data/lib/fluent/plugin/formatter_json.rb +3 -1
- data/lib/fluent/plugin/formatter_ltsv.rb +7 -5
- data/lib/fluent/plugin/formatter_out_file.rb +6 -4
- data/lib/fluent/plugin/formatter_single_value.rb +4 -2
- data/lib/fluent/plugin/formatter_tsv.rb +4 -2
- data/lib/fluent/plugin/in_http.rb +24 -3
- data/lib/fluent/plugin/in_monitor_agent.rb +1 -1
- data/lib/fluent/plugin/in_tail.rb +128 -41
- data/lib/fluent/plugin/in_tail/position_file.rb +39 -14
- data/lib/fluent/plugin/in_tcp.rb +1 -0
- data/lib/fluent/plugin/out_copy.rb +18 -5
- data/lib/fluent/plugin/out_exec_filter.rb +3 -3
- data/lib/fluent/plugin/out_forward.rb +61 -28
- data/lib/fluent/plugin/out_http.rb +29 -4
- data/lib/fluent/plugin/output.rb +14 -6
- data/lib/fluent/plugin/storage_local.rb +3 -3
- data/lib/fluent/plugin_helper/http_server/compat/server.rb +1 -1
- data/lib/fluent/plugin_helper/inject.rb +4 -1
- data/lib/fluent/plugin_helper/retry_state.rb +4 -0
- data/lib/fluent/supervisor.rb +153 -48
- data/lib/fluent/system_config.rb +2 -1
- data/lib/fluent/time.rb +58 -1
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +22 -4
- data/templates/plugin_config_formatter/param.md-table.erb +10 -0
- data/test/command/test_binlog_reader.rb +22 -6
- data/test/command/test_cap_ctl.rb +100 -0
- data/test/command/test_ctl.rb +57 -0
- data/test/command/test_fluentd.rb +38 -0
- data/test/command/test_plugin_config_formatter.rb +124 -2
- data/test/config/test_configurable.rb +1 -1
- data/test/plugin/in_tail/test_position_file.rb +46 -26
- data/test/plugin/out_forward/test_connection_manager.rb +6 -0
- data/test/plugin/test_filter_stdout.rb +6 -1
- data/test/plugin/test_formatter_hash.rb +6 -3
- data/test/plugin/test_formatter_json.rb +14 -4
- data/test/plugin/test_formatter_ltsv.rb +13 -5
- data/test/plugin/test_formatter_out_file.rb +35 -14
- data/test/plugin/test_formatter_single_value.rb +12 -6
- data/test/plugin/test_formatter_tsv.rb +12 -4
- data/test/plugin/test_in_exec.rb +1 -1
- data/test/plugin/test_in_http.rb +25 -0
- data/test/plugin/test_in_tail.rb +470 -32
- data/test/plugin/test_out_copy.rb +87 -0
- data/test/plugin/test_out_file.rb +23 -18
- data/test/plugin/test_out_forward.rb +74 -0
- data/test/plugin/test_out_http.rb +20 -1
- data/test/plugin/test_output.rb +12 -0
- data/test/plugin/test_parser_syslog.rb +2 -2
- data/test/plugin/test_sd_file.rb +1 -1
- data/test/plugin_helper/test_child_process.rb +5 -2
- data/test/plugin_helper/test_compat_parameters.rb +7 -2
- data/test/plugin_helper/test_http_server_helper.rb +3 -1
- data/test/plugin_helper/test_inject.rb +42 -0
- data/test/plugin_helper/test_server.rb +18 -5
- data/test/test_capability.rb +74 -0
- data/test/test_event.rb +16 -0
- data/test/test_formatter.rb +64 -10
- data/test/test_output.rb +6 -1
- data/test/test_supervisor.rb +150 -1
- data/test/test_time_parser.rb +109 -0
- metadata +61 -29
- data/.travis.yml +0 -57
- data/appveyor.yml +0 -28
@@ -37,13 +37,17 @@ class TSVFormatterTest < ::Test::Unit::TestCase
|
|
37
37
|
assert_equal false, d.instance.add_newline
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
data("newline (LF)" => ["lf", "\n"],
|
41
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
42
|
+
def test_format(data)
|
43
|
+
newline_conf, newline = data
|
41
44
|
d = create_driver(
|
42
45
|
'keys' => 'message,greeting',
|
46
|
+
'newline' => newline_conf
|
43
47
|
)
|
44
48
|
formatted = d.instance.format(tag, @time, record)
|
45
49
|
|
46
|
-
assert_equal("awesome\thello
|
50
|
+
assert_equal("awesome\thello#{newline}", formatted)
|
47
51
|
end
|
48
52
|
|
49
53
|
def test_format_without_newline
|
@@ -56,13 +60,17 @@ class TSVFormatterTest < ::Test::Unit::TestCase
|
|
56
60
|
assert_equal("awesome\thello", formatted)
|
57
61
|
end
|
58
62
|
|
59
|
-
|
63
|
+
data("newline (LF)" => ["lf", "\n"],
|
64
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
65
|
+
def test_format_with_customized_delimiters(data)
|
66
|
+
newline_conf, newline = data
|
60
67
|
d = create_driver(
|
61
68
|
'keys' => 'message,greeting',
|
62
69
|
'delimiter' => ',',
|
70
|
+
'newline' => newline_conf,
|
63
71
|
)
|
64
72
|
formatted = d.instance.format(tag, @time, record)
|
65
73
|
|
66
|
-
assert_equal("awesome,hello
|
74
|
+
assert_equal("awesome,hello#{newline}", formatted)
|
67
75
|
end
|
68
76
|
end
|
data/test/plugin/test_in_exec.rb
CHANGED
data/test/plugin/test_in_http.rb
CHANGED
@@ -46,6 +46,7 @@ class HttpInputTest < Test::Unit::TestCase
|
|
46
46
|
assert_equal 10*1024*1024, d.instance.body_size_limit
|
47
47
|
assert_equal 5, d.instance.keepalive_timeout
|
48
48
|
assert_equal false, d.instance.add_http_headers
|
49
|
+
assert_equal false, d.instance.add_query_params
|
49
50
|
end
|
50
51
|
|
51
52
|
def test_time
|
@@ -907,6 +908,30 @@ class HttpInputTest < Test::Unit::TestCase
|
|
907
908
|
assert_equal ["403", "403"], res_codes
|
908
909
|
end
|
909
910
|
|
911
|
+
def test_add_query_params
|
912
|
+
d = create_driver(CONFIG + "add_query_params true")
|
913
|
+
assert_equal true, d.instance.add_query_params
|
914
|
+
|
915
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
916
|
+
time_i = time.to_i
|
917
|
+
events = [
|
918
|
+
["tag1", time, {"a"=>1, "QUERY_A"=>"b"}],
|
919
|
+
["tag2", time, {"a"=>2, "QUERY_A"=>"b"}],
|
920
|
+
]
|
921
|
+
res_codes = []
|
922
|
+
res_bodies = []
|
923
|
+
|
924
|
+
d.run do
|
925
|
+
events.each do |tag, _t, record|
|
926
|
+
res = post("/#{tag}?a=b", {"json"=>record.to_json, "time"=>time_i.to_s})
|
927
|
+
res_codes << res.code
|
928
|
+
end
|
929
|
+
end
|
930
|
+
assert_equal ["200", "200"], res_codes
|
931
|
+
assert_equal [], res_bodies
|
932
|
+
assert_equal events, d.events
|
933
|
+
end
|
934
|
+
|
910
935
|
$test_in_http_connection_object_ids = []
|
911
936
|
$test_in_http_content_types = []
|
912
937
|
$test_in_http_content_types_flag = false
|
data/test/plugin/test_in_tail.rb
CHANGED
@@ -6,6 +6,8 @@ require 'fluent/system_config'
|
|
6
6
|
require 'net/http'
|
7
7
|
require 'flexmock/test_unit'
|
8
8
|
require 'timecop'
|
9
|
+
require 'tmpdir'
|
10
|
+
require 'securerandom'
|
9
11
|
|
10
12
|
class TailInputTest < Test::Unit::TestCase
|
11
13
|
include FlexMock::TestCase
|
@@ -17,29 +19,98 @@ class TailInputTest < Test::Unit::TestCase
|
|
17
19
|
|
18
20
|
def teardown
|
19
21
|
super
|
22
|
+
cleanup_directory(TMP_DIR)
|
20
23
|
Fluent::Engine.stop
|
21
24
|
end
|
22
25
|
|
23
26
|
def cleanup_directory(path)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
unless Dir.exist?(path)
|
28
|
+
FileUtils.mkdir_p(path)
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
if Fluent.windows?
|
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
|
27
49
|
end
|
28
50
|
FileUtils.mkdir_p(path)
|
29
51
|
end
|
30
52
|
|
53
|
+
def cleanup_file(path)
|
54
|
+
if Fluent.windows?
|
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
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_target_info(path)
|
89
|
+
Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
|
90
|
+
end
|
91
|
+
|
31
92
|
TMP_DIR = File.dirname(__FILE__) + "/../tmp/tail#{ENV['TEST_ENV_NUMBER']}"
|
32
93
|
|
33
94
|
CONFIG = config_element("ROOT", "", {
|
34
95
|
"path" => "#{TMP_DIR}/tail.txt",
|
35
96
|
"tag" => "t1",
|
36
|
-
"rotate_wait" => "2s"
|
97
|
+
"rotate_wait" => "2s",
|
98
|
+
"refresh_interval" => "1s"
|
37
99
|
})
|
38
100
|
COMMON_CONFIG = CONFIG + config_element("", "", { "pos_file" => "#{TMP_DIR}/tail.pos" })
|
39
101
|
CONFIG_READ_FROM_HEAD = config_element("", "", { "read_from_head" => true })
|
40
102
|
CONFIG_ENABLE_WATCH_TIMER = config_element("", "", { "enable_watch_timer" => false })
|
41
103
|
CONFIG_DISABLE_STAT_WATCHER = config_element("", "", { "enable_stat_watcher" => false })
|
42
104
|
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
|
+
"follow_inodes" => "true"
|
113
|
+
})
|
43
114
|
SINGLE_LINE_CONFIG = config_element("", "", { "format" => "/(?<message>.*)/" })
|
44
115
|
PARSE_SINGLE_LINE_CONFIG = config_element("", "", {}, [config_element("parse", "", { "@type" => "/(?<message>.*)/" })])
|
45
116
|
MULTILINE_CONFIG = config_element(
|
@@ -86,6 +157,9 @@ class TailInputTest < Test::Unit::TestCase
|
|
86
157
|
assert_equal "#{TMP_DIR}/tail.pos", d.instance.pos_file
|
87
158
|
assert_equal 1000, d.instance.read_lines_limit
|
88
159
|
assert_equal false, d.instance.ignore_repeated_permission_error
|
160
|
+
assert_nothing_raised do
|
161
|
+
d.instance.have_read_capability?
|
162
|
+
end
|
89
163
|
end
|
90
164
|
|
91
165
|
data("empty" => config_element,
|
@@ -115,6 +189,12 @@ class TailInputTest < Test::Unit::TestCase
|
|
115
189
|
end
|
116
190
|
end
|
117
191
|
|
192
|
+
test "follow_inodes w/o pos file" do
|
193
|
+
assert_raise(Fluent::ConfigError) do
|
194
|
+
create_driver(CONFIG + config_element('', '', {'follow_inodes' => 'true'}))
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
118
198
|
test "both enable_watch_timer and enable_stat_watcher are false" do
|
119
199
|
assert_raise(Fluent::ConfigError) do
|
120
200
|
create_driver(CONFIG_ENABLE_WATCH_TIMER + CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
|
@@ -229,7 +309,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
229
309
|
d = create_driver(config)
|
230
310
|
msg = 'test' * 2000 # in_tail reads 8192 bytes at once.
|
231
311
|
|
232
|
-
d.run(expect_emits: num_events, timeout:
|
312
|
+
d.run(expect_emits: num_events, timeout: 2) do
|
233
313
|
File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
|
234
314
|
f.puts msg
|
235
315
|
f.puts msg
|
@@ -999,6 +1079,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
999
1079
|
# * path test
|
1000
1080
|
# TODO: Clean up tests
|
1001
1081
|
EX_ROTATE_WAIT = 0
|
1082
|
+
EX_FOLLOW_INODES = false
|
1002
1083
|
|
1003
1084
|
EX_CONFIG = config_element("", "", {
|
1004
1085
|
"tag" => "tail",
|
@@ -1008,38 +1089,43 @@ class TailInputTest < Test::Unit::TestCase
|
|
1008
1089
|
"read_from_head" => true,
|
1009
1090
|
"refresh_interval" => 30,
|
1010
1091
|
"rotate_wait" => "#{EX_ROTATE_WAIT}s",
|
1092
|
+
"follow_inodes" => "#{EX_FOLLOW_INODES}",
|
1011
1093
|
})
|
1012
|
-
EX_PATHS = [
|
1013
|
-
'test/plugin/data/2010/01/20100102-030405.log',
|
1014
|
-
'test/plugin/data/log/foo/bar.log',
|
1015
|
-
'test/plugin/data/log/test.log'
|
1016
|
-
]
|
1017
|
-
|
1018
1094
|
def test_expand_paths
|
1095
|
+
ex_paths = [
|
1096
|
+
create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
|
1097
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1098
|
+
create_target_info('test/plugin/data/log/test.log')
|
1099
|
+
]
|
1019
1100
|
plugin = create_driver(EX_CONFIG, false).instance
|
1020
1101
|
flexstub(Time) do |timeclass|
|
1021
1102
|
timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 2, 3, 4, 5))
|
1022
|
-
assert_equal
|
1103
|
+
assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1023
1104
|
end
|
1024
1105
|
|
1025
1106
|
# Test exclusion
|
1026
|
-
exclude_config = EX_CONFIG + config_element("", "", { "exclude_path" => %Q(["#{
|
1107
|
+
exclude_config = EX_CONFIG + config_element("", "", { "exclude_path" => %Q(["#{ex_paths.last.path}"]) })
|
1027
1108
|
plugin = create_driver(exclude_config, false).instance
|
1028
|
-
assert_equal
|
1109
|
+
assert_equal ex_paths - [ex_paths.last], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1029
1110
|
end
|
1030
1111
|
|
1031
1112
|
def test_expand_paths_with_duplicate_configuration
|
1032
1113
|
expanded_paths = [
|
1033
|
-
'test/plugin/data/log/foo/bar.log',
|
1034
|
-
'test/plugin/data/log/test.log'
|
1114
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1115
|
+
create_target_info('test/plugin/data/log/test.log')
|
1035
1116
|
]
|
1036
1117
|
duplicate_config = EX_CONFIG.dup
|
1037
1118
|
duplicate_config["path"]="test/plugin/data/log/**/*.log, test/plugin/data/log/**/*.log"
|
1038
1119
|
plugin = create_driver(EX_CONFIG, false).instance
|
1039
|
-
assert_equal expanded_paths, plugin.expand_paths.
|
1120
|
+
assert_equal expanded_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1040
1121
|
end
|
1041
1122
|
|
1042
1123
|
def test_expand_paths_with_timezone
|
1124
|
+
ex_paths = [
|
1125
|
+
create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
|
1126
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1127
|
+
create_target_info('test/plugin/data/log/test.log')
|
1128
|
+
]
|
1043
1129
|
['Asia/Taipei', '+08'].each do |tz_type|
|
1044
1130
|
taipei_config = EX_CONFIG + config_element("", "", {"path_timezone" => tz_type})
|
1045
1131
|
plugin = create_driver(taipei_config, false).instance
|
@@ -1053,8 +1139,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
1053
1139
|
# env : 2010-01-01 19:04:05 (UTC), tail path : 2010-01-02 03:04:05 (Asia/Taipei)
|
1054
1140
|
timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 1, 19, 4, 5))
|
1055
1141
|
|
1056
|
-
assert_equal
|
1057
|
-
assert_equal
|
1142
|
+
assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1143
|
+
assert_equal ex_paths - [ex_paths.first], exclude_plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1058
1144
|
end
|
1059
1145
|
end
|
1060
1146
|
end
|
@@ -1062,10 +1148,10 @@ class TailInputTest < Test::Unit::TestCase
|
|
1062
1148
|
|
1063
1149
|
def test_log_file_without_extension
|
1064
1150
|
expected_files = [
|
1065
|
-
'test/plugin/data/log/bar',
|
1066
|
-
'test/plugin/data/log/foo/bar.log',
|
1067
|
-
'test/plugin/data/log/foo/bar2',
|
1068
|
-
'test/plugin/data/log/test.log'
|
1151
|
+
create_target_info('test/plugin/data/log/bar'),
|
1152
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1153
|
+
create_target_info('test/plugin/data/log/foo/bar2'),
|
1154
|
+
create_target_info('test/plugin/data/log/test.log')
|
1069
1155
|
]
|
1070
1156
|
|
1071
1157
|
config = config_element("", "", {
|
@@ -1076,7 +1162,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
1076
1162
|
})
|
1077
1163
|
|
1078
1164
|
plugin = create_driver(config, false).instance
|
1079
|
-
assert_equal expected_files, plugin.expand_paths.
|
1165
|
+
assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1080
1166
|
end
|
1081
1167
|
|
1082
1168
|
def test_unwatched_files_should_be_removed
|
@@ -1112,6 +1198,49 @@ class TailInputTest < Test::Unit::TestCase
|
|
1112
1198
|
end
|
1113
1199
|
end
|
1114
1200
|
|
1201
|
+
sub_test_case "path w/ Linux capability" do
|
1202
|
+
def capability_enabled?
|
1203
|
+
if Fluent.linux?
|
1204
|
+
begin
|
1205
|
+
require 'capng'
|
1206
|
+
true
|
1207
|
+
rescue LoadError
|
1208
|
+
false
|
1209
|
+
end
|
1210
|
+
else
|
1211
|
+
false
|
1212
|
+
end
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
setup do
|
1216
|
+
omit "This environment is not enabled Linux capability handling feature" unless capability_enabled?
|
1217
|
+
|
1218
|
+
@capng = CapNG.new(:current_process)
|
1219
|
+
flexstub(Fluent::Capability) do |klass|
|
1220
|
+
klass.should_receive(:new).with(:current_process).and_return(@capng)
|
1221
|
+
end
|
1222
|
+
end
|
1223
|
+
|
1224
|
+
data("dac_read_search" => [:dac_read_search, true, 1],
|
1225
|
+
"dac_override" => [:dac_override, true, 1],
|
1226
|
+
"chown" => [:chown, false, 0],
|
1227
|
+
)
|
1228
|
+
test "with partially elevated privileges" do |data|
|
1229
|
+
cap, result, readable_paths = data
|
1230
|
+
@capng.update(:add, :effective, cap)
|
1231
|
+
|
1232
|
+
d = create_driver(
|
1233
|
+
config_element("ROOT", "", {
|
1234
|
+
"path" => "/var/log/ker*.log", # Use /var/log/kern.log
|
1235
|
+
"tag" => "t1",
|
1236
|
+
"rotate_wait" => "2s"
|
1237
|
+
}) + PARSE_SINGLE_LINE_CONFIG, false)
|
1238
|
+
|
1239
|
+
assert_equal readable_paths, d.instance.expand_paths.length
|
1240
|
+
assert_equal result, d.instance.have_read_capability?
|
1241
|
+
end
|
1242
|
+
end
|
1243
|
+
|
1115
1244
|
def test_pos_file_dir_creation
|
1116
1245
|
config = config_element("", "", {
|
1117
1246
|
"tag" => "tail",
|
@@ -1159,25 +1288,35 @@ class TailInputTest < Test::Unit::TestCase
|
|
1159
1288
|
end
|
1160
1289
|
|
1161
1290
|
def test_z_refresh_watchers
|
1291
|
+
ex_paths = [
|
1292
|
+
create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
|
1293
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1294
|
+
create_target_info('test/plugin/data/log/test.log'),
|
1295
|
+
]
|
1162
1296
|
plugin = create_driver(EX_CONFIG, false).instance
|
1163
1297
|
sio = StringIO.new
|
1164
1298
|
plugin.instance_eval do
|
1165
|
-
@pf = Fluent::Plugin::TailInput::PositionFile.load(sio, logger: $log)
|
1299
|
+
@pf = Fluent::Plugin::TailInput::PositionFile.load(sio, EX_FOLLOW_INODES, {}, logger: $log)
|
1166
1300
|
@loop = Coolio::Loop.new
|
1167
1301
|
end
|
1168
1302
|
|
1169
1303
|
Timecop.freeze(2010, 1, 2, 3, 4, 5) do
|
1170
|
-
|
1171
|
-
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(
|
1304
|
+
ex_paths.each do |target_info|
|
1305
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
|
1172
1306
|
end
|
1173
1307
|
|
1174
1308
|
plugin.refresh_watchers
|
1175
1309
|
end
|
1176
1310
|
|
1177
|
-
|
1311
|
+
path = 'test/plugin/data/2010/01/20100102-030405.log'
|
1312
|
+
target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
|
1313
|
+
mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)[target_info], target_info.ino)
|
1178
1314
|
|
1179
1315
|
Timecop.freeze(2010, 1, 2, 3, 4, 6) do
|
1180
|
-
|
1316
|
+
path = "test/plugin/data/2010/01/20100102-030406.log"
|
1317
|
+
inode = Fluent::FileWrapper.stat(path).ino
|
1318
|
+
target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, inode)
|
1319
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
|
1181
1320
|
plugin.refresh_watchers
|
1182
1321
|
|
1183
1322
|
flexstub(Fluent::Plugin::TailInput::TailWatcher) do |watcherclass|
|
@@ -1323,6 +1462,305 @@ class TailInputTest < Test::Unit::TestCase
|
|
1323
1462
|
end
|
1324
1463
|
end
|
1325
1464
|
|
1465
|
+
sub_test_case 'inode_processing' do
|
1466
|
+
def test_should_delete_file_pos_entry_for_non_existing_file_with_follow_inodes
|
1467
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1468
|
+
|
1469
|
+
path = "#{TMP_DIR}/tail.txt"
|
1470
|
+
ino = 1
|
1471
|
+
pos = 1234
|
1472
|
+
File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
|
1473
|
+
f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
|
1474
|
+
}
|
1475
|
+
|
1476
|
+
d = create_driver(config, false)
|
1477
|
+
d.run
|
1478
|
+
|
1479
|
+
pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
|
1480
|
+
pos_file.pos = 0
|
1481
|
+
|
1482
|
+
assert_raise(EOFError) do
|
1483
|
+
pos_file.readline
|
1484
|
+
end
|
1485
|
+
end
|
1486
|
+
|
1487
|
+
def test_should_write_latest_offset_after_rotate_wait
|
1488
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1489
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1490
|
+
f.puts "test1"
|
1491
|
+
f.puts "test2"
|
1492
|
+
}
|
1493
|
+
|
1494
|
+
d = create_driver(config, false)
|
1495
|
+
d.run(expect_emits: 2, shutdown: false) do
|
1496
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1497
|
+
FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt" + "1")
|
1498
|
+
sleep 1
|
1499
|
+
File.open("#{TMP_DIR}/tail.txt" + "1", "ab") {|f| f.puts "test4\n"}
|
1500
|
+
end
|
1501
|
+
|
1502
|
+
pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
|
1503
|
+
pos_file.pos = 0
|
1504
|
+
line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
1505
|
+
waiting(5) {
|
1506
|
+
while line_parts[2].to_i(16) != 24
|
1507
|
+
sleep(0.1)
|
1508
|
+
pos_file.pos = 0
|
1509
|
+
line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
1510
|
+
end
|
1511
|
+
}
|
1512
|
+
assert_equal(24, line_parts[2].to_i(16))
|
1513
|
+
d.instance_shutdown
|
1514
|
+
end
|
1515
|
+
|
1516
|
+
def test_should_keep_and_update_existing_file_pos_entry_for_deleted_file_when_new_file_with_same_name_created
|
1517
|
+
config = config_element("", "", {"format" => "none"})
|
1518
|
+
|
1519
|
+
path = "#{TMP_DIR}/tail.txt"
|
1520
|
+
ino = 1
|
1521
|
+
pos = 1234
|
1522
|
+
File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
|
1523
|
+
f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
|
1524
|
+
}
|
1525
|
+
|
1526
|
+
d = create_driver(config)
|
1527
|
+
d.run(shutdown: false)
|
1528
|
+
|
1529
|
+
pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
|
1530
|
+
pos_file.pos = 0
|
1531
|
+
|
1532
|
+
path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
1533
|
+
assert_equal(path, path_pos_ino[1])
|
1534
|
+
assert_equal(pos, path_pos_ino[2].to_i(16))
|
1535
|
+
assert_equal(ino, path_pos_ino[3].to_i(16))
|
1536
|
+
|
1537
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1538
|
+
f.puts "test1"
|
1539
|
+
f.puts "test2"
|
1540
|
+
}
|
1541
|
+
Timecop.travel(Time.now + 10) do
|
1542
|
+
sleep 5
|
1543
|
+
pos_file.pos = 0
|
1544
|
+
tuple = create_target_info("#{TMP_DIR}/tail.txt")
|
1545
|
+
path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
1546
|
+
assert_equal(tuple.path, path_pos_ino[1])
|
1547
|
+
assert_equal(12, path_pos_ino[2].to_i(16))
|
1548
|
+
assert_equal(tuple.ino, path_pos_ino[3].to_i(16))
|
1549
|
+
end
|
1550
|
+
d.instance_shutdown
|
1551
|
+
end
|
1552
|
+
|
1553
|
+
def test_should_mark_file_unwatched_after_limit_recently_modified_and_rotate_wait
|
1554
|
+
config = config_element("ROOT", "", {
|
1555
|
+
"path" => "#{TMP_DIR}/tail.txt*",
|
1556
|
+
"pos_file" => "#{TMP_DIR}/tail.pos",
|
1557
|
+
"tag" => "t1",
|
1558
|
+
"rotate_wait" => "1s",
|
1559
|
+
"refresh_interval" => "1s",
|
1560
|
+
"limit_recently_modified" => "1s",
|
1561
|
+
"read_from_head" => "true",
|
1562
|
+
"format" => "none",
|
1563
|
+
"follow_inodes" => "true",
|
1564
|
+
})
|
1565
|
+
|
1566
|
+
d = create_driver(config, false)
|
1567
|
+
|
1568
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1569
|
+
f.puts "test1"
|
1570
|
+
f.puts "test2"
|
1571
|
+
}
|
1572
|
+
target_info = create_target_info("#{TMP_DIR}/tail.txt")
|
1573
|
+
|
1574
|
+
d.run(expect_emits: 1, shutdown: false) do
|
1575
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1576
|
+
end
|
1577
|
+
|
1578
|
+
|
1579
|
+
Timecop.travel(Time.now + 10) do
|
1580
|
+
waiting(5) {
|
1581
|
+
# @pos will be reset as 0 when UNWATCHED_POSITION is specified.
|
1582
|
+
sleep 0.1 until d.instance.instance_variable_get(:@pf)[target_info].read_pos == 0
|
1583
|
+
}
|
1584
|
+
end
|
1585
|
+
|
1586
|
+
assert_equal(0, d.instance.instance_variable_get(:@pf)[target_info].read_pos)
|
1587
|
+
|
1588
|
+
d.instance_shutdown
|
1589
|
+
end
|
1590
|
+
|
1591
|
+
def test_should_read_from_head_on_file_renaming_with_star_in_pattern
|
1592
|
+
config = config_element("ROOT", "", {
|
1593
|
+
"path" => "#{TMP_DIR}/tail.txt*",
|
1594
|
+
"pos_file" => "#{TMP_DIR}/tail.pos",
|
1595
|
+
"tag" => "t1",
|
1596
|
+
"rotate_wait" => "10s",
|
1597
|
+
"refresh_interval" => "1s",
|
1598
|
+
"limit_recently_modified" => "60s",
|
1599
|
+
"read_from_head" => "true",
|
1600
|
+
"format" => "none",
|
1601
|
+
"follow_inodes" => "true"
|
1602
|
+
})
|
1603
|
+
|
1604
|
+
d = create_driver(config, false)
|
1605
|
+
|
1606
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1607
|
+
f.puts "test1"
|
1608
|
+
f.puts "test2"
|
1609
|
+
}
|
1610
|
+
|
1611
|
+
d.run(expect_emits: 2, shutdown: false) do
|
1612
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1613
|
+
FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
|
1614
|
+
end
|
1615
|
+
|
1616
|
+
events = d.events
|
1617
|
+
assert_equal(3, events.length)
|
1618
|
+
d.instance_shutdown
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
def test_should_not_read_from_head_on_rotation_when_watching_inodes
|
1622
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1623
|
+
|
1624
|
+
d = create_driver(config, false)
|
1625
|
+
|
1626
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1627
|
+
f.puts "test1"
|
1628
|
+
f.puts "test2"
|
1629
|
+
}
|
1630
|
+
|
1631
|
+
d.run(expect_emits: 1, shutdown: false) do
|
1632
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
|
1636
|
+
Timecop.travel(Time.now + 10) do
|
1637
|
+
sleep 2
|
1638
|
+
events = d.events
|
1639
|
+
assert_equal(3, events.length)
|
1640
|
+
end
|
1641
|
+
|
1642
|
+
d.instance_shutdown
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
def test_should_mark_file_unwatched_if_same_name_file_created_with_different_inode
|
1646
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1647
|
+
|
1648
|
+
d = create_driver(config, false)
|
1649
|
+
|
1650
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1651
|
+
f.puts "test1"
|
1652
|
+
f.puts "test2"
|
1653
|
+
}
|
1654
|
+
target_info = create_target_info("#{TMP_DIR}/tail.txt")
|
1655
|
+
|
1656
|
+
d.run(expect_emits: 2, shutdown: false) do
|
1657
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1658
|
+
cleanup_file("#{TMP_DIR}/tail.txt")
|
1659
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f| f.puts "test4\n"}
|
1660
|
+
end
|
1661
|
+
|
1662
|
+
new_target_info = create_target_info("#{TMP_DIR}/tail.txt")
|
1663
|
+
|
1664
|
+
pos_file = d.instance.instance_variable_get(:@pf)
|
1665
|
+
|
1666
|
+
waiting(10) {
|
1667
|
+
# @pos will be reset as 0 when UNWATCHED_POSITION is specified.
|
1668
|
+
sleep 0.1 until pos_file[target_info].read_pos == 0
|
1669
|
+
}
|
1670
|
+
new_position = pos_file[new_target_info].read_pos
|
1671
|
+
assert_equal(6, new_position)
|
1672
|
+
|
1673
|
+
d.instance_shutdown
|
1674
|
+
end
|
1675
|
+
|
1676
|
+
def test_should_close_watcher_after_rotate_wait
|
1677
|
+
now = Time.now
|
1678
|
+
config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"rotate_wait" => "1s", "limit_recently_modified" => "1s"})
|
1679
|
+
|
1680
|
+
d = create_driver(config, false)
|
1681
|
+
|
1682
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1683
|
+
f.puts "test1"
|
1684
|
+
f.puts "test2"
|
1685
|
+
}
|
1686
|
+
target_info = create_target_info("#{TMP_DIR}/tail.txt")
|
1687
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, true, anything, nil, anything).once
|
1688
|
+
d.run(shutdown: false)
|
1689
|
+
assert d.instance.instance_variable_get(:@tails)[target_info]
|
1690
|
+
|
1691
|
+
Timecop.travel(now + 10) do
|
1692
|
+
d.instance.instance_eval do
|
1693
|
+
sleep 0.1 until @tails[target_info] == nil
|
1694
|
+
end
|
1695
|
+
assert_nil d.instance.instance_variable_get(:@tails)[target_info]
|
1696
|
+
end
|
1697
|
+
d.instance_shutdown
|
1698
|
+
end
|
1699
|
+
|
1700
|
+
def test_should_create_new_watcher_for_new_file_with_same_name
|
1701
|
+
now = Time.now
|
1702
|
+
config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"limit_recently_modified" => "2s"})
|
1703
|
+
|
1704
|
+
d = create_driver(config, false)
|
1705
|
+
|
1706
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1707
|
+
f.puts "test1"
|
1708
|
+
f.puts "test2"
|
1709
|
+
}
|
1710
|
+
path_ino = create_target_info("#{TMP_DIR}/tail.txt")
|
1711
|
+
|
1712
|
+
d.run(expect_emits: 1, shutdown: false) do
|
1713
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1714
|
+
end
|
1715
|
+
|
1716
|
+
cleanup_file("#{TMP_DIR}/tail.txt")
|
1717
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1718
|
+
f.puts "test3"
|
1719
|
+
f.puts "test4"
|
1720
|
+
}
|
1721
|
+
new_path_ino = create_target_info("#{TMP_DIR}/tail.txt")
|
1722
|
+
|
1723
|
+
Timecop.travel(now + 10) do
|
1724
|
+
sleep 3
|
1725
|
+
d.instance.instance_eval do
|
1726
|
+
@tails[path_ino] == nil
|
1727
|
+
@tails[new_path_ino] != nil
|
1728
|
+
end
|
1729
|
+
end
|
1730
|
+
|
1731
|
+
events = d.events
|
1732
|
+
|
1733
|
+
assert_equal(5, events.length)
|
1734
|
+
|
1735
|
+
d.instance_shutdown
|
1736
|
+
end
|
1737
|
+
|
1738
|
+
def test_truncate_file_with_follow_inodes
|
1739
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1740
|
+
|
1741
|
+
d = create_driver(config, false)
|
1742
|
+
|
1743
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1744
|
+
f.puts "test1"
|
1745
|
+
f.puts "test2"
|
1746
|
+
}
|
1747
|
+
|
1748
|
+
d.run(expect_emits: 3, shutdown: false) do
|
1749
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1750
|
+
sleep 2
|
1751
|
+
File.open("#{TMP_DIR}/tail.txt", "w+b") {|f| f.puts "test4\n"}
|
1752
|
+
end
|
1753
|
+
|
1754
|
+
events = d.events
|
1755
|
+
assert_equal(4, events.length)
|
1756
|
+
assert_equal({"message" => "test1"}, events[0][2])
|
1757
|
+
assert_equal({"message" => "test2"}, events[1][2])
|
1758
|
+
assert_equal({"message" => "test3"}, events[2][2])
|
1759
|
+
assert_equal({"message" => "test4"}, events[3][2])
|
1760
|
+
d.instance_shutdown
|
1761
|
+
end
|
1762
|
+
end
|
1763
|
+
|
1326
1764
|
sub_test_case "tail_path" do
|
1327
1765
|
def test_tail_path_with_singleline
|
1328
1766
|
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
@@ -1452,13 +1890,13 @@ class TailInputTest < Test::Unit::TestCase
|
|
1452
1890
|
})
|
1453
1891
|
|
1454
1892
|
expected_files = [
|
1455
|
-
"#{TMP_DIR}/tail_watch1.txt",
|
1456
|
-
"#{TMP_DIR}/tail_watch2.txt"
|
1893
|
+
create_target_info("#{TMP_DIR}/tail_watch1.txt"),
|
1894
|
+
create_target_info("#{TMP_DIR}/tail_watch2.txt")
|
1457
1895
|
]
|
1458
1896
|
|
1459
1897
|
Timecop.freeze(now) do
|
1460
1898
|
plugin = create_driver(config, false).instance
|
1461
|
-
assert_equal expected_files, plugin.expand_paths.
|
1899
|
+
assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1462
1900
|
end
|
1463
1901
|
end
|
1464
1902
|
|