fluentd 1.15.3-x86-mingw32 → 1.16.2-x86-mingw32
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 +1 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yaml +1 -0
- data/.github/workflows/linux-test.yaml +2 -2
- data/.github/workflows/macos-test.yaml +2 -2
- data/.github/workflows/stale-actions.yml +24 -0
- data/.github/workflows/windows-test.yaml +2 -2
- data/CHANGELOG.md +151 -0
- data/CONTRIBUTING.md +1 -1
- data/MAINTAINERS.md +5 -3
- data/README.md +0 -1
- data/SECURITY.md +5 -9
- data/fluentd.gemspec +3 -3
- data/lib/fluent/command/ctl.rb +2 -2
- data/lib/fluent/command/fluentd.rb +55 -53
- data/lib/fluent/command/plugin_config_formatter.rb +1 -1
- data/lib/fluent/config/dsl.rb +1 -1
- data/lib/fluent/config/v1_parser.rb +2 -2
- 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/event.rb +8 -4
- 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/plugin/base.rb +6 -8
- data/lib/fluent/plugin/buf_file.rb +32 -3
- 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 +21 -0
- data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
- data/lib/fluent/plugin/in_forward.rb +1 -1
- data/lib/fluent/plugin/in_http.rb +8 -8
- data/lib/fluent/plugin/in_sample.rb +1 -1
- data/lib/fluent/plugin/in_tail/position_file.rb +32 -18
- data/lib/fluent/plugin/in_tail.rb +58 -24
- data/lib/fluent/plugin/in_tcp.rb +47 -2
- data/lib/fluent/plugin/out_exec_filter.rb +2 -2
- data/lib/fluent/plugin/out_forward/ack_handler.rb +19 -4
- data/lib/fluent/plugin/out_forward.rb +2 -2
- data/lib/fluent/plugin/out_secondary_file.rb +39 -22
- data/lib/fluent/plugin/output.rb +50 -13
- data/lib/fluent/plugin/parser_json.rb +1 -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/record_accessor.rb +1 -1
- data/lib/fluent/plugin_helper/server.rb +8 -0
- data/lib/fluent/plugin_helper/thread.rb +3 -3
- data/lib/fluent/plugin_id.rb +1 -1
- data/lib/fluent/supervisor.rb +157 -251
- 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/version.rb +1 -1
- data/templates/new_gem/test/helper.rb.erb +0 -1
- data/test/command/test_ctl.rb +1 -1
- data/test/command/test_fluentd.rb +137 -6
- data/test/command/test_plugin_config_formatter.rb +0 -1
- data/test/compat/test_parser.rb +5 -5
- data/test/config/test_system_config.rb +0 -8
- data/test/log/test_console_adapter.rb +110 -0
- data/test/plugin/in_tail/test_position_file.rb +31 -1
- data/test/plugin/out_forward/test_ack_handler.rb +39 -0
- data/test/plugin/test_base.rb +99 -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_chunk.rb +11 -0
- data/test/plugin/test_in_forward.rb +9 -9
- data/test/plugin/test_in_http.rb +2 -3
- data/test/plugin/test_in_monitor_agent.rb +2 -3
- data/test/plugin/test_in_tail.rb +379 -0
- 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_multi_output.rb +1 -1
- data/test/plugin/test_out_exec_filter.rb +2 -2
- data/test/plugin/test_out_file.rb +2 -2
- data/test/plugin/test_out_forward.rb +14 -18
- data/test/plugin/test_out_http.rb +1 -0
- data/test/plugin/test_output.rb +281 -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 +1 -1
- data/test/plugin/test_output_as_buffered_secondary.rb +2 -2
- data/test/plugin/test_parser_regexp.rb +1 -6
- data/test/plugin_helper/test_child_process.rb +2 -2
- data/test/plugin_helper/test_http_server_helper.rb +1 -1
- data/test/plugin_helper/test_server.rb +60 -6
- data/test/test_config.rb +0 -21
- data/test/test_formatter.rb +23 -20
- data/test/test_log.rb +108 -36
- data/test/test_msgpack_factory.rb +32 -0
- data/test/test_supervisor.rb +287 -279
- metadata +15 -21
- data/.drone.yml +0 -35
- data/.gitlab-ci.yml +0 -103
- data/test/test_logger_initializer.rb +0 -46
@@ -53,10 +53,16 @@ module Fluent::Plugin
|
|
53
53
|
}
|
54
54
|
end
|
55
55
|
|
56
|
+
def unwatch_removed_targets(existing_targets)
|
57
|
+
@map.reject { |key, entry|
|
58
|
+
existing_targets.key?(key)
|
59
|
+
}.each_key { |key|
|
60
|
+
unwatch_key(key)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
56
64
|
def unwatch(target_info)
|
57
|
-
|
58
|
-
entry.update_pos(UNWATCHED_POSITION)
|
59
|
-
end
|
65
|
+
unwatch_key(@follow_inodes ? target_info.ino : target_info.path)
|
60
66
|
end
|
61
67
|
|
62
68
|
def load(existing_targets = nil)
|
@@ -96,6 +102,7 @@ module Fluent::Plugin
|
|
96
102
|
end
|
97
103
|
|
98
104
|
entries = fetch_compacted_entries
|
105
|
+
@logger&.debug "Compacted entries: ", entries.keys
|
99
106
|
|
100
107
|
@file_mutex.synchronize do
|
101
108
|
if last_modified == @file.mtime && size == @file.size
|
@@ -117,17 +124,31 @@ module Fluent::Plugin
|
|
117
124
|
|
118
125
|
private
|
119
126
|
|
127
|
+
def unwatch_key(key)
|
128
|
+
if (entry = @map.delete(key))
|
129
|
+
entry.update_pos(UNWATCHED_POSITION)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
120
133
|
def compact(existing_targets = nil)
|
121
134
|
@file_mutex.synchronize do
|
122
|
-
entries = fetch_compacted_entries
|
135
|
+
entries = fetch_compacted_entries
|
136
|
+
@logger&.debug "Compacted entries: ", entries.keys
|
137
|
+
|
138
|
+
if existing_targets
|
139
|
+
entries = remove_deleted_files_entries(entries, existing_targets)
|
140
|
+
@logger&.debug "Remove missing entries.",
|
141
|
+
existing_targets: existing_targets.keys,
|
142
|
+
entries_after_removing: entries.keys
|
143
|
+
end
|
123
144
|
|
124
145
|
@file.pos = 0
|
125
146
|
@file.truncate(0)
|
126
|
-
@file.write(entries.join)
|
147
|
+
@file.write(entries.values.map(&:to_entry_fmt).join)
|
127
148
|
end
|
128
149
|
end
|
129
150
|
|
130
|
-
def fetch_compacted_entries
|
151
|
+
def fetch_compacted_entries
|
131
152
|
entries = {}
|
132
153
|
|
133
154
|
@file.pos = 0
|
@@ -145,31 +166,24 @@ module Fluent::Plugin
|
|
145
166
|
if pos == UNWATCHED_POSITION
|
146
167
|
@logger.debug "Remove unwatched line from pos_file: #{line}" if @logger
|
147
168
|
else
|
148
|
-
if entries.include?(path)
|
149
|
-
@logger.warn("#{path} already exists. use latest one: deleted #{entries[path]}") if @logger
|
150
|
-
end
|
151
|
-
|
152
169
|
if @follow_inodes
|
170
|
+
@logger&.warn("#{path} (inode: #{ino}) already exists. use latest one: deleted #{entries[ino]}") if entries.include?(ino)
|
153
171
|
entries[ino] = Entry.new(path, pos, ino, file_pos + path.bytesize + 1)
|
154
172
|
else
|
173
|
+
@logger&.warn("#{path} already exists. use latest one: deleted #{entries[path]}") if entries.include?(path)
|
155
174
|
entries[path] = Entry.new(path, pos, ino, file_pos + path.bytesize + 1)
|
156
175
|
end
|
157
176
|
file_pos += line.size
|
158
177
|
end
|
159
178
|
end
|
160
179
|
|
161
|
-
entries = remove_deleted_files_entries(entries, existing_targets)
|
162
180
|
entries
|
163
181
|
end
|
164
182
|
|
165
183
|
def remove_deleted_files_entries(existent_entries, existing_targets)
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
}
|
170
|
-
else
|
171
|
-
existent_entries
|
172
|
-
end
|
184
|
+
existent_entries.select { |path_or_ino|
|
185
|
+
existing_targets.key?(path_or_ino)
|
186
|
+
}
|
173
187
|
end
|
174
188
|
end
|
175
189
|
|
@@ -370,17 +370,30 @@ module Fluent::Plugin
|
|
370
370
|
def refresh_watchers
|
371
371
|
target_paths_hash = expand_paths
|
372
372
|
existence_paths_hash = existence_path
|
373
|
-
|
373
|
+
|
374
374
|
log.debug {
|
375
375
|
target_paths_str = target_paths_hash.collect { |key, target_info| target_info.path }.join(",")
|
376
376
|
existence_paths_str = existence_paths_hash.collect { |key, target_info| target_info.path }.join(",")
|
377
377
|
"tailing paths: target = #{target_paths_str} | existing = #{existence_paths_str}"
|
378
378
|
}
|
379
379
|
|
380
|
-
|
380
|
+
if !@follow_inodes
|
381
|
+
need_unwatch_in_stop_watchers = true
|
382
|
+
else
|
383
|
+
# When using @follow_inodes, need this to unwatch the rotated old inode when it disappears.
|
384
|
+
# After `update_watcher` detaches an old TailWatcher, the inode is lost from the `@tails`.
|
385
|
+
# So that inode can't be contained in `removed_hash`, and can't be unwatched by `stop_watchers`.
|
386
|
+
#
|
387
|
+
# This logic may work for `@follow_inodes false` too.
|
388
|
+
# Just limiting the case to supress the impact to existing logics.
|
389
|
+
@pf&.unwatch_removed_targets(target_paths_hash)
|
390
|
+
need_unwatch_in_stop_watchers = false
|
391
|
+
end
|
392
|
+
|
393
|
+
removed_hash = existence_paths_hash.reject {|key, value| target_paths_hash.key?(key)}
|
381
394
|
added_hash = target_paths_hash.reject {|key, value| existence_paths_hash.key?(key)}
|
382
395
|
|
383
|
-
stop_watchers(
|
396
|
+
stop_watchers(removed_hash, unwatched: need_unwatch_in_stop_watchers) unless removed_hash.empty?
|
384
397
|
start_watchers(added_hash) unless added_hash.empty?
|
385
398
|
@startup = false if @startup
|
386
399
|
end
|
@@ -484,8 +497,26 @@ module Fluent::Plugin
|
|
484
497
|
end
|
485
498
|
|
486
499
|
# refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
|
487
|
-
def update_watcher(
|
488
|
-
|
500
|
+
def update_watcher(tail_watcher, pe, new_inode)
|
501
|
+
# TODO we should use another callback for this.
|
502
|
+
# To supress impact to existing logics, limit the case to `@follow_inodes`.
|
503
|
+
# We may not need `@follow_inodes` condition.
|
504
|
+
if @follow_inodes && new_inode.nil?
|
505
|
+
# nil inode means the file disappeared, so we only need to stop it.
|
506
|
+
@tails.delete(tail_watcher.path)
|
507
|
+
# https://github.com/fluent/fluentd/pull/4237#issuecomment-1633358632
|
508
|
+
# Because of this problem, log duplication can occur during `rotate_wait`.
|
509
|
+
# Need to set `rotate_wait 0` for a workaround.
|
510
|
+
# Duplication will occur if `refresh_watcher` is called during the `rotate_wait`.
|
511
|
+
# In that case, `refresh_watcher` will add the new TailWatcher to tail the same target,
|
512
|
+
# and it causes the log duplication.
|
513
|
+
# (Other `detach_watcher_after_rotate_wait` may have the same problem.
|
514
|
+
# We need the mechanism not to add duplicated TailWathcer with detaching TailWatcher.)
|
515
|
+
detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
|
516
|
+
return
|
517
|
+
end
|
518
|
+
|
519
|
+
path = tail_watcher.path
|
489
520
|
|
490
521
|
log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
|
491
522
|
|
@@ -499,23 +530,22 @@ module Fluent::Plugin
|
|
499
530
|
end
|
500
531
|
end
|
501
532
|
|
502
|
-
|
533
|
+
new_target_info = TargetInfo.new(path, new_inode)
|
503
534
|
|
504
535
|
if @follow_inodes
|
505
|
-
new_position_entry = @pf[
|
506
|
-
|
536
|
+
new_position_entry = @pf[new_target_info]
|
537
|
+
# If `refresh_watcher` find the new file before, this will not be zero.
|
538
|
+
# In this case, only we have to do is detaching the current tail_watcher.
|
507
539
|
if new_position_entry.read_inode == 0
|
508
|
-
|
509
|
-
# So it should be unwatched here explicitly.
|
510
|
-
rotated_tw.unwatched = true if rotated_tw
|
511
|
-
@tails[path] = setup_watcher(target_info, new_position_entry)
|
540
|
+
@tails[path] = setup_watcher(new_target_info, new_position_entry)
|
512
541
|
@tails[path].on_notify
|
513
542
|
end
|
514
543
|
else
|
515
|
-
@tails[path] = setup_watcher(
|
544
|
+
@tails[path] = setup_watcher(new_target_info, pe)
|
516
545
|
@tails[path].on_notify
|
517
546
|
end
|
518
|
-
|
547
|
+
|
548
|
+
detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
|
519
549
|
end
|
520
550
|
|
521
551
|
# TailWatcher#close is called by another thread at shutdown phase.
|
@@ -523,6 +553,10 @@ module Fluent::Plugin
|
|
523
553
|
# so adding close_io argument to avoid this problem.
|
524
554
|
# At shutdown, IOHandler's io will be released automatically after detached the event loop
|
525
555
|
def detach_watcher(tw, ino, close_io = true)
|
556
|
+
if @follow_inodes && tw.ino != ino
|
557
|
+
log.warn("detach_watcher could be detaching an unexpected tail_watcher with a different ino.",
|
558
|
+
path: tw.path, actual_ino_in_tw: tw.ino, expect_ino_to_close: ino)
|
559
|
+
end
|
526
560
|
tw.watchers.each do |watcher|
|
527
561
|
event_loop_detach(watcher)
|
528
562
|
end
|
@@ -778,7 +812,7 @@ module Fluent::Plugin
|
|
778
812
|
attr_accessor :group_watcher
|
779
813
|
|
780
814
|
def tag
|
781
|
-
@parsed_tag ||= @path.tr('/', '.').
|
815
|
+
@parsed_tag ||= @path.tr('/', '.').squeeze('.').gsub(/^\./, '')
|
782
816
|
end
|
783
817
|
|
784
818
|
def register_watcher(watcher)
|
@@ -874,21 +908,21 @@ module Fluent::Plugin
|
|
874
908
|
|
875
909
|
if watcher_needs_update
|
876
910
|
if @follow_inodes
|
877
|
-
#
|
878
|
-
#
|
879
|
-
#
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
911
|
+
# If stat is nil (file not present), NEED to stop and discard this watcher.
|
912
|
+
# When the file is disappeared but is resurrected soon, then `#refresh_watcher`
|
913
|
+
# can't recognize this TailWatcher needs to be stopped.
|
914
|
+
# This can happens when the file is rotated.
|
915
|
+
# If a notify comes before the new file for the path is created during rotation,
|
916
|
+
# then it appears as if the file was resurrected once it disappeared.
|
917
|
+
# Don't want to swap state because we need latest read offset in pos file even after rotate_wait
|
918
|
+
@update_watcher.call(self, @pe, stat&.ino)
|
884
919
|
else
|
885
920
|
# Permit to handle if stat is nil (file not present).
|
886
921
|
# If a file is mv-ed and a new file is created during
|
887
922
|
# calling `#refresh_watchers`s, and `#refresh_watchers` won't run `#start_watchers`
|
888
923
|
# and `#stop_watchers()` for the path because `target_paths_hash`
|
889
924
|
# always contains the path.
|
890
|
-
|
891
|
-
@update_watcher.call(target_info, swap_state(@pe))
|
925
|
+
@update_watcher.call(self, swap_state(@pe), stat&.ino)
|
892
926
|
end
|
893
927
|
else
|
894
928
|
@log.info "detected rotation of #{@path}"
|
data/lib/fluent/plugin/in_tcp.rb
CHANGED
@@ -36,10 +36,16 @@ module Fluent::Plugin
|
|
36
36
|
desc "The field name of the client's address."
|
37
37
|
config_param :source_address_key, :string, default: nil
|
38
38
|
|
39
|
+
# Setting default to nil for backward compatibility
|
40
|
+
desc "The max bytes of message."
|
41
|
+
config_param :message_length_limit, :size, default: nil
|
42
|
+
|
39
43
|
config_param :blocking_timeout, :time, default: 0.5
|
40
44
|
|
41
45
|
desc 'The payload is read up to this character.'
|
42
46
|
config_param :delimiter, :string, default: "\n" # syslog family add "\n" to each message and this seems only way to split messages in tcp stream
|
47
|
+
desc 'Check the remote connection is still available by sending a keepalive packet if this value is true.'
|
48
|
+
config_param :send_keepalive_packet, :bool, default: false
|
43
49
|
|
44
50
|
# in_forward like host/network restriction
|
45
51
|
config_section :security, required: false, multi: false do
|
@@ -100,8 +106,9 @@ module Fluent::Plugin
|
|
100
106
|
|
101
107
|
log.info "listening tcp socket", bind: @bind, port: @port
|
102
108
|
del_size = @delimiter.length
|
109
|
+
discard_till_next_delimiter = false
|
103
110
|
if @_extract_enabled && @_extract_tag_key
|
104
|
-
server_create(:in_tcp_server_single_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key) do |data, conn|
|
111
|
+
server_create(:in_tcp_server_single_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key, send_keepalive_packet: @send_keepalive_packet) do |data, conn|
|
105
112
|
unless check_client(conn)
|
106
113
|
conn.close
|
107
114
|
next
|
@@ -114,6 +121,16 @@ module Fluent::Plugin
|
|
114
121
|
msg = buf[pos...i]
|
115
122
|
pos = i + del_size
|
116
123
|
|
124
|
+
if discard_till_next_delimiter
|
125
|
+
discard_till_next_delimiter = false
|
126
|
+
next
|
127
|
+
end
|
128
|
+
|
129
|
+
if !@message_length_limit.nil? && @message_length_limit < msg.bytesize
|
130
|
+
log.info "The received data is larger than 'message_length_limit', dropped:", limit: @message_length_limit, size: msg.bytesize, head: msg[...32]
|
131
|
+
next
|
132
|
+
end
|
133
|
+
|
117
134
|
@parser.parse(msg) do |time, record|
|
118
135
|
unless time && record
|
119
136
|
log.warn "pattern not matched", message: msg
|
@@ -129,9 +146,18 @@ module Fluent::Plugin
|
|
129
146
|
end
|
130
147
|
end
|
131
148
|
buf.slice!(0, pos) if pos > 0
|
149
|
+
# If the buffer size exceeds the limit here, it means that the next message will definitely exceed the limit.
|
150
|
+
# So we should clear the buffer here. Otherwise, it will keep storing useless data until the next delimiter comes.
|
151
|
+
if !@message_length_limit.nil? && @message_length_limit < buf.bytesize
|
152
|
+
log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buf.bytesize, head: buf[...32]
|
153
|
+
buf.clear
|
154
|
+
# We should discard the subsequent data until the next delimiter comes.
|
155
|
+
discard_till_next_delimiter = true
|
156
|
+
next
|
157
|
+
end
|
132
158
|
end
|
133
159
|
else
|
134
|
-
server_create(:in_tcp_server_batch_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key) do |data, conn|
|
160
|
+
server_create(:in_tcp_server_batch_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key, send_keepalive_packet: @send_keepalive_packet) do |data, conn|
|
135
161
|
unless check_client(conn)
|
136
162
|
conn.close
|
137
163
|
next
|
@@ -145,6 +171,16 @@ module Fluent::Plugin
|
|
145
171
|
msg = buf[pos...i]
|
146
172
|
pos = i + del_size
|
147
173
|
|
174
|
+
if discard_till_next_delimiter
|
175
|
+
discard_till_next_delimiter = false
|
176
|
+
next
|
177
|
+
end
|
178
|
+
|
179
|
+
if !@message_length_limit.nil? && @message_length_limit < msg.bytesize
|
180
|
+
log.info "The received data is larger than 'message_length_limit', dropped:", limit: @message_length_limit, size: msg.bytesize, head: msg[...32]
|
181
|
+
next
|
182
|
+
end
|
183
|
+
|
148
184
|
@parser.parse(msg) do |time, record|
|
149
185
|
unless time && record
|
150
186
|
log.warn "pattern not matched", message: msg
|
@@ -159,6 +195,15 @@ module Fluent::Plugin
|
|
159
195
|
end
|
160
196
|
router.emit_stream(@tag, es)
|
161
197
|
buf.slice!(0, pos) if pos > 0
|
198
|
+
# If the buffer size exceeds the limit here, it means that the next message will definitely exceed the limit.
|
199
|
+
# So we should clear the buffer here. Otherwise, it will keep storing useless data until the next delimiter comes.
|
200
|
+
if !@message_length_limit.nil? && @message_length_limit < buf.bytesize
|
201
|
+
log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buf.bytesize, head: buf[...32]
|
202
|
+
buf.clear
|
203
|
+
# We should discard the subsequent data until the next delimiter comes.
|
204
|
+
discard_till_next_delimiter = true
|
205
|
+
next
|
206
|
+
end
|
162
207
|
end
|
163
208
|
end
|
164
209
|
end
|
@@ -163,7 +163,7 @@ module Fluent::Plugin
|
|
163
163
|
0
|
164
164
|
elsif (@child_respawn == 'inf') || (@child_respawn == '-1')
|
165
165
|
-1
|
166
|
-
elsif @child_respawn
|
166
|
+
elsif /^\d+$/.match?(@child_respawn)
|
167
167
|
@child_respawn.to_i
|
168
168
|
else
|
169
169
|
raise ConfigError, "child_respawn option argument invalid: none(or 0), inf(or -1) or positive number"
|
@@ -187,7 +187,7 @@ module Fluent::Plugin
|
|
187
187
|
@rr = 0
|
188
188
|
|
189
189
|
exit_callback = ->(status){
|
190
|
-
c = @children.
|
190
|
+
c = @children.find{|child| child.pid == status.pid }
|
191
191
|
if c
|
192
192
|
unless self.stopped?
|
193
193
|
log.warn "child process exits with error code", code: status.to_i, status: status.exitstatus, signal: status.termsig
|
@@ -59,7 +59,13 @@ module Fluent::Plugin
|
|
59
59
|
@ack_waitings = new_list
|
60
60
|
end
|
61
61
|
|
62
|
-
|
62
|
+
begin
|
63
|
+
readable_sockets, _, _ = IO.select(sockets, nil, nil, select_interval)
|
64
|
+
rescue IOError
|
65
|
+
@log.info "connection closed while waiting for readable sockets"
|
66
|
+
readable_sockets = nil
|
67
|
+
end
|
68
|
+
|
63
69
|
if readable_sockets
|
64
70
|
readable_sockets.each do |sock|
|
65
71
|
results << read_ack_from_sock(sock)
|
@@ -109,13 +115,22 @@ module Fluent::Plugin
|
|
109
115
|
raw_data = sock.instance_of?(Fluent::PluginHelper::Socket::WrappedSocket::TLS) ? sock.readpartial(@read_length) : sock.recv(@read_length)
|
110
116
|
rescue Errno::ECONNRESET, EOFError # ECONNRESET for #recv, #EOFError for #readpartial
|
111
117
|
raw_data = ''
|
118
|
+
rescue IOError
|
119
|
+
@log.info "socket closed while receiving ack response"
|
120
|
+
return nil, Result::FAILED
|
112
121
|
end
|
113
122
|
|
114
123
|
info = find(sock)
|
115
124
|
|
116
|
-
|
117
|
-
|
118
|
-
|
125
|
+
if info.nil?
|
126
|
+
# The info can be deleted by another thread during `sock.recv()` and `find()`.
|
127
|
+
# This is OK since another thread has completed to process the ack, so we can skip this.
|
128
|
+
# Note: exclusion mechanism about `collect_response()` may need to be considered.
|
129
|
+
@log.debug "could not find the ack info. this ack may be processed by another thread."
|
130
|
+
return nil, Result::FAILED
|
131
|
+
elsif raw_data.empty?
|
132
|
+
# When connection is closed by remote host, socket is ready to read and #recv returns an empty string that means EOF.
|
133
|
+
# If this happens we assume the data wasn't delivered and retry it.
|
119
134
|
@log.warn 'destination node closed the connection. regard it as unavailable.', host: info.node.host, port: info.node.port
|
120
135
|
# info.node.disable!
|
121
136
|
return info, Result::FAILED
|
@@ -521,8 +521,8 @@ module Fluent::Plugin
|
|
521
521
|
when AckHandler::Result::SUCCESS
|
522
522
|
commit_write(chunk_id)
|
523
523
|
when AckHandler::Result::FAILED
|
524
|
-
node
|
525
|
-
rollback_write(chunk_id, update_retry: false)
|
524
|
+
node&.disable!
|
525
|
+
rollback_write(chunk_id, update_retry: false) if chunk_id
|
526
526
|
when AckHandler::Result::CHUNKID_UNMATCHED
|
527
527
|
rollback_write(chunk_id, update_retry: false)
|
528
528
|
else
|
@@ -64,31 +64,29 @@ module Fluent::Plugin
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def multi_workers_ready?
|
67
|
-
### TODO: add hack to synchronize for multi workers
|
68
67
|
true
|
69
68
|
end
|
70
69
|
|
71
70
|
def write(chunk)
|
72
71
|
path_without_suffix = extract_placeholders(@path_without_suffix, chunk)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
72
|
+
generate_path(path_without_suffix) do |path|
|
73
|
+
FileUtils.mkdir_p File.dirname(path), mode: @dir_perm
|
74
|
+
|
75
|
+
case @compress
|
76
|
+
when :text
|
77
|
+
File.open(path, "ab", @file_perm) {|f|
|
78
|
+
f.flock(File::LOCK_EX)
|
79
|
+
chunk.write_to(f)
|
80
|
+
}
|
81
|
+
when :gzip
|
82
|
+
File.open(path, "ab", @file_perm) {|f|
|
83
|
+
f.flock(File::LOCK_EX)
|
84
|
+
gz = Zlib::GzipWriter.new(f)
|
85
|
+
chunk.write_to(gz)
|
86
|
+
gz.close
|
87
|
+
}
|
88
|
+
end
|
89
89
|
end
|
90
|
-
|
91
|
-
path
|
92
90
|
end
|
93
91
|
|
94
92
|
private
|
@@ -117,15 +115,34 @@ module Fluent::Plugin
|
|
117
115
|
|
118
116
|
def generate_path(path_without_suffix)
|
119
117
|
if @append
|
120
|
-
"#{path_without_suffix}#{@suffix}"
|
121
|
-
|
118
|
+
path = "#{path_without_suffix}#{@suffix}"
|
119
|
+
synchronize_path(path) do
|
120
|
+
yield path
|
121
|
+
end
|
122
|
+
return path
|
123
|
+
end
|
124
|
+
|
125
|
+
begin
|
122
126
|
i = 0
|
123
127
|
loop do
|
124
128
|
path = "#{path_without_suffix}.#{i}#{@suffix}"
|
125
|
-
|
129
|
+
break unless File.exist?(path)
|
126
130
|
i += 1
|
127
131
|
end
|
132
|
+
synchronize_path(path) do
|
133
|
+
# If multiple processes or threads select the same path and another
|
134
|
+
# one entered this locking block first, the file should already
|
135
|
+
# exist and this one should retry to find new path.
|
136
|
+
raise FileAlreadyExist if File.exist?(path)
|
137
|
+
yield path
|
138
|
+
end
|
139
|
+
rescue FileAlreadyExist
|
140
|
+
retry
|
128
141
|
end
|
142
|
+
path
|
143
|
+
end
|
144
|
+
|
145
|
+
class FileAlreadyExist < StandardError
|
129
146
|
end
|
130
147
|
end
|
131
148
|
end
|
data/lib/fluent/plugin/output.rb
CHANGED
@@ -99,7 +99,6 @@ module Fluent
|
|
99
99
|
config_param :retry_max_interval, :time, default: nil, desc: 'The maximum interval seconds for exponential backoff between retries while failing.'
|
100
100
|
|
101
101
|
config_param :retry_randomize, :bool, default: true, desc: 'If true, output plugin will retry after randomized interval not to do burst retries.'
|
102
|
-
config_param :disable_chunk_backup, :bool, default: false, desc: 'If true, chunks are thrown away when unrecoverable error happens'
|
103
102
|
end
|
104
103
|
|
105
104
|
config_section :secondary, param_name: :secondary_config, required: false, multi: false, final: true do
|
@@ -199,6 +198,7 @@ module Fluent
|
|
199
198
|
def initialize
|
200
199
|
super
|
201
200
|
@counter_mutex = Mutex.new
|
201
|
+
@flush_thread_mutex = Mutex.new
|
202
202
|
@buffering = false
|
203
203
|
@delayed_commit = false
|
204
204
|
@as_secondary = false
|
@@ -378,6 +378,7 @@ module Fluent
|
|
378
378
|
buffer_conf = conf.elements(name: 'buffer').first || Fluent::Config::Element.new('buffer', '', {}, [])
|
379
379
|
@buffer = Plugin.new_buffer(buffer_type, parent: self)
|
380
380
|
@buffer.configure(buffer_conf)
|
381
|
+
keep_buffer_config_compat
|
381
382
|
@buffer.enable_update_timekeys if @chunk_key_time
|
382
383
|
|
383
384
|
@flush_at_shutdown = @buffer_config.flush_at_shutdown
|
@@ -425,7 +426,9 @@ module Fluent
|
|
425
426
|
end
|
426
427
|
@secondary.acts_as_secondary(self)
|
427
428
|
@secondary.configure(secondary_conf)
|
428
|
-
if (
|
429
|
+
if (@secondary.class.to_s != "Fluent::Plugin::SecondaryFileOutput") &&
|
430
|
+
(self.class != @secondary.class) &&
|
431
|
+
(@custom_format || @secondary.implement?(:custom_format))
|
429
432
|
log.warn "Use different plugin for secondary. Check the plugin works with primary like secondary_file", primary: self.class.to_s, secondary: @secondary.class.to_s
|
430
433
|
end
|
431
434
|
else
|
@@ -435,6 +438,12 @@ module Fluent
|
|
435
438
|
self
|
436
439
|
end
|
437
440
|
|
441
|
+
def keep_buffer_config_compat
|
442
|
+
# Need this to call `@buffer_config.disable_chunk_backup` just as before,
|
443
|
+
# since some plugins may use this option in this way.
|
444
|
+
@buffer_config[:disable_chunk_backup] = @buffer.disable_chunk_backup
|
445
|
+
end
|
446
|
+
|
438
447
|
def start
|
439
448
|
super
|
440
449
|
|
@@ -591,6 +600,42 @@ module Fluent
|
|
591
600
|
super
|
592
601
|
end
|
593
602
|
|
603
|
+
def actual_flush_thread_count
|
604
|
+
return 0 unless @buffering
|
605
|
+
return @buffer_config.flush_thread_count unless @as_secondary
|
606
|
+
@primary_instance.buffer_config.flush_thread_count
|
607
|
+
end
|
608
|
+
|
609
|
+
# Ensures `path` (filename or filepath) processable
|
610
|
+
# only by the current thread in the current process.
|
611
|
+
# For multiple workers, the lock is shared if `path` is the same value.
|
612
|
+
# For multiple threads, the lock is shared by all threads in the same process.
|
613
|
+
def synchronize_path(path)
|
614
|
+
synchronize_path_in_workers(path) do
|
615
|
+
synchronize_in_threads do
|
616
|
+
yield
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
def synchronize_path_in_workers(path)
|
622
|
+
need_worker_lock = system_config.workers > 1
|
623
|
+
if need_worker_lock
|
624
|
+
acquire_worker_lock(path) { yield }
|
625
|
+
else
|
626
|
+
yield
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
def synchronize_in_threads
|
631
|
+
need_thread_lock = actual_flush_thread_count > 1
|
632
|
+
if need_thread_lock
|
633
|
+
@flush_thread_mutex.synchronize { yield }
|
634
|
+
else
|
635
|
+
yield
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
594
639
|
def support_in_v12_style?(feature)
|
595
640
|
# for plugins written in v0.12 styles
|
596
641
|
case feature
|
@@ -779,7 +824,7 @@ module Fluent
|
|
779
824
|
if str.include?('${tag}')
|
780
825
|
rvalue = rvalue.gsub('${tag}', metadata.tag)
|
781
826
|
end
|
782
|
-
if str
|
827
|
+
if CHUNK_TAG_PLACEHOLDER_PATTERN.match?(str)
|
783
828
|
hash = {}
|
784
829
|
tag_parts = metadata.tag.split('.')
|
785
830
|
tag_parts.each_with_index do |part, i|
|
@@ -1240,18 +1285,10 @@ module Fluent
|
|
1240
1285
|
end
|
1241
1286
|
|
1242
1287
|
def backup_chunk(chunk, using_secondary, delayed_commit)
|
1243
|
-
if @
|
1288
|
+
if @buffer.disable_chunk_backup
|
1244
1289
|
log.warn "disable_chunk_backup is true. #{dump_unique_id_hex(chunk.unique_id)} chunk is thrown away"
|
1245
1290
|
else
|
1246
|
-
|
1247
|
-
safe_plugin_id = plugin_id.gsub(/[ "\/\\:;|*<>?]/, '_')
|
1248
|
-
backup_base_dir = system_config.root_dir || DEFAULT_BACKUP_DIR
|
1249
|
-
backup_file = File.join(backup_base_dir, 'backup', "worker#{fluentd_worker_id}", safe_plugin_id, "#{unique_id}.log")
|
1250
|
-
backup_dir = File.dirname(backup_file)
|
1251
|
-
|
1252
|
-
log.warn "bad chunk is moved to #{backup_file}"
|
1253
|
-
FileUtils.mkdir_p(backup_dir, mode: system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION) unless Dir.exist?(backup_dir)
|
1254
|
-
File.open(backup_file, 'ab', system_config.file_permission || Fluent::DEFAULT_FILE_PERMISSION) { |f|
|
1291
|
+
@buffer.backup(chunk.unique_id) { |f|
|
1255
1292
|
chunk.write_to(f)
|
1256
1293
|
}
|
1257
1294
|
end
|
@@ -99,7 +99,7 @@ module Fluent
|
|
99
99
|
|
100
100
|
def shutdown
|
101
101
|
@_event_loop_mutex.synchronize do
|
102
|
-
@_event_loop_attached_watchers.
|
102
|
+
@_event_loop_attached_watchers.reverse_each do |w|
|
103
103
|
if w.attached?
|
104
104
|
begin
|
105
105
|
w.detach
|
@@ -116,7 +116,7 @@ module Fluent
|
|
116
116
|
def after_shutdown
|
117
117
|
timeout_at = Fluent::Clock.now + EVENT_LOOP_SHUTDOWN_TIMEOUT
|
118
118
|
@_event_loop_mutex.synchronize do
|
119
|
-
@_event_loop.watchers.
|
119
|
+
@_event_loop.watchers.reverse_each do |w|
|
120
120
|
begin
|
121
121
|
w.detach
|
122
122
|
rescue => e
|
@@ -21,6 +21,7 @@ require 'async/http/endpoint'
|
|
21
21
|
require 'fluent/plugin_helper/http_server/app'
|
22
22
|
require 'fluent/plugin_helper/http_server/router'
|
23
23
|
require 'fluent/plugin_helper/http_server/methods'
|
24
|
+
require 'fluent/log/console_adapter'
|
24
25
|
|
25
26
|
module Fluent
|
26
27
|
module PluginHelper
|
@@ -38,7 +39,7 @@ module Fluent
|
|
38
39
|
scheme = tls_context ? 'https' : 'http'
|
39
40
|
@uri = URI("#{scheme}://#{@addr}:#{@port}").to_s
|
40
41
|
@router = Router.new(default_app)
|
41
|
-
@reactor = Async::Reactor.new(nil, logger: @logger)
|
42
|
+
@reactor = Async::Reactor.new(nil, logger: Fluent::Log::ConsoleAdapter.wrap(@logger))
|
42
43
|
|
43
44
|
opts = if tls_context
|
44
45
|
{ ssl_context: tls_context }
|