fluentd 1.11.4-x86-mingw32 → 1.12.3-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 +35 -0
- data/.gitlab-ci.yml +41 -19
- data/CHANGELOG.md +166 -0
- data/MAINTAINERS.md +5 -2
- data/README.md +7 -4
- data/bin/fluent-cap-ctl +7 -0
- data/bin/fluent-ctl +7 -0
- data/fluentd.gemspec +7 -5
- 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/command/plugin_generator.rb +31 -1
- 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/file_wrapper.rb +39 -3
- 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 +129 -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 +28 -3
- data/lib/fluent/plugin/output.rb +18 -10
- data/lib/fluent/plugin/parser_csv.rb +2 -2
- data/lib/fluent/plugin/parser_syslog.rb +2 -2
- data/lib/fluent/plugin/storage_local.rb +4 -4
- data/lib/fluent/plugin_helper/http_server/compat/server.rb +1 -1
- data/lib/fluent/plugin_helper/inject.rb +4 -2
- data/lib/fluent/plugin_helper/retry_state.rb +4 -0
- data/lib/fluent/plugin_helper/server.rb +4 -2
- data/lib/fluent/plugin_helper/socket_option.rb +2 -2
- 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/new_gem/fluent-plugin.gemspec.erb +3 -3
- 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/test_file_wrapper.rb +105 -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 +503 -42
- 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 +94 -6
- data/test/plugin/test_out_http.rb +20 -1
- data/test/plugin/test_output.rb +15 -3
- data/test/plugin/test_output_as_buffered_backup.rb +2 -0
- data/test/plugin/test_parser_csv.rb +14 -0
- data/test/plugin/test_parser_syslog.rb +16 -2
- data/test/plugin/test_sd_file.rb +1 -1
- data/test/plugin_helper/service_discovery/test_manager.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 +4 -2
- data/test/plugin_helper/test_inject.rb +29 -0
- data/test/plugin_helper/test_server.rb +26 -7
- 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 +8 -3
- data/test/test_supervisor.rb +150 -1
- data/test/test_time_parser.rb +109 -0
- metadata +87 -33
- data/.travis.yml +0 -57
- data/appveyor.yml +0 -28
@@ -12,6 +12,11 @@ class StdoutFilterTest < Test::Unit::TestCase
|
|
12
12
|
@old_tz = ENV["TZ"]
|
13
13
|
ENV["TZ"] = "UTC"
|
14
14
|
Timecop.freeze
|
15
|
+
@default_newline = if Fluent.windows?
|
16
|
+
"\r\n"
|
17
|
+
else
|
18
|
+
"\n"
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
22
|
def teardown
|
@@ -106,7 +111,7 @@ class StdoutFilterTest < Test::Unit::TestCase
|
|
106
111
|
def test_format_json
|
107
112
|
d = create_driver(CONFIG + config_element("", "", { "format" => "json" }))
|
108
113
|
out = capture_log(d) { filter(d, event_time, {'test' => 'test'}) }
|
109
|
-
assert_equal "{\"test\":\"test\"}
|
114
|
+
assert_equal "{\"test\":\"test\"}#{@default_newline}", out
|
110
115
|
end
|
111
116
|
end
|
112
117
|
|
@@ -19,11 +19,14 @@ class HashFormatterTest < ::Test::Unit::TestCase
|
|
19
19
|
{'message' => 'awesome', 'greeting' => 'hello'}
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
data("newline (LF)" => ["lf", "\n"],
|
23
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
24
|
+
def test_format(data)
|
25
|
+
newline_conf, newline = data
|
26
|
+
d = create_driver({"newline" => newline_conf})
|
24
27
|
formatted = d.instance.format(tag, @time, record)
|
25
28
|
|
26
|
-
assert_equal(%Q!{"message"=>"awesome", "greeting"=>"hello"}
|
29
|
+
assert_equal(%Q!{"message"=>"awesome", "greeting"=>"hello"}#{newline}!, formatted.encode(Encoding::UTF_8))
|
27
30
|
end
|
28
31
|
|
29
32
|
def test_format_without_newline
|
@@ -7,6 +7,11 @@ class JsonFormatterTest < ::Test::Unit::TestCase
|
|
7
7
|
|
8
8
|
def setup
|
9
9
|
@time = event_time
|
10
|
+
@default_newline = if Fluent.windows?
|
11
|
+
"\r\n"
|
12
|
+
else
|
13
|
+
"\n"
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
12
17
|
def create_driver(conf = "")
|
@@ -25,12 +30,17 @@ class JsonFormatterTest < ::Test::Unit::TestCase
|
|
25
30
|
{:message => :awesome}
|
26
31
|
end
|
27
32
|
|
28
|
-
data('oj' => 'oj',
|
33
|
+
data('oj with LF' => ['oj', "lf", "\n"],
|
34
|
+
'oj with CRLF' => ['oj', "crlf", "\r\n"],
|
35
|
+
'yajl with LF' => ['yajl', "lf", "\n"],
|
36
|
+
'yajl with CRLF' => ['yajl', "crlf", "\r\n"]
|
37
|
+
)
|
29
38
|
def test_format(data)
|
30
|
-
|
39
|
+
parser, newline_conf, newline = data
|
40
|
+
d = create_driver('json_parser' => parser, 'newline' => newline_conf)
|
31
41
|
formatted = d.instance.format(tag, @time, record)
|
32
42
|
|
33
|
-
assert_equal("#{JSON.generate(record)}
|
43
|
+
assert_equal("#{JSON.generate(record)}#{newline}", formatted)
|
34
44
|
end
|
35
45
|
|
36
46
|
data('oj' => 'oj', 'yajl' => 'yajl')
|
@@ -46,6 +56,6 @@ class JsonFormatterTest < ::Test::Unit::TestCase
|
|
46
56
|
d = create_driver('json_parser' => data)
|
47
57
|
formatted = d.instance.format(tag, @time, symbolic_record)
|
48
58
|
|
49
|
-
assert_equal("#{JSON.generate(record)}
|
59
|
+
assert_equal("#{JSON.generate(record)}#{@default_newline}", formatted)
|
50
60
|
end
|
51
61
|
end
|
@@ -36,11 +36,14 @@ class LabeledTSVFormatterTest < ::Test::Unit::TestCase
|
|
36
36
|
assert_equal false, d.instance.add_newline
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
data("newline (LF)" => ["lf", "\n"],
|
40
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
41
|
+
def test_format(data)
|
42
|
+
newline_conf, newline = data
|
43
|
+
d = create_driver({"newline" => newline_conf})
|
41
44
|
formatted = d.instance.format(tag, @time, record)
|
42
45
|
|
43
|
-
assert_equal("message:awesome\tgreeting:hello
|
46
|
+
assert_equal("message:awesome\tgreeting:hello#{newline}", formatted)
|
44
47
|
end
|
45
48
|
|
46
49
|
def test_format_without_newline
|
@@ -50,13 +53,18 @@ class LabeledTSVFormatterTest < ::Test::Unit::TestCase
|
|
50
53
|
assert_equal("message:awesome\tgreeting:hello", formatted)
|
51
54
|
end
|
52
55
|
|
53
|
-
|
56
|
+
data("newline (LF)" => ["lf", "\n"],
|
57
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
58
|
+
def test_format_with_customized_delimiters(data)
|
59
|
+
newline_conf, newline = data
|
60
|
+
|
54
61
|
d = create_driver(
|
55
62
|
'delimiter' => ',',
|
56
63
|
'label_delimiter' => '=',
|
64
|
+
'newline' => newline_conf,
|
57
65
|
)
|
58
66
|
formatted = d.instance.format(tag, @time, record)
|
59
67
|
|
60
|
-
assert_equal("message=awesome,greeting=hello
|
68
|
+
assert_equal("message=awesome,greeting=hello#{newline}", formatted)
|
61
69
|
end
|
62
70
|
end
|
@@ -5,6 +5,11 @@ require 'fluent/plugin/formatter_out_file'
|
|
5
5
|
class OutFileFormatterTest < ::Test::Unit::TestCase
|
6
6
|
def setup
|
7
7
|
@time = event_time
|
8
|
+
@default_newline = if Fluent.windows?
|
9
|
+
"\r\n"
|
10
|
+
else
|
11
|
+
"\n"
|
12
|
+
end
|
8
13
|
end
|
9
14
|
|
10
15
|
def create_driver(conf = {})
|
@@ -48,48 +53,64 @@ class OutFileFormatterTest < ::Test::Unit::TestCase
|
|
48
53
|
oldtz, ENV['TZ'] = ENV['TZ'], "UTC+07"
|
49
54
|
d = create_driver(config_element('ROOT', '', {key => value}))
|
50
55
|
tag = 'test'
|
51
|
-
assert_equal "#{expected}\t#{tag}\t#{Yajl.dump(record)}
|
56
|
+
assert_equal "#{expected}\t#{tag}\t#{Yajl.dump(record)}#{@default_newline}", d.instance.format(tag, time, record)
|
52
57
|
ensure
|
53
58
|
ENV['TZ'] = oldtz
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
57
|
-
|
58
|
-
|
62
|
+
data("newline (LF)" => ["lf", "\n"],
|
63
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
64
|
+
def test_format(data)
|
65
|
+
newline_conf, newline = data
|
66
|
+
d = create_driver({"newline" => newline_conf})
|
59
67
|
formatted = d.instance.format(tag, @time, record)
|
60
68
|
|
61
|
-
assert_equal("#{time2str(@time)}\t#{tag}\t#{Yajl.dump(record)}
|
69
|
+
assert_equal("#{time2str(@time)}\t#{tag}\t#{Yajl.dump(record)}#{newline}", formatted)
|
62
70
|
end
|
63
71
|
|
64
|
-
|
65
|
-
|
72
|
+
data("newline (LF)" => ["lf", "\n"],
|
73
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
74
|
+
def test_format_without_time(data)
|
75
|
+
newline_conf, newline = data
|
76
|
+
d = create_driver('output_time' => 'false', 'newline' => newline_conf)
|
66
77
|
formatted = d.instance.format(tag, @time, record)
|
67
78
|
|
68
|
-
assert_equal("#{tag}\t#{Yajl.dump(record)}
|
79
|
+
assert_equal("#{tag}\t#{Yajl.dump(record)}#{newline}", formatted)
|
69
80
|
end
|
70
81
|
|
71
|
-
|
72
|
-
|
82
|
+
data("newline (LF)" => ["lf", "\n"],
|
83
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
84
|
+
def test_format_without_tag(data)
|
85
|
+
newline_conf, newline = data
|
86
|
+
d = create_driver('output_tag' => 'false', 'newline' => newline_conf)
|
73
87
|
formatted = d.instance.format(tag, @time, record)
|
74
88
|
|
75
|
-
assert_equal("#{time2str(@time)}\t#{Yajl.dump(record)}
|
89
|
+
assert_equal("#{time2str(@time)}\t#{Yajl.dump(record)}#{newline}", formatted)
|
76
90
|
end
|
77
91
|
|
92
|
+
data("newline (LF)" => ["lf", "\n"],
|
93
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
78
94
|
def test_format_without_time_and_tag
|
79
|
-
|
95
|
+
newline_conf, newline = data
|
96
|
+
d = create_driver('output_tag' => 'false', 'output_time' => 'false', 'newline' => newline_conf)
|
80
97
|
formatted = d.instance.format('tag', @time, record)
|
81
98
|
|
82
|
-
assert_equal("#{Yajl.dump(record)}
|
99
|
+
assert_equal("#{Yajl.dump(record)}#{newline}", formatted)
|
83
100
|
end
|
84
101
|
|
85
|
-
|
102
|
+
data("newline (LF)" => ["lf", "\n"],
|
103
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
104
|
+
def test_format_without_time_and_tag_against_string_literal_configure(data)
|
105
|
+
newline_conf, newline = data
|
86
106
|
d = create_driver(%[
|
87
107
|
utc true
|
88
108
|
output_tag false
|
89
109
|
output_time false
|
110
|
+
newline #{newline_conf}
|
90
111
|
])
|
91
112
|
formatted = d.instance.format('tag', @time, record)
|
92
113
|
|
93
|
-
assert_equal("#{Yajl.dump(record)}
|
114
|
+
assert_equal("#{Yajl.dump(record)}#{newline}", formatted)
|
94
115
|
end
|
95
116
|
end
|
@@ -17,10 +17,13 @@ class SingleValueFormatterTest < ::Test::Unit::TestCase
|
|
17
17
|
assert_equal "foobar", d.instance.message_key
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
data("newline (LF)" => ["lf", "\n"],
|
21
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
22
|
+
def test_format(data)
|
23
|
+
newline_conf, newline = data
|
24
|
+
d = create_driver('newline' => newline_conf)
|
22
25
|
formatted = d.instance.format('tag', event_time, {'message' => 'awesome'})
|
23
|
-
assert_equal("awesome
|
26
|
+
assert_equal("awesome#{newline}", formatted)
|
24
27
|
end
|
25
28
|
|
26
29
|
def test_format_without_newline
|
@@ -29,10 +32,13 @@ class SingleValueFormatterTest < ::Test::Unit::TestCase
|
|
29
32
|
assert_equal("awesome", formatted)
|
30
33
|
end
|
31
34
|
|
32
|
-
|
33
|
-
|
35
|
+
data("newline (LF)" => ["lf", "\n"],
|
36
|
+
"newline (CRLF)" => ["crlf", "\r\n"])
|
37
|
+
def test_format_with_message_key(data)
|
38
|
+
newline_conf, newline = data
|
39
|
+
d = create_driver('message_key' => 'foobar', 'newline' => newline_conf)
|
34
40
|
formatted = d.instance.format('tag', event_time, {'foobar' => 'foo'})
|
35
41
|
|
36
|
-
assert_equal("foo
|
42
|
+
assert_equal("foo#{newline}", formatted)
|
37
43
|
end
|
38
44
|
end
|
@@ -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
|
@@ -509,6 +589,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
509
589
|
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
510
590
|
f.puts "test1"
|
511
591
|
f.puts "test2"
|
592
|
+
f.flush
|
512
593
|
}
|
513
594
|
|
514
595
|
d = create_driver(config)
|
@@ -518,19 +599,23 @@ class TailInputTest < Test::Unit::TestCase
|
|
518
599
|
f.puts "test3\ntest4"
|
519
600
|
f.flush
|
520
601
|
}
|
521
|
-
sleep 1
|
602
|
+
waiting(2) { sleep 0.1 until d.events.length == 2 }
|
522
603
|
File.truncate("#{TMP_DIR}/tail.txt", 6)
|
523
604
|
end
|
524
605
|
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
606
|
+
expected = {
|
607
|
+
emit_count: 2,
|
608
|
+
events: [
|
609
|
+
[Fluent::EventTime, {"message" => "test3"}],
|
610
|
+
[Fluent::EventTime, {"message" => "test4"}],
|
611
|
+
[Fluent::EventTime, {"message" => "test1"}],
|
612
|
+
]
|
613
|
+
}
|
614
|
+
actual = {
|
615
|
+
emit_count: d.emit_count,
|
616
|
+
events: d.events.collect{|event| [event[1].class, event[2]]}
|
617
|
+
}
|
618
|
+
assert_equal(expected, actual)
|
534
619
|
end
|
535
620
|
|
536
621
|
def test_move_truncate_move_back
|
@@ -999,6 +1084,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
999
1084
|
# * path test
|
1000
1085
|
# TODO: Clean up tests
|
1001
1086
|
EX_ROTATE_WAIT = 0
|
1087
|
+
EX_FOLLOW_INODES = false
|
1002
1088
|
|
1003
1089
|
EX_CONFIG = config_element("", "", {
|
1004
1090
|
"tag" => "tail",
|
@@ -1008,38 +1094,43 @@ class TailInputTest < Test::Unit::TestCase
|
|
1008
1094
|
"read_from_head" => true,
|
1009
1095
|
"refresh_interval" => 30,
|
1010
1096
|
"rotate_wait" => "#{EX_ROTATE_WAIT}s",
|
1097
|
+
"follow_inodes" => "#{EX_FOLLOW_INODES}",
|
1011
1098
|
})
|
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
1099
|
def test_expand_paths
|
1100
|
+
ex_paths = [
|
1101
|
+
create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
|
1102
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1103
|
+
create_target_info('test/plugin/data/log/test.log')
|
1104
|
+
]
|
1019
1105
|
plugin = create_driver(EX_CONFIG, false).instance
|
1020
1106
|
flexstub(Time) do |timeclass|
|
1021
1107
|
timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 2, 3, 4, 5))
|
1022
|
-
assert_equal
|
1108
|
+
assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1023
1109
|
end
|
1024
1110
|
|
1025
1111
|
# Test exclusion
|
1026
|
-
exclude_config = EX_CONFIG + config_element("", "", { "exclude_path" => %Q(["#{
|
1112
|
+
exclude_config = EX_CONFIG + config_element("", "", { "exclude_path" => %Q(["#{ex_paths.last.path}"]) })
|
1027
1113
|
plugin = create_driver(exclude_config, false).instance
|
1028
|
-
assert_equal
|
1114
|
+
assert_equal ex_paths - [ex_paths.last], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1029
1115
|
end
|
1030
1116
|
|
1031
1117
|
def test_expand_paths_with_duplicate_configuration
|
1032
1118
|
expanded_paths = [
|
1033
|
-
'test/plugin/data/log/foo/bar.log',
|
1034
|
-
'test/plugin/data/log/test.log'
|
1119
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1120
|
+
create_target_info('test/plugin/data/log/test.log')
|
1035
1121
|
]
|
1036
1122
|
duplicate_config = EX_CONFIG.dup
|
1037
1123
|
duplicate_config["path"]="test/plugin/data/log/**/*.log, test/plugin/data/log/**/*.log"
|
1038
1124
|
plugin = create_driver(EX_CONFIG, false).instance
|
1039
|
-
assert_equal expanded_paths, plugin.expand_paths.
|
1125
|
+
assert_equal expanded_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1040
1126
|
end
|
1041
1127
|
|
1042
1128
|
def test_expand_paths_with_timezone
|
1129
|
+
ex_paths = [
|
1130
|
+
create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
|
1131
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1132
|
+
create_target_info('test/plugin/data/log/test.log')
|
1133
|
+
]
|
1043
1134
|
['Asia/Taipei', '+08'].each do |tz_type|
|
1044
1135
|
taipei_config = EX_CONFIG + config_element("", "", {"path_timezone" => tz_type})
|
1045
1136
|
plugin = create_driver(taipei_config, false).instance
|
@@ -1053,8 +1144,8 @@ class TailInputTest < Test::Unit::TestCase
|
|
1053
1144
|
# env : 2010-01-01 19:04:05 (UTC), tail path : 2010-01-02 03:04:05 (Asia/Taipei)
|
1054
1145
|
timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 1, 19, 4, 5))
|
1055
1146
|
|
1056
|
-
assert_equal
|
1057
|
-
assert_equal
|
1147
|
+
assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1148
|
+
assert_equal ex_paths - [ex_paths.first], exclude_plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1058
1149
|
end
|
1059
1150
|
end
|
1060
1151
|
end
|
@@ -1062,10 +1153,10 @@ class TailInputTest < Test::Unit::TestCase
|
|
1062
1153
|
|
1063
1154
|
def test_log_file_without_extension
|
1064
1155
|
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'
|
1156
|
+
create_target_info('test/plugin/data/log/bar'),
|
1157
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1158
|
+
create_target_info('test/plugin/data/log/foo/bar2'),
|
1159
|
+
create_target_info('test/plugin/data/log/test.log')
|
1069
1160
|
]
|
1070
1161
|
|
1071
1162
|
config = config_element("", "", {
|
@@ -1076,7 +1167,7 @@ class TailInputTest < Test::Unit::TestCase
|
|
1076
1167
|
})
|
1077
1168
|
|
1078
1169
|
plugin = create_driver(config, false).instance
|
1079
|
-
assert_equal expected_files, plugin.expand_paths.
|
1170
|
+
assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1080
1171
|
end
|
1081
1172
|
|
1082
1173
|
def test_unwatched_files_should_be_removed
|
@@ -1112,6 +1203,49 @@ class TailInputTest < Test::Unit::TestCase
|
|
1112
1203
|
end
|
1113
1204
|
end
|
1114
1205
|
|
1206
|
+
sub_test_case "path w/ Linux capability" do
|
1207
|
+
def capability_enabled?
|
1208
|
+
if Fluent.linux?
|
1209
|
+
begin
|
1210
|
+
require 'capng'
|
1211
|
+
true
|
1212
|
+
rescue LoadError
|
1213
|
+
false
|
1214
|
+
end
|
1215
|
+
else
|
1216
|
+
false
|
1217
|
+
end
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
setup do
|
1221
|
+
omit "This environment is not enabled Linux capability handling feature" unless capability_enabled?
|
1222
|
+
|
1223
|
+
@capng = CapNG.new(:current_process)
|
1224
|
+
flexstub(Fluent::Capability) do |klass|
|
1225
|
+
klass.should_receive(:new).with(:current_process).and_return(@capng)
|
1226
|
+
end
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
data("dac_read_search" => [:dac_read_search, true, 1],
|
1230
|
+
"dac_override" => [:dac_override, true, 1],
|
1231
|
+
"chown" => [:chown, false, 0],
|
1232
|
+
)
|
1233
|
+
test "with partially elevated privileges" do |data|
|
1234
|
+
cap, result, readable_paths = data
|
1235
|
+
@capng.update(:add, :effective, cap)
|
1236
|
+
|
1237
|
+
d = create_driver(
|
1238
|
+
config_element("ROOT", "", {
|
1239
|
+
"path" => "/var/log/ker*.log", # Use /var/log/kern.log
|
1240
|
+
"tag" => "t1",
|
1241
|
+
"rotate_wait" => "2s"
|
1242
|
+
}) + PARSE_SINGLE_LINE_CONFIG, false)
|
1243
|
+
|
1244
|
+
assert_equal readable_paths, d.instance.expand_paths.length
|
1245
|
+
assert_equal result, d.instance.have_read_capability?
|
1246
|
+
end
|
1247
|
+
end
|
1248
|
+
|
1115
1249
|
def test_pos_file_dir_creation
|
1116
1250
|
config = config_element("", "", {
|
1117
1251
|
"tag" => "tail",
|
@@ -1159,25 +1293,35 @@ class TailInputTest < Test::Unit::TestCase
|
|
1159
1293
|
end
|
1160
1294
|
|
1161
1295
|
def test_z_refresh_watchers
|
1296
|
+
ex_paths = [
|
1297
|
+
create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
|
1298
|
+
create_target_info('test/plugin/data/log/foo/bar.log'),
|
1299
|
+
create_target_info('test/plugin/data/log/test.log'),
|
1300
|
+
]
|
1162
1301
|
plugin = create_driver(EX_CONFIG, false).instance
|
1163
1302
|
sio = StringIO.new
|
1164
1303
|
plugin.instance_eval do
|
1165
|
-
@pf = Fluent::Plugin::TailInput::PositionFile.load(sio, logger: $log)
|
1304
|
+
@pf = Fluent::Plugin::TailInput::PositionFile.load(sio, EX_FOLLOW_INODES, {}, logger: $log)
|
1166
1305
|
@loop = Coolio::Loop.new
|
1167
1306
|
end
|
1168
1307
|
|
1169
1308
|
Timecop.freeze(2010, 1, 2, 3, 4, 5) do
|
1170
|
-
|
1171
|
-
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(
|
1309
|
+
ex_paths.each do |target_info|
|
1310
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
|
1172
1311
|
end
|
1173
1312
|
|
1174
1313
|
plugin.refresh_watchers
|
1175
1314
|
end
|
1176
1315
|
|
1177
|
-
|
1316
|
+
path = 'test/plugin/data/2010/01/20100102-030405.log'
|
1317
|
+
target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
|
1318
|
+
mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)[target_info], target_info.ino)
|
1178
1319
|
|
1179
1320
|
Timecop.freeze(2010, 1, 2, 3, 4, 6) do
|
1180
|
-
|
1321
|
+
path = "test/plugin/data/2010/01/20100102-030406.log"
|
1322
|
+
inode = Fluent::FileWrapper.stat(path).ino
|
1323
|
+
target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, inode)
|
1324
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
|
1181
1325
|
plugin.refresh_watchers
|
1182
1326
|
|
1183
1327
|
flexstub(Fluent::Plugin::TailInput::TailWatcher) do |watcherclass|
|
@@ -1323,6 +1467,305 @@ class TailInputTest < Test::Unit::TestCase
|
|
1323
1467
|
end
|
1324
1468
|
end
|
1325
1469
|
|
1470
|
+
sub_test_case 'inode_processing' do
|
1471
|
+
def test_should_delete_file_pos_entry_for_non_existing_file_with_follow_inodes
|
1472
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1473
|
+
|
1474
|
+
path = "#{TMP_DIR}/tail.txt"
|
1475
|
+
ino = 1
|
1476
|
+
pos = 1234
|
1477
|
+
File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
|
1478
|
+
f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
|
1479
|
+
}
|
1480
|
+
|
1481
|
+
d = create_driver(config, false)
|
1482
|
+
d.run
|
1483
|
+
|
1484
|
+
pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
|
1485
|
+
pos_file.pos = 0
|
1486
|
+
|
1487
|
+
assert_raise(EOFError) do
|
1488
|
+
pos_file.readline
|
1489
|
+
end
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
def test_should_write_latest_offset_after_rotate_wait
|
1493
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1494
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1495
|
+
f.puts "test1"
|
1496
|
+
f.puts "test2"
|
1497
|
+
}
|
1498
|
+
|
1499
|
+
d = create_driver(config, false)
|
1500
|
+
d.run(expect_emits: 2, shutdown: false) do
|
1501
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1502
|
+
FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt" + "1")
|
1503
|
+
sleep 1
|
1504
|
+
File.open("#{TMP_DIR}/tail.txt" + "1", "ab") {|f| f.puts "test4\n"}
|
1505
|
+
end
|
1506
|
+
|
1507
|
+
pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
|
1508
|
+
pos_file.pos = 0
|
1509
|
+
line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
1510
|
+
waiting(5) {
|
1511
|
+
while line_parts[2].to_i(16) != 24
|
1512
|
+
sleep(0.1)
|
1513
|
+
pos_file.pos = 0
|
1514
|
+
line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
1515
|
+
end
|
1516
|
+
}
|
1517
|
+
assert_equal(24, line_parts[2].to_i(16))
|
1518
|
+
d.instance_shutdown
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
def test_should_keep_and_update_existing_file_pos_entry_for_deleted_file_when_new_file_with_same_name_created
|
1522
|
+
config = config_element("", "", {"format" => "none"})
|
1523
|
+
|
1524
|
+
path = "#{TMP_DIR}/tail.txt"
|
1525
|
+
ino = 1
|
1526
|
+
pos = 1234
|
1527
|
+
File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
|
1528
|
+
f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
|
1529
|
+
}
|
1530
|
+
|
1531
|
+
d = create_driver(config)
|
1532
|
+
d.run(shutdown: false)
|
1533
|
+
|
1534
|
+
pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
|
1535
|
+
pos_file.pos = 0
|
1536
|
+
|
1537
|
+
path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
1538
|
+
assert_equal(path, path_pos_ino[1])
|
1539
|
+
assert_equal(pos, path_pos_ino[2].to_i(16))
|
1540
|
+
assert_equal(ino, path_pos_ino[3].to_i(16))
|
1541
|
+
|
1542
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1543
|
+
f.puts "test1"
|
1544
|
+
f.puts "test2"
|
1545
|
+
}
|
1546
|
+
Timecop.travel(Time.now + 10) do
|
1547
|
+
sleep 5
|
1548
|
+
pos_file.pos = 0
|
1549
|
+
tuple = create_target_info("#{TMP_DIR}/tail.txt")
|
1550
|
+
path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
|
1551
|
+
assert_equal(tuple.path, path_pos_ino[1])
|
1552
|
+
assert_equal(12, path_pos_ino[2].to_i(16))
|
1553
|
+
assert_equal(tuple.ino, path_pos_ino[3].to_i(16))
|
1554
|
+
end
|
1555
|
+
d.instance_shutdown
|
1556
|
+
end
|
1557
|
+
|
1558
|
+
def test_should_mark_file_unwatched_after_limit_recently_modified_and_rotate_wait
|
1559
|
+
config = config_element("ROOT", "", {
|
1560
|
+
"path" => "#{TMP_DIR}/tail.txt*",
|
1561
|
+
"pos_file" => "#{TMP_DIR}/tail.pos",
|
1562
|
+
"tag" => "t1",
|
1563
|
+
"rotate_wait" => "1s",
|
1564
|
+
"refresh_interval" => "1s",
|
1565
|
+
"limit_recently_modified" => "1s",
|
1566
|
+
"read_from_head" => "true",
|
1567
|
+
"format" => "none",
|
1568
|
+
"follow_inodes" => "true",
|
1569
|
+
})
|
1570
|
+
|
1571
|
+
d = create_driver(config, false)
|
1572
|
+
|
1573
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1574
|
+
f.puts "test1"
|
1575
|
+
f.puts "test2"
|
1576
|
+
}
|
1577
|
+
target_info = create_target_info("#{TMP_DIR}/tail.txt")
|
1578
|
+
|
1579
|
+
d.run(expect_emits: 1, shutdown: false) do
|
1580
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
|
1584
|
+
Timecop.travel(Time.now + 10) do
|
1585
|
+
waiting(5) {
|
1586
|
+
# @pos will be reset as 0 when UNWATCHED_POSITION is specified.
|
1587
|
+
sleep 0.1 until d.instance.instance_variable_get(:@pf)[target_info].read_pos == 0
|
1588
|
+
}
|
1589
|
+
end
|
1590
|
+
|
1591
|
+
assert_equal(0, d.instance.instance_variable_get(:@pf)[target_info].read_pos)
|
1592
|
+
|
1593
|
+
d.instance_shutdown
|
1594
|
+
end
|
1595
|
+
|
1596
|
+
def test_should_read_from_head_on_file_renaming_with_star_in_pattern
|
1597
|
+
config = config_element("ROOT", "", {
|
1598
|
+
"path" => "#{TMP_DIR}/tail.txt*",
|
1599
|
+
"pos_file" => "#{TMP_DIR}/tail.pos",
|
1600
|
+
"tag" => "t1",
|
1601
|
+
"rotate_wait" => "10s",
|
1602
|
+
"refresh_interval" => "1s",
|
1603
|
+
"limit_recently_modified" => "60s",
|
1604
|
+
"read_from_head" => "true",
|
1605
|
+
"format" => "none",
|
1606
|
+
"follow_inodes" => "true"
|
1607
|
+
})
|
1608
|
+
|
1609
|
+
d = create_driver(config, false)
|
1610
|
+
|
1611
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1612
|
+
f.puts "test1"
|
1613
|
+
f.puts "test2"
|
1614
|
+
}
|
1615
|
+
|
1616
|
+
d.run(expect_emits: 2, shutdown: false) do
|
1617
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1618
|
+
FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
events = d.events
|
1622
|
+
assert_equal(3, events.length)
|
1623
|
+
d.instance_shutdown
|
1624
|
+
end
|
1625
|
+
|
1626
|
+
def test_should_not_read_from_head_on_rotation_when_watching_inodes
|
1627
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1628
|
+
|
1629
|
+
d = create_driver(config, false)
|
1630
|
+
|
1631
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1632
|
+
f.puts "test1"
|
1633
|
+
f.puts "test2"
|
1634
|
+
}
|
1635
|
+
|
1636
|
+
d.run(expect_emits: 1, shutdown: false) do
|
1637
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1638
|
+
end
|
1639
|
+
|
1640
|
+
FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
|
1641
|
+
Timecop.travel(Time.now + 10) do
|
1642
|
+
sleep 2
|
1643
|
+
events = d.events
|
1644
|
+
assert_equal(3, events.length)
|
1645
|
+
end
|
1646
|
+
|
1647
|
+
d.instance_shutdown
|
1648
|
+
end
|
1649
|
+
|
1650
|
+
def test_should_mark_file_unwatched_if_same_name_file_created_with_different_inode
|
1651
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1652
|
+
|
1653
|
+
d = create_driver(config, false)
|
1654
|
+
|
1655
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1656
|
+
f.puts "test1"
|
1657
|
+
f.puts "test2"
|
1658
|
+
}
|
1659
|
+
target_info = create_target_info("#{TMP_DIR}/tail.txt")
|
1660
|
+
|
1661
|
+
d.run(expect_emits: 2, shutdown: false) do
|
1662
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1663
|
+
cleanup_file("#{TMP_DIR}/tail.txt")
|
1664
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f| f.puts "test4\n"}
|
1665
|
+
end
|
1666
|
+
|
1667
|
+
new_target_info = create_target_info("#{TMP_DIR}/tail.txt")
|
1668
|
+
|
1669
|
+
pos_file = d.instance.instance_variable_get(:@pf)
|
1670
|
+
|
1671
|
+
waiting(10) {
|
1672
|
+
# @pos will be reset as 0 when UNWATCHED_POSITION is specified.
|
1673
|
+
sleep 0.1 until pos_file[target_info].read_pos == 0
|
1674
|
+
}
|
1675
|
+
new_position = pos_file[new_target_info].read_pos
|
1676
|
+
assert_equal(6, new_position)
|
1677
|
+
|
1678
|
+
d.instance_shutdown
|
1679
|
+
end
|
1680
|
+
|
1681
|
+
def test_should_close_watcher_after_rotate_wait
|
1682
|
+
now = Time.now
|
1683
|
+
config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"rotate_wait" => "1s", "limit_recently_modified" => "1s"})
|
1684
|
+
|
1685
|
+
d = create_driver(config, false)
|
1686
|
+
|
1687
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1688
|
+
f.puts "test1"
|
1689
|
+
f.puts "test2"
|
1690
|
+
}
|
1691
|
+
target_info = create_target_info("#{TMP_DIR}/tail.txt")
|
1692
|
+
mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, true, anything, nil, anything).once
|
1693
|
+
d.run(shutdown: false)
|
1694
|
+
assert d.instance.instance_variable_get(:@tails)[target_info]
|
1695
|
+
|
1696
|
+
Timecop.travel(now + 10) do
|
1697
|
+
d.instance.instance_eval do
|
1698
|
+
sleep 0.1 until @tails[target_info] == nil
|
1699
|
+
end
|
1700
|
+
assert_nil d.instance.instance_variable_get(:@tails)[target_info]
|
1701
|
+
end
|
1702
|
+
d.instance_shutdown
|
1703
|
+
end
|
1704
|
+
|
1705
|
+
def test_should_create_new_watcher_for_new_file_with_same_name
|
1706
|
+
now = Time.now
|
1707
|
+
config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"limit_recently_modified" => "2s"})
|
1708
|
+
|
1709
|
+
d = create_driver(config, false)
|
1710
|
+
|
1711
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1712
|
+
f.puts "test1"
|
1713
|
+
f.puts "test2"
|
1714
|
+
}
|
1715
|
+
path_ino = create_target_info("#{TMP_DIR}/tail.txt")
|
1716
|
+
|
1717
|
+
d.run(expect_emits: 1, shutdown: false) do
|
1718
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1719
|
+
end
|
1720
|
+
|
1721
|
+
cleanup_file("#{TMP_DIR}/tail.txt")
|
1722
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1723
|
+
f.puts "test3"
|
1724
|
+
f.puts "test4"
|
1725
|
+
}
|
1726
|
+
new_path_ino = create_target_info("#{TMP_DIR}/tail.txt")
|
1727
|
+
|
1728
|
+
Timecop.travel(now + 10) do
|
1729
|
+
sleep 3
|
1730
|
+
d.instance.instance_eval do
|
1731
|
+
@tails[path_ino] == nil
|
1732
|
+
@tails[new_path_ino] != nil
|
1733
|
+
end
|
1734
|
+
end
|
1735
|
+
|
1736
|
+
events = d.events
|
1737
|
+
|
1738
|
+
assert_equal(5, events.length)
|
1739
|
+
|
1740
|
+
d.instance_shutdown
|
1741
|
+
end
|
1742
|
+
|
1743
|
+
def test_truncate_file_with_follow_inodes
|
1744
|
+
config = COMMON_FOLLOW_INODE_CONFIG
|
1745
|
+
|
1746
|
+
d = create_driver(config, false)
|
1747
|
+
|
1748
|
+
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
1749
|
+
f.puts "test1"
|
1750
|
+
f.puts "test2"
|
1751
|
+
}
|
1752
|
+
|
1753
|
+
d.run(expect_emits: 3, shutdown: false) do
|
1754
|
+
File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
|
1755
|
+
sleep 2
|
1756
|
+
File.open("#{TMP_DIR}/tail.txt", "w+b") {|f| f.puts "test4\n"}
|
1757
|
+
end
|
1758
|
+
|
1759
|
+
events = d.events
|
1760
|
+
assert_equal(4, events.length)
|
1761
|
+
assert_equal({"message" => "test1"}, events[0][2])
|
1762
|
+
assert_equal({"message" => "test2"}, events[1][2])
|
1763
|
+
assert_equal({"message" => "test3"}, events[2][2])
|
1764
|
+
assert_equal({"message" => "test4"}, events[3][2])
|
1765
|
+
d.instance_shutdown
|
1766
|
+
end
|
1767
|
+
end
|
1768
|
+
|
1326
1769
|
sub_test_case "tail_path" do
|
1327
1770
|
def test_tail_path_with_singleline
|
1328
1771
|
File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
|
@@ -1452,13 +1895,13 @@ class TailInputTest < Test::Unit::TestCase
|
|
1452
1895
|
})
|
1453
1896
|
|
1454
1897
|
expected_files = [
|
1455
|
-
"#{TMP_DIR}/tail_watch1.txt",
|
1456
|
-
"#{TMP_DIR}/tail_watch2.txt"
|
1898
|
+
create_target_info("#{TMP_DIR}/tail_watch1.txt"),
|
1899
|
+
create_target_info("#{TMP_DIR}/tail_watch2.txt")
|
1457
1900
|
]
|
1458
1901
|
|
1459
1902
|
Timecop.freeze(now) do
|
1460
1903
|
plugin = create_driver(config, false).instance
|
1461
|
-
assert_equal expected_files, plugin.expand_paths.
|
1904
|
+
assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
|
1462
1905
|
end
|
1463
1906
|
end
|
1464
1907
|
|
@@ -1476,4 +1919,22 @@ class TailInputTest < Test::Unit::TestCase
|
|
1476
1919
|
waiting(5) { sleep 0.1 until d.instance.instance_variable_get(:@tails).keys.size == 1 }
|
1477
1920
|
d.instance_shutdown
|
1478
1921
|
end
|
1922
|
+
|
1923
|
+
def test_ENOENT_error_after_setup_watcher
|
1924
|
+
path = "#{TMP_DIR}/tail.txt"
|
1925
|
+
FileUtils.touch(path)
|
1926
|
+
config = config_element('', '', {
|
1927
|
+
'format' => 'none',
|
1928
|
+
})
|
1929
|
+
d = create_driver(config)
|
1930
|
+
mock.proxy(d.instance).setup_watcher(anything, anything) do |tw|
|
1931
|
+
cleanup_file(path)
|
1932
|
+
tw
|
1933
|
+
end
|
1934
|
+
assert_nothing_raised do
|
1935
|
+
d.run(shutdown: false) {}
|
1936
|
+
end
|
1937
|
+
d.instance_shutdown
|
1938
|
+
assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed with ENOENT. Drop tail watcher for now.\n") })
|
1939
|
+
end
|
1479
1940
|
end
|