fluentd 1.19.3 → 1.20.0
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/AGENTS.md +46 -0
- data/CHANGELOG.md +120 -0
- data/GithubWorkflow.md +1 -1
- data/MAINTAINERS.md +2 -2
- data/README.md +3 -2
- data/Rakefile +2 -1
- data/SECURITY.md +26 -1
- data/lib/fluent/command/cat.rb +3 -3
- data/lib/fluent/compat/exec_util.rb +20 -5
- data/lib/fluent/config/basic_parser.rb +0 -1
- data/lib/fluent/config/literal_parser.rb +22 -10
- data/lib/fluent/config/types.rb +10 -2
- data/lib/fluent/config/yaml_parser/loader.rb +12 -1
- data/lib/fluent/config/yaml_parser/parser.rb +14 -13
- data/lib/fluent/configurable.rb +1 -0
- data/lib/fluent/daemon.rb +2 -1
- data/lib/fluent/env.rb +1 -0
- data/lib/fluent/event.rb +37 -0
- data/lib/fluent/load.rb +0 -1
- data/lib/fluent/log.rb +2 -2
- data/lib/fluent/plugin/base.rb +7 -2
- data/lib/fluent/plugin/buf_file.rb +3 -3
- data/lib/fluent/plugin/buf_file_single.rb +6 -2
- data/lib/fluent/plugin/buffer/chunk.rb +14 -6
- data/lib/fluent/plugin/buffer/file_chunk.rb +28 -13
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +28 -13
- data/lib/fluent/plugin/buffer.rb +125 -41
- data/lib/fluent/plugin/compressable.rb +2 -2
- data/lib/fluent/plugin/extractor.rb +17 -6
- data/lib/fluent/plugin/filter_record_transformer.rb +2 -1
- data/lib/fluent/plugin/formatter_out_file.rb +3 -4
- data/lib/fluent/plugin/in_exec.rb +6 -4
- data/lib/fluent/plugin/in_forward.rb +39 -29
- data/lib/fluent/plugin/in_http.rb +83 -2
- data/lib/fluent/plugin/in_monitor_agent.rb +2 -1
- data/lib/fluent/plugin/in_object_space.rb +2 -2
- data/lib/fluent/plugin/in_sample.rb +2 -1
- data/lib/fluent/plugin/in_syslog.rb +72 -6
- data/lib/fluent/plugin/in_tail/compatibility.rb +111 -0
- data/lib/fluent/plugin/in_tail/dir_watcher.rb +713 -0
- data/lib/fluent/plugin/in_tail/group_watch.rb +13 -13
- data/lib/fluent/plugin/in_tail/io_handler.rb +391 -0
- data/lib/fluent/plugin/in_tail/line_feeder.rb +230 -0
- data/lib/fluent/plugin/in_tail/stat_watcher.rb +52 -0
- data/lib/fluent/plugin/in_tail/tail_watcher.rb +276 -0
- data/lib/fluent/plugin/in_tail/worker_pool.rb +298 -0
- data/lib/fluent/plugin/in_tail.rb +324 -815
- data/lib/fluent/plugin/in_unix.rb +5 -3
- data/lib/fluent/plugin/out_exec_filter.rb +2 -2
- data/lib/fluent/plugin/out_file.rb +10 -4
- data/lib/fluent/plugin/out_forward/connection_manager.rb +18 -0
- data/lib/fluent/plugin/out_forward.rb +21 -2
- data/lib/fluent/plugin/out_http.rb +0 -10
- data/lib/fluent/plugin/output.rb +81 -31
- data/lib/fluent/plugin/parser_csv.rb +7 -2
- data/lib/fluent/plugin/parser_json.rb +25 -12
- data/lib/fluent/plugin/parser_ltsv.rb +2 -4
- data/lib/fluent/plugin/parser_msgpack.rb +7 -6
- data/lib/fluent/plugin/parser_syslog.rb +55 -8
- data/lib/fluent/plugin/parser_tsv.rb +6 -1
- data/lib/fluent/plugin/sd_file.rb +2 -1
- data/lib/fluent/plugin/storage_local.rb +2 -2
- data/lib/fluent/plugin_helper/http_server/server.rb +29 -13
- data/lib/fluent/plugin_helper/http_server.rb +2 -1
- data/lib/fluent/root_agent.rb +1 -1
- data/lib/fluent/supervisor.rb +32 -6
- data/lib/fluent/system_config.rb +5 -2
- data/lib/fluent/time.rb +8 -7
- data/lib/fluent/version.rb +1 -1
- data/templates/new_gem/.gitignore +8 -0
- metadata +41 -21
|
@@ -19,7 +19,7 @@ require 'fluent/plugin/input'
|
|
|
19
19
|
require 'fluent/msgpack_factory'
|
|
20
20
|
|
|
21
21
|
require 'cool.io'
|
|
22
|
-
require '
|
|
22
|
+
require 'json'
|
|
23
23
|
require 'fileutils'
|
|
24
24
|
require 'socket'
|
|
25
25
|
|
|
@@ -158,8 +158,7 @@ module Fluent::Plugin
|
|
|
158
158
|
first = data[0]
|
|
159
159
|
if first == '{'.freeze || first == '['.freeze
|
|
160
160
|
m = method(:on_read_json)
|
|
161
|
-
@parser =
|
|
162
|
-
@parser.on_parse_complete = @on_message
|
|
161
|
+
@parser = JSON::ResumableParser.new(**Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
163
162
|
else
|
|
164
163
|
m = method(:on_read_msgpack)
|
|
165
164
|
@parser = Fluent::MessagePackFactory.msgpack_unpacker
|
|
@@ -173,6 +172,9 @@ module Fluent::Plugin
|
|
|
173
172
|
|
|
174
173
|
def on_read_json(data)
|
|
175
174
|
@parser << data
|
|
175
|
+
while @parser.parse
|
|
176
|
+
@on_message.call(@parser.value)
|
|
177
|
+
end
|
|
176
178
|
rescue => e
|
|
177
179
|
@log.error "unexpected error in json payload", error: e.to_s
|
|
178
180
|
@log.error_backtrace
|
|
@@ -17,7 +17,7 @@ require 'fluent/plugin/output'
|
|
|
17
17
|
require 'fluent/env'
|
|
18
18
|
require 'fluent/config/error'
|
|
19
19
|
|
|
20
|
-
require '
|
|
20
|
+
require 'json'
|
|
21
21
|
|
|
22
22
|
module Fluent::Plugin
|
|
23
23
|
class ExecFilterOutput < Output
|
|
@@ -309,7 +309,7 @@ module Fluent::Plugin
|
|
|
309
309
|
router.emit(tag, time, record)
|
|
310
310
|
rescue => e
|
|
311
311
|
if @suppress_error_log_interval == 0 || Time.now.to_i > @next_log_time
|
|
312
|
-
log.error "exec_filter failed to emit", record:
|
|
312
|
+
log.error "exec_filter failed to emit", record: JSON.generate(record), error: e
|
|
313
313
|
log.error_backtrace e.backtrace
|
|
314
314
|
@next_log_time = Time.now.to_i + @suppress_error_log_interval
|
|
315
315
|
end
|
|
@@ -75,9 +75,8 @@ module Fluent::Plugin
|
|
|
75
75
|
def metadata(timekey: nil, tag: nil, variables: nil)
|
|
76
76
|
metadata = super
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@latest_metadata = metadata
|
|
78
|
+
if newer_timekey?(metadata.timekey)
|
|
79
|
+
synchronize { @latest_timekey = metadata.timekey if newer_timekey?(metadata.timekey) }
|
|
81
80
|
end
|
|
82
81
|
|
|
83
82
|
metadata
|
|
@@ -97,7 +96,8 @@ module Fluent::Plugin
|
|
|
97
96
|
# timekey will be appended into that file chunk. On the other side, resumed file chunks might NOT
|
|
98
97
|
# have timekey, especially in the cases that resumed file chunks are generated by Fluentd v0.12.
|
|
99
98
|
# These chunks will be enqueued immediately, and will be flushed soon.
|
|
100
|
-
|
|
99
|
+
# Split chunks (seq > 0) are generated outside the buffer lock, so ln_sf would race between threads.
|
|
100
|
+
if chunk.metadata.timekey && chunk.metadata.seq == 0 && chunk.metadata.timekey == @latest_timekey
|
|
101
101
|
sym_path = @_output_plugin_for_symlink.extract_placeholders(@_symlink_path, chunk)
|
|
102
102
|
FileUtils.mkdir_p(File.dirname(sym_path), mode: @_output_plugin_for_symlink.dir_perm)
|
|
103
103
|
if @_output_plugin_for_symlink.symlink_path_use_relative
|
|
@@ -109,6 +109,12 @@ module Fluent::Plugin
|
|
|
109
109
|
end
|
|
110
110
|
chunk
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def newer_timekey?(timekey)
|
|
116
|
+
timekey && (@latest_timekey.nil? || timekey > @latest_timekey)
|
|
117
|
+
end
|
|
112
118
|
end
|
|
113
119
|
|
|
114
120
|
def configure(conf)
|
|
@@ -78,6 +78,24 @@ module Fluent::Plugin
|
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
# Drop a socket instead of returning it to the keepalive reuse pool.
|
|
82
|
+
#
|
|
83
|
+
# Used when a chunk's ack timed out or came back with a mismatched chunk
|
|
84
|
+
# id: such a connection may still carry an in-flight/stale ack for a
|
|
85
|
+
# previous chunk. Reusing it would let that stale ack be read against a
|
|
86
|
+
# chunk sent on the reused socket, permanently offsetting every following
|
|
87
|
+
# ack and flooding the log with 'ack in response and chunk id in sent data
|
|
88
|
+
# are different' warnings (see #4057). Revoking removes it from the pool so
|
|
89
|
+
# the next chunk uses a fresh connection with a clean ack stream.
|
|
90
|
+
def discard(sock)
|
|
91
|
+
if @socket_cache
|
|
92
|
+
@socket_cache.revoke(sock)
|
|
93
|
+
else
|
|
94
|
+
sock.close_write rescue nil
|
|
95
|
+
sock.close rescue nil
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
81
99
|
private
|
|
82
100
|
|
|
83
101
|
def connect_keepalive(host:, port:, hostname:, ack: nil)
|
|
@@ -119,6 +119,8 @@ module Fluent::Plugin
|
|
|
119
119
|
config_param :keepalive, :bool, default: false
|
|
120
120
|
desc "Expired time of keepalive. Default value is nil, which means to keep connection as long as possible"
|
|
121
121
|
config_param :keepalive_timeout, :time, default: nil
|
|
122
|
+
desc 'Check the remote connection is still available by sending a keepalive packet if this value is true.'
|
|
123
|
+
config_param :send_keepalive_packet, :bool, default: false
|
|
122
124
|
|
|
123
125
|
config_section :security, required: false, multi: false do
|
|
124
126
|
desc 'The hostname'
|
|
@@ -270,6 +272,10 @@ module Fluent::Plugin
|
|
|
270
272
|
log.warn('The value of keepalive_timeout is ignored. if you want to use keepalive, please add `keepalive true` to your conf.')
|
|
271
273
|
end
|
|
272
274
|
|
|
275
|
+
if @send_keepalive_packet && !@keepalive
|
|
276
|
+
raise Fluent::ConfigError, "'send_keepalive_packet' is enabled but 'keepalive' is not. Enable 'keepalive' to use TCP keepalive."
|
|
277
|
+
end
|
|
278
|
+
|
|
273
279
|
raise Fluent::ConfigError, "ack_response_timeout must be a positive integer" if @ack_response_timeout < 1
|
|
274
280
|
|
|
275
281
|
if @compress == :zstd
|
|
@@ -414,6 +420,7 @@ module Fluent::Plugin
|
|
|
414
420
|
send_timeout: @send_timeout,
|
|
415
421
|
recv_timeout: @ack_response_timeout,
|
|
416
422
|
connect_timeout: @connect_timeout,
|
|
423
|
+
send_keepalive_packet: @send_keepalive_packet,
|
|
417
424
|
&block
|
|
418
425
|
)
|
|
419
426
|
when :tcp
|
|
@@ -423,6 +430,7 @@ module Fluent::Plugin
|
|
|
423
430
|
send_timeout: @send_timeout,
|
|
424
431
|
recv_timeout: @ack_response_timeout,
|
|
425
432
|
connect_timeout: @connect_timeout,
|
|
433
|
+
send_keepalive_packet: @send_keepalive_packet,
|
|
426
434
|
&block
|
|
427
435
|
)
|
|
428
436
|
else
|
|
@@ -525,17 +533,28 @@ module Fluent::Plugin
|
|
|
525
533
|
|
|
526
534
|
def ack_check(select_interval)
|
|
527
535
|
@ack_handler.collect_response(select_interval) do |chunk_id, node, sock, result|
|
|
528
|
-
@connection_manager.close(sock)
|
|
529
|
-
|
|
530
536
|
case result
|
|
531
537
|
when AckHandler::Result::SUCCESS
|
|
538
|
+
@connection_manager.close(sock)
|
|
532
539
|
commit_write(chunk_id)
|
|
533
540
|
when AckHandler::Result::FAILED
|
|
541
|
+
# The ack timed out or the peer closed the connection. A keepalive
|
|
542
|
+
# socket may still have an in-flight ack for this chunk, so drop it
|
|
543
|
+
# instead of returning it to the reuse pool; otherwise a late ack
|
|
544
|
+
# could later be read against a chunk sent on the reused socket (#4057).
|
|
545
|
+
@connection_manager.discard(sock)
|
|
534
546
|
node&.disable!
|
|
535
547
|
rollback_write(chunk_id, update_retry: false) if chunk_id
|
|
536
548
|
when AckHandler::Result::CHUNKID_UNMATCHED
|
|
549
|
+
# The ack read from this socket belongs to a different chunk than the
|
|
550
|
+
# one tracked for it: the keepalive connection's ack stream is out of
|
|
551
|
+
# sync (e.g. a stale ack left over from before an aggregator restart).
|
|
552
|
+
# Discard the socket so the following acks are not each offset by one,
|
|
553
|
+
# which would otherwise flood the log with mismatch warnings (#4057).
|
|
554
|
+
@connection_manager.discard(sock)
|
|
537
555
|
rollback_write(chunk_id, update_retry: false)
|
|
538
556
|
else
|
|
557
|
+
@connection_manager.close(sock)
|
|
539
558
|
log.warn("BUG: invalid status #{result} #{chunk_id}")
|
|
540
559
|
|
|
541
560
|
if chunk_id
|
|
@@ -22,16 +22,6 @@ require 'fluent/tls'
|
|
|
22
22
|
require 'fluent/plugin/output'
|
|
23
23
|
require 'fluent/plugin_helper/socket'
|
|
24
24
|
|
|
25
|
-
# patch Net::HTTP to support extra_chain_cert which was added in Ruby feature #9758.
|
|
26
|
-
# see: https://github.com/ruby/ruby/commit/31af0dafba6d3769d2a39617c0dddedb97883712
|
|
27
|
-
unless Net::HTTP::SSL_IVNAMES.include?(:@extra_chain_cert)
|
|
28
|
-
class Net::HTTP
|
|
29
|
-
SSL_IVNAMES << :@extra_chain_cert
|
|
30
|
-
SSL_ATTRIBUTES << :extra_chain_cert
|
|
31
|
-
attr_accessor :extra_chain_cert
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
25
|
module Fluent::Plugin
|
|
36
26
|
class HTTPOutput < Output
|
|
37
27
|
Fluent::Plugin.register_output('http', self)
|
data/lib/fluent/plugin/output.rb
CHANGED
|
@@ -28,6 +28,7 @@ require 'fluent/unique_id'
|
|
|
28
28
|
require 'fluent/clock'
|
|
29
29
|
require 'fluent/ext_monitor_require'
|
|
30
30
|
|
|
31
|
+
require 'json'
|
|
31
32
|
require 'time'
|
|
32
33
|
|
|
33
34
|
module Fluent
|
|
@@ -44,8 +45,21 @@ module Fluent
|
|
|
44
45
|
CHUNK_KEY_PLACEHOLDER_PATTERN = /\$\{([-_.@$a-zA-Z0-9]+)\}/
|
|
45
46
|
CHUNK_TAG_PLACEHOLDER_PATTERN = /\$\{(tag(?:\[-?\d+\])?)\}/
|
|
46
47
|
CHUNK_ID_PLACEHOLDER_PATTERN = /\$\{chunk_id\}/
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
|
|
49
|
+
# Matches absolute paths, including the POSIX root, Windows drive roots, and UNC paths (e.g., \\host\share).
|
|
50
|
+
# It also accounts for forward slashes used as directory separators in Windows.
|
|
51
|
+
ABSOLUTE_PATH_PATTERN = %r{\A[/\\]}
|
|
52
|
+
# Matches Windows drive specifications.
|
|
53
|
+
# A trailing separator after the colon is not required, because a drive-relative path like C:Windows can still escape a relative path template.
|
|
54
|
+
WINDOWS_DRIVE_PATTERN = %r{\A[a-zA-Z]:}
|
|
55
|
+
# Matches .. only when it acts as an independent path component (e.g., ../etc or foo/..),
|
|
56
|
+
# ignoring dots embedded within strings like app..web or a..b/c.
|
|
57
|
+
# It uses zero-width negative lookarounds to avoid consuming characters.
|
|
58
|
+
# This ensures that adjacent components like /a/../../b are accurately detected and counted without missing any.
|
|
59
|
+
PARENT_DIRECTORY_PATTERN = %r{(?<![^/\\])\.\.(?![^/\\])}
|
|
60
|
+
|
|
61
|
+
INVALID_PATH_COMPONENT_PATTERN =
|
|
62
|
+
Regexp.union(ABSOLUTE_PATH_PATTERN, WINDOWS_DRIVE_PATTERN, PARENT_DIRECTORY_PATTERN)
|
|
49
63
|
|
|
50
64
|
CHUNKING_FIELD_WARN_NUM = 4
|
|
51
65
|
|
|
@@ -878,12 +892,13 @@ module Fluent
|
|
|
878
892
|
|
|
879
893
|
replace
|
|
880
894
|
end
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# Verifies that the number of parent directory traversals (..) has not increased after variable substitution.
|
|
898
|
+
# While each replaced value is validated individually, this step prevents harmless values from concatenating into a dangerous path (e.g., ${tag}${key}/ becoming ../ if both tag and key are .).
|
|
899
|
+
if rvalue.match?(PARENT_DIRECTORY_PATTERN) &&
|
|
900
|
+
rvalue.scan(PARENT_DIRECTORY_PATTERN).size > str.scan(PARENT_DIRECTORY_PATTERN).size
|
|
901
|
+
raise Fluent::UnrecoverableError, "Invalid path component detected, replaced to: #{rvalue}"
|
|
887
902
|
end
|
|
888
903
|
|
|
889
904
|
if rvalue =~ CHUNK_KEY_PLACEHOLDER_PATTERN
|
|
@@ -943,39 +958,47 @@ module Fluent
|
|
|
943
958
|
return Struct.new(:timekey, :tag, :variables).new
|
|
944
959
|
end
|
|
945
960
|
|
|
961
|
+
timekey = @chunk_key_time ? calculate_timekey(time) : nil
|
|
962
|
+
@_metadata_cache ||= MetadataCache.new
|
|
963
|
+
|
|
946
964
|
# timekey is int from epoch, and `timekey - timekey % 60` is assumed to mach with 0s of each minutes.
|
|
947
965
|
# it's wrong if timezone is configured as one which supports leap second, but it's very rare and
|
|
948
966
|
# we can ignore it (especially in production systems).
|
|
949
967
|
if @chunk_keys.empty?
|
|
950
|
-
if
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
968
|
+
return @_metadata_cache.metadata if @_metadata_cache.cached?(timekey: timekey, tag: tag)
|
|
969
|
+
|
|
970
|
+
meta = if @chunk_key_time && @chunk_key_tag
|
|
971
|
+
@buffer.metadata(timekey: timekey, tag: tag)
|
|
972
|
+
elsif @chunk_key_time
|
|
973
|
+
@buffer.metadata(timekey: timekey)
|
|
974
|
+
elsif @chunk_key_tag
|
|
975
|
+
@buffer.metadata(tag: tag)
|
|
976
|
+
else
|
|
977
|
+
@buffer.metadata()
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
@_metadata_cache.update(timekey: timekey, tag: tag, metadata: meta)
|
|
981
|
+
meta
|
|
961
982
|
else
|
|
962
|
-
timekey = if @chunk_key_time
|
|
963
|
-
calculate_timekey(time)
|
|
964
|
-
else
|
|
965
|
-
nil
|
|
966
|
-
end
|
|
967
983
|
pairs = Hash[@chunk_key_accessors.map { |k, a| [k, a.call(record)] }]
|
|
968
|
-
@
|
|
984
|
+
return @_metadata_cache.metadata if @_metadata_cache.cached?(timekey: timekey, tag: tag, variables: pairs)
|
|
985
|
+
|
|
986
|
+
meta = @buffer.metadata(timekey: timekey, tag: (@chunk_key_tag ? tag : nil), variables: pairs)
|
|
987
|
+
@_metadata_cache.update(timekey: timekey, tag: tag, variables: pairs, metadata: meta)
|
|
988
|
+
meta
|
|
969
989
|
end
|
|
970
990
|
end
|
|
971
991
|
|
|
972
992
|
def calculate_timekey(time)
|
|
973
993
|
time_int = time.to_i
|
|
994
|
+
return @calculate_timekey_last_timekey if @calculate_timekey_last_time_int == time_int
|
|
995
|
+
|
|
996
|
+
@calculate_timekey_last_time_int = time_int
|
|
974
997
|
if @timekey_use_utc
|
|
975
|
-
(time_int - (time_int % @timekey)).to_i
|
|
998
|
+
@calculate_timekey_last_timekey = (time_int - (time_int % @timekey)).to_i
|
|
976
999
|
else
|
|
977
1000
|
offset = @calculate_offset ? @calculate_offset.call(time) : @offset
|
|
978
|
-
(time_int - ((time_int + offset)% @timekey)).to_i
|
|
1001
|
+
@calculate_timekey_last_timekey = (time_int - ((time_int + offset) % @timekey)).to_i
|
|
979
1002
|
end
|
|
980
1003
|
end
|
|
981
1004
|
|
|
@@ -1212,7 +1235,7 @@ module Fluent
|
|
|
1212
1235
|
end
|
|
1213
1236
|
end
|
|
1214
1237
|
|
|
1215
|
-
UNRECOVERABLE_ERRORS = [Fluent::UnrecoverableError, TypeError, ArgumentError, NoMethodError, MessagePack::UnpackError, EncodingError]
|
|
1238
|
+
UNRECOVERABLE_ERRORS = [Fluent::UnrecoverableError, TypeError, ArgumentError, NoMethodError, MessagePack::UnpackError, EncodingError, JSON::GeneratorError]
|
|
1216
1239
|
|
|
1217
1240
|
def try_flush
|
|
1218
1241
|
chunk = @buffer.dequeue_chunk
|
|
@@ -1319,9 +1342,14 @@ module Fluent
|
|
|
1319
1342
|
if @buffer.disable_chunk_backup
|
|
1320
1343
|
log.warn "disable_chunk_backup is true. #{dump_unique_id_hex(chunk.unique_id)} chunk is thrown away"
|
|
1321
1344
|
else
|
|
1322
|
-
|
|
1323
|
-
chunk.
|
|
1324
|
-
|
|
1345
|
+
begin
|
|
1346
|
+
@buffer.backup(chunk.unique_id) { |f|
|
|
1347
|
+
chunk.write_to(f)
|
|
1348
|
+
}
|
|
1349
|
+
rescue => e
|
|
1350
|
+
log.warn "failed to back up #{dump_unique_id_hex(chunk.unique_id)} chunk. it is thrown away", error: e
|
|
1351
|
+
log.warn_backtrace
|
|
1352
|
+
end
|
|
1325
1353
|
end
|
|
1326
1354
|
commit_write(chunk.unique_id, secondary: using_secondary, delayed: delayed_commit)
|
|
1327
1355
|
end
|
|
@@ -1634,5 +1662,27 @@ module Fluent
|
|
|
1634
1662
|
{ 'output' => stats }
|
|
1635
1663
|
end
|
|
1636
1664
|
end
|
|
1665
|
+
|
|
1666
|
+
class MetadataCache
|
|
1667
|
+
attr_reader :metadata
|
|
1668
|
+
|
|
1669
|
+
def initialize
|
|
1670
|
+
@timekey = nil
|
|
1671
|
+
@tag = nil
|
|
1672
|
+
@variables = nil
|
|
1673
|
+
@metadata = nil
|
|
1674
|
+
end
|
|
1675
|
+
|
|
1676
|
+
def cached?(timekey:, tag:, variables: nil)
|
|
1677
|
+
@metadata && @timekey == timekey && @tag == tag && @variables == variables
|
|
1678
|
+
end
|
|
1679
|
+
|
|
1680
|
+
def update(timekey:, tag:, variables: nil, metadata:)
|
|
1681
|
+
@timekey = timekey
|
|
1682
|
+
@tag = tag
|
|
1683
|
+
@variables = variables
|
|
1684
|
+
@metadata = metadata
|
|
1685
|
+
end
|
|
1686
|
+
end
|
|
1637
1687
|
end
|
|
1638
1688
|
end
|
|
@@ -32,7 +32,7 @@ module Fluent
|
|
|
32
32
|
|
|
33
33
|
def configure(conf)
|
|
34
34
|
super
|
|
35
|
-
|
|
35
|
+
@key_num = @keys.length
|
|
36
36
|
|
|
37
37
|
if @parser_engine == :fast
|
|
38
38
|
@quote_char = '"'
|
|
@@ -46,13 +46,18 @@ module Fluent
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def parse(text, &block)
|
|
49
|
+
r = {}
|
|
50
|
+
i = 0
|
|
49
51
|
values = CSV.parse_line(text, col_sep: @delimiter)
|
|
50
52
|
unless values
|
|
51
53
|
yield nil, nil
|
|
52
54
|
return
|
|
53
55
|
end
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
while i < @key_num
|
|
58
|
+
r[@keys[i]] = values[i]
|
|
59
|
+
i += 1
|
|
60
|
+
end
|
|
56
61
|
time, record = convert_values(parse_time(r), r)
|
|
57
62
|
yield time, record
|
|
58
63
|
end
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
#
|
|
16
16
|
|
|
17
|
+
require 'fluent/env'
|
|
17
18
|
require 'fluent/plugin/parser'
|
|
18
19
|
require 'fluent/time'
|
|
19
20
|
require 'fluent/oj_options'
|
|
20
21
|
|
|
21
|
-
require 'yajl'
|
|
22
22
|
require 'json'
|
|
23
23
|
|
|
24
24
|
module Fluent
|
|
@@ -28,16 +28,18 @@ module Fluent
|
|
|
28
28
|
|
|
29
29
|
config_set_default :time_key, 'time'
|
|
30
30
|
desc 'Set JSON parser'
|
|
31
|
+
# NOTE: Contains yajl for backward compatibility
|
|
31
32
|
config_param :json_parser, :enum, list: [:oj, :yajl, :json], default: :oj
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
# from IO streams, so maintain this for backwards-compatibility.
|
|
35
|
-
# https://www.rubydoc.info/github/brianmario/yajl-ruby/Yajl%2FParser:parse
|
|
36
|
-
desc 'Set the buffer size that Yajl will use when parsing streaming input'
|
|
34
|
+
desc 'Set the buffer size that JSON parser will use when parsing streaming input'
|
|
37
35
|
config_param :stream_buffer_size, :integer, default: 8192
|
|
38
36
|
|
|
39
37
|
config_set_default :time_type, :float
|
|
40
38
|
|
|
39
|
+
# Use a shared proc rather than a per-call lambda so that
|
|
40
|
+
# configure_json_parser returns the same object every time.
|
|
41
|
+
JSON_PARSE_PROC = ->(text) { JSON.parse(text, **Fluent::DEFAULT_JSON_PARSE_OPTIONS) }
|
|
42
|
+
|
|
41
43
|
def configure(conf)
|
|
42
44
|
if conf.has_key?('time_format')
|
|
43
45
|
conf['time_type'] ||= 'string'
|
|
@@ -54,8 +56,8 @@ module Fluent
|
|
|
54
56
|
|
|
55
57
|
log&.info "Oj is not installed, and failing back to JSON for json parser"
|
|
56
58
|
configure_json_parser(:json)
|
|
57
|
-
when :json
|
|
58
|
-
|
|
59
|
+
when :yajl, :json # NOTE: Fallback yajl to json for backward compatibility
|
|
60
|
+
[JSON_PARSE_PROC, JSON::ParserError]
|
|
59
61
|
else
|
|
60
62
|
raise "BUG: unknown json parser specified: #{name}"
|
|
61
63
|
end
|
|
@@ -94,11 +96,22 @@ module Fluent
|
|
|
94
96
|
end
|
|
95
97
|
|
|
96
98
|
def parse_io(io, &block)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
parser = JSON::ResumableParser.new(**Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
100
|
+
begin
|
|
101
|
+
chunk = +"".b
|
|
102
|
+
while io.readpartial(@stream_buffer_size, chunk)
|
|
103
|
+
parser << chunk
|
|
104
|
+
while parser.parse
|
|
105
|
+
record = parser.value
|
|
106
|
+
block.call(parse_time(record), record)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
rescue EOFError
|
|
110
|
+
unless parser.empty?
|
|
111
|
+
log&.warn "JSON stream ended in the middle of a document; " \
|
|
112
|
+
"discarding incomplete data"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
102
115
|
end
|
|
103
116
|
end
|
|
104
117
|
end
|
|
@@ -38,10 +38,8 @@ module Fluent
|
|
|
38
38
|
def parse(text)
|
|
39
39
|
r = {}
|
|
40
40
|
text.split(@delimiter).each do |pair|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
r[key] = value
|
|
44
|
-
end
|
|
41
|
+
key, value = pair.split(@label_delimiter, 2)
|
|
42
|
+
r[key] = value if value
|
|
45
43
|
end
|
|
46
44
|
time, record = convert_values(parse_time(r), r)
|
|
47
45
|
yield time, record
|
|
@@ -22,21 +22,22 @@ module Fluent
|
|
|
22
22
|
class MessagePackParser < Parser
|
|
23
23
|
Plugin.register_parser('msgpack', self)
|
|
24
24
|
|
|
25
|
-
def configure(conf)
|
|
26
|
-
super
|
|
27
|
-
@unpacker = Fluent::MessagePackFactory.engine_factory.unpacker
|
|
28
|
-
end
|
|
29
|
-
|
|
30
25
|
def parser_type
|
|
31
26
|
:binary
|
|
32
27
|
end
|
|
33
28
|
|
|
34
29
|
def parse(data, &block)
|
|
30
|
+
Fluent::MessagePackFactory.engine_factory.unpacker.feed_each(data) do |obj|
|
|
31
|
+
parse_unpacked_data(obj, &block)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parse_partial_data(data, &block)
|
|
36
|
+
@unpacker ||= Fluent::MessagePackFactory.engine_factory.unpacker
|
|
35
37
|
@unpacker.feed_each(data) do |obj|
|
|
36
38
|
parse_unpacked_data(obj, &block)
|
|
37
39
|
end
|
|
38
40
|
end
|
|
39
|
-
alias parse_partial_data parse
|
|
40
41
|
|
|
41
42
|
def parse_io(io, &block)
|
|
42
43
|
u = Fluent::MessagePackFactory.engine_factory.unpacker(io)
|
|
@@ -44,7 +44,7 @@ module Fluent
|
|
|
44
44
|
RFC3164_CAPTURES = RFC3164_WITHOUT_TIME_AND_PRI_REGEXP.names.freeze
|
|
45
45
|
RFC3164_PRI_REGEXP = /^<(?<pri>[0-9]{1,3})>/
|
|
46
46
|
|
|
47
|
-
RFC5424_WITHOUT_TIME_AND_PRI_REGEXP = /(?<host>[!-~]{1,255}) (?<ident>[!-~]{1,48}) (?<pid>[!-~]{1,128}) (?<msgid>[!-~]{1,32}) (?<extradata>(?:\-|(?:\[.*?(?<!\\)\])
|
|
47
|
+
RFC5424_WITHOUT_TIME_AND_PRI_REGEXP = /(?<host>[!-~]{1,255}) (?<ident>[!-~]{1,48}) (?<pid>[!-~]{1,128}) (?<msgid>[!-~]{1,32}) (?<extradata>(?:\-|(?:\[.*?(?<!\\)\])))(?: (?<message>.+))?\z/m
|
|
48
48
|
RFC5424_CAPTURES = RFC5424_WITHOUT_TIME_AND_PRI_REGEXP.names.freeze
|
|
49
49
|
RFC5424_PRI_REGEXP = /^<(?<pri>\d{1,3})>\d\d{0,2}\s/
|
|
50
50
|
|
|
@@ -71,6 +71,7 @@ module Fluent
|
|
|
71
71
|
@time_parser_rfc5424 = nil
|
|
72
72
|
@space_count_rfc3164 = nil
|
|
73
73
|
@space_count_rfc5424 = nil
|
|
74
|
+
@time_format_starts_with_space_rfc3164 = false
|
|
74
75
|
@skip_space_count_rfc3164 = false
|
|
75
76
|
@skip_space_count_rfc5424 = false
|
|
76
77
|
@time_parser_rfc5424_without_subseconds = nil
|
|
@@ -123,10 +124,12 @@ module Fluent
|
|
|
123
124
|
|
|
124
125
|
def setup_time_parser_3164(time_fmt)
|
|
125
126
|
@time_parser_rfc3164 = time_parser_create(format: time_fmt)
|
|
127
|
+
@time_format_starts_with_space_rfc3164 = time_fmt.start_with?(SPLIT_CHAR)
|
|
126
128
|
if ['%b %d %H:%M:%S', '%b %d %H:%M:%S.%N'].include?(time_fmt)
|
|
127
129
|
@skip_space_count_rfc3164 = true
|
|
128
130
|
end
|
|
129
131
|
@space_count_rfc3164 = time_fmt.squeeze(' ').count(' ') + 1
|
|
132
|
+
@space_count_rfc3164 -= 1 if @time_format_starts_with_space_rfc3164
|
|
130
133
|
end
|
|
131
134
|
|
|
132
135
|
def setup_time_parser_5424(time_fmt)
|
|
@@ -162,6 +165,8 @@ module Fluent
|
|
|
162
165
|
end
|
|
163
166
|
|
|
164
167
|
SPLIT_CHAR = ' '.freeze
|
|
168
|
+
DOT_CHAR = '.'.freeze
|
|
169
|
+
RFC3164_PRI_DIGITS_REGEXP = /\A[0-9]{1,3}\z/
|
|
165
170
|
|
|
166
171
|
def parse_rfc3164_regex(text, &block)
|
|
167
172
|
idx = 0
|
|
@@ -180,13 +185,26 @@ module Fluent
|
|
|
180
185
|
|
|
181
186
|
i = idx - 1
|
|
182
187
|
sq = false
|
|
188
|
+
first_time_field = true
|
|
183
189
|
@space_count_rfc3164.times do
|
|
184
190
|
while text[i + 1] == SPLIT_CHAR
|
|
191
|
+
if first_time_field
|
|
192
|
+
unless @time_format_starts_with_space_rfc3164
|
|
193
|
+
idx += 1
|
|
194
|
+
i += 1
|
|
195
|
+
break
|
|
196
|
+
end
|
|
197
|
+
end
|
|
185
198
|
sq = true
|
|
186
199
|
i += 1
|
|
187
200
|
end
|
|
188
201
|
|
|
202
|
+
first_time_field = false
|
|
189
203
|
i = text.index(SPLIT_CHAR, i + 1)
|
|
204
|
+
unless i
|
|
205
|
+
yield nil, nil
|
|
206
|
+
return
|
|
207
|
+
end
|
|
190
208
|
end
|
|
191
209
|
|
|
192
210
|
time_str = sq ? text.slice(idx, i - idx).squeeze(SPLIT_CHAR) : text.slice(idx, i - idx)
|
|
@@ -287,16 +305,30 @@ module Fluent
|
|
|
287
305
|
end
|
|
288
306
|
end
|
|
289
307
|
|
|
308
|
+
if text[cursor] == SPLIT_CHAR
|
|
309
|
+
cursor = rfc3164_space_cursor(text, cursor)
|
|
310
|
+
unless cursor
|
|
311
|
+
yield nil, nil
|
|
312
|
+
return
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
290
316
|
if @skip_space_count_rfc3164
|
|
291
317
|
# header part
|
|
292
318
|
time_size = 15 # skip Mmm dd hh:mm:ss
|
|
293
|
-
|
|
319
|
+
time_end_index = cursor + time_size
|
|
320
|
+
time_end = text[time_end_index]
|
|
321
|
+
|
|
294
322
|
if time_end == SPLIT_CHAR
|
|
295
323
|
time_str = text.slice(cursor, time_size)
|
|
296
324
|
cursor += 16 # time + ' '
|
|
297
|
-
elsif time_end ==
|
|
325
|
+
elsif time_end == DOT_CHAR
|
|
298
326
|
# support subsecond time
|
|
299
|
-
i = text.index(SPLIT_CHAR,
|
|
327
|
+
i = text.index(SPLIT_CHAR, time_end_index)
|
|
328
|
+
unless i
|
|
329
|
+
yield nil, nil
|
|
330
|
+
return
|
|
331
|
+
end
|
|
300
332
|
time_str = text.slice(cursor, i - cursor)
|
|
301
333
|
cursor = i + 1
|
|
302
334
|
else
|
|
@@ -312,14 +344,18 @@ module Fluent
|
|
|
312
344
|
i += 1
|
|
313
345
|
end
|
|
314
346
|
i = text.index(SPLIT_CHAR, i + 1)
|
|
347
|
+
unless i
|
|
348
|
+
yield nil, nil
|
|
349
|
+
return
|
|
350
|
+
end
|
|
315
351
|
end
|
|
316
352
|
|
|
317
|
-
time_str = sq ? text.slice(
|
|
353
|
+
time_str = sq ? text.slice(cursor, i - cursor).squeeze(SPLIT_CHAR) : text.slice(cursor, i - cursor)
|
|
318
354
|
cursor = i + 1
|
|
319
355
|
end
|
|
320
356
|
|
|
321
357
|
i = text.index(SPLIT_CHAR, cursor)
|
|
322
|
-
|
|
358
|
+
unless i
|
|
323
359
|
yield nil, nil
|
|
324
360
|
return
|
|
325
361
|
end
|
|
@@ -363,12 +399,23 @@ module Fluent
|
|
|
363
399
|
msg.chomp!
|
|
364
400
|
record['message'] = msg
|
|
365
401
|
|
|
366
|
-
|
|
402
|
+
begin
|
|
403
|
+
time = @time_parser_rfc3164.parse(time_str)
|
|
404
|
+
rescue Fluent::TimeParser::TimeParseError
|
|
405
|
+
yield nil, nil
|
|
406
|
+
return
|
|
407
|
+
end
|
|
367
408
|
record['time'] = time_str if @keep_time_key
|
|
368
409
|
|
|
369
410
|
yield time, record
|
|
370
411
|
end
|
|
371
412
|
|
|
413
|
+
def rfc3164_space_cursor(text, cursor)
|
|
414
|
+
return if @with_priority && !RFC3164_PRI_DIGITS_REGEXP.match?(text.slice(1, cursor - 2))
|
|
415
|
+
|
|
416
|
+
@time_format_starts_with_space_rfc3164 ? cursor : cursor + 1
|
|
417
|
+
end
|
|
418
|
+
|
|
372
419
|
NILVALUE = '-'.freeze
|
|
373
420
|
|
|
374
421
|
def parse_rfc5424(text, &block)
|
|
@@ -406,7 +453,7 @@ module Fluent
|
|
|
406
453
|
i = text.index(SPLIT_CHAR, i + 1)
|
|
407
454
|
end
|
|
408
455
|
|
|
409
|
-
time_str = sq ? text.slice(
|
|
456
|
+
time_str = sq ? text.slice(cursor, i - cursor).squeeze(SPLIT_CHAR) : text.slice(cursor, i - cursor)
|
|
410
457
|
cursor = i + 1
|
|
411
458
|
end
|
|
412
459
|
|