fluentd 1.19.3-x64-mingw32 → 1.19.4-x64-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/CHANGELOG.md +45 -0
- data/lib/fluent/command/cat.rb +1 -1
- data/lib/fluent/config/basic_parser.rb +0 -1
- data/lib/fluent/config/literal_parser.rb +18 -7
- data/lib/fluent/config/types.rb +10 -2
- data/lib/fluent/configurable.rb +1 -0
- data/lib/fluent/daemon.rb +2 -1
- data/lib/fluent/env.rb +1 -0
- data/lib/fluent/plugin/base.rb +7 -2
- data/lib/fluent/plugin/buf_file.rb +1 -1
- data/lib/fluent/plugin/buffer/chunk.rb +14 -6
- data/lib/fluent/plugin/buffer/file_chunk.rb +5 -3
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +5 -3
- data/lib/fluent/plugin/buffer.rb +23 -5
- 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/in_http.rb +83 -2
- data/lib/fluent/plugin/in_monitor_agent.rb +2 -1
- data/lib/fluent/plugin/in_sample.rb +2 -1
- data/lib/fluent/plugin/in_syslog.rb +70 -4
- data/lib/fluent/plugin/out_forward/connection_manager.rb +18 -0
- data/lib/fluent/plugin/out_forward.rb +13 -2
- data/lib/fluent/plugin/output.rb +32 -12
- data/lib/fluent/plugin/parser_json.rb +6 -1
- data/lib/fluent/plugin/parser_syslog.rb +3 -3
- data/lib/fluent/plugin/sd_file.rb +2 -1
- data/lib/fluent/plugin/storage_local.rb +2 -2
- data/lib/fluent/supervisor.rb +8 -1
- data/lib/fluent/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b109ce733ba054b3b03daa1f47658e8287dcb3b2e0a1ca2c0cca4defb8b1415a
|
|
4
|
+
data.tar.gz: d80e36b87699060e8c895d9fb332dc521f0ba9cdbb0b0f4c80dd996003fdca50
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec9c12b4dfa48360cd34ba349a84ab8777a079a1d8c35af4a28f94fd1db56691846cef5d6df96cd2ff690e1b467467b6f51c6672f956e90703bc02adb67654c1
|
|
7
|
+
data.tar.gz: 7af35f61b6d84663e4871f410e9f4d32116d59e5953ecf32797fbb3331f5caefff699d1a55820967b4b45d183ac0980a9776284e4d29ed94828264d5648d7992
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# v1.19
|
|
2
2
|
|
|
3
|
+
## Release v1.19.4 - 2026/09/28
|
|
4
|
+
|
|
5
|
+
### Bug Fix
|
|
6
|
+
|
|
7
|
+
* buffer: enforce decompression_size_limit on the chunk IO path https://github.com/fluent/fluentd/pull/5504
|
|
8
|
+
* buffer: clamp exported buffer size metrics to non-negative values https://github.com/fluent/fluentd/pull/5488
|
|
9
|
+
* buffer: fix spurious `BufferOverflowError` caused by `queue_size` leaking when a chunk purge fails https://github.com/fluent/fluentd/pull/5487
|
|
10
|
+
* buffer: fix `stage_byte_size` leak on staged to unstaged chunk demotion that could eventually raise spurious `BufferOverflowError` in plugins implementing `#format` https://github.com/fluent/fluentd/pull/5456
|
|
11
|
+
* in_syslog: enforce message_length_limit on TCP/TLS transport https://github.com/fluent/fluentd/pull/5502
|
|
12
|
+
* The default value of `message_length_limit` is changed from 2048 to 8192 to match rsyslog's default `MaxMessageSize`.
|
|
13
|
+
* output: fix incomplete path traversal check in extract_placeholders https://github.com/fluent/fluentd/pull/5501
|
|
14
|
+
* output: treat JSON::GeneratorError as unrecoverable error https://github.com/fluent/fluentd/pull/5423
|
|
15
|
+
* parser_syslog: fix NameError when RFC5424 timestamp has repeated spaces https://github.com/fluent/fluentd/pull/5500
|
|
16
|
+
* parser_syslog: fix NameError when RFC3164 timestamp has repeated spaces https://github.com/fluent/fluentd/pull/5497
|
|
17
|
+
* parser_syslog: avoid excessive backtracking when parsing malformed RFC5424 structured data https://github.com/fluent/fluentd/pull/5444
|
|
18
|
+
* chunk: ensure to close the Tempfile for decompressed data https://github.com/fluent/fluentd/pull/5486
|
|
19
|
+
* plugin base: bound the number of worker lock files by hashing the path into a fixed set of buckets https://github.com/fluent/fluentd/pull/5471
|
|
20
|
+
* out_forward: stop the endless "ack in response and chunk id in sent data are different" warning storm by discarding (instead of reusing) a keepalive socket whose ack failed or came back with a mismatched chunk id https://github.com/fluent/fluentd/pull/5445
|
|
21
|
+
* config: accept empty lines in quoted strings https://github.com/fluent/fluentd/pull/5478
|
|
22
|
+
* config: fix a config error when a single scalar value is given to an array option in YAML config syntax (for example `retryable_response_codes: 503`) https://github.com/fluent/fluentd/pull/5433
|
|
23
|
+
* supervisor: reduce memory usage of cleanup_lock_dir with huge number of lock files https://github.com/fluent/fluentd/pull/5472
|
|
24
|
+
|
|
25
|
+
### Enhancement
|
|
26
|
+
|
|
27
|
+
* in_http: add `<auth>` for basic authentication and `<security>` for client network allowlisting https://github.com/fluent/fluentd/pull/5503
|
|
28
|
+
|
|
29
|
+
### Misc
|
|
30
|
+
|
|
31
|
+
* gem: support json gem v3.x https://github.com/fluent/fluentd/pull/5493
|
|
32
|
+
* Add `allow_comments: true` option for json parser https://github.com/fluent/fluentd/pull/5432
|
|
33
|
+
* Set `allow_duplicate_key: true` for all `JSON.parse` call https://github.com/fluent/fluentd/pull/5430
|
|
34
|
+
* CI fixes
|
|
35
|
+
* https://github.com/fluent/fluentd/pull/5495
|
|
36
|
+
* https://github.com/fluent/fluentd/pull/5477
|
|
37
|
+
* https://github.com/fluent/fluentd/pull/5466
|
|
38
|
+
* https://github.com/fluent/fluentd/pull/5460
|
|
39
|
+
* https://github.com/fluent/fluentd/pull/5455
|
|
40
|
+
* https://github.com/fluent/fluentd/pull/5454
|
|
41
|
+
* https://github.com/fluent/fluentd/pull/5453
|
|
42
|
+
* https://github.com/fluent/fluentd/pull/5452
|
|
43
|
+
* https://github.com/fluent/fluentd/pull/5434
|
|
44
|
+
* https://github.com/fluent/fluentd/pull/5426
|
|
45
|
+
* https://github.com/fluent/fluentd/pull/5425
|
|
46
|
+
* https://github.com/fluent/fluentd/pull/5424
|
|
47
|
+
|
|
3
48
|
## Release v1.19.3 - 2026/06/25
|
|
4
49
|
|
|
5
50
|
### Bug Fix
|
data/lib/fluent/command/cat.rb
CHANGED
|
@@ -28,7 +28,6 @@ module Fluent
|
|
|
28
28
|
SPACING = /(?:[ \t\r\n]|\z|\#.*?(?:\z|[\r\n]))+/
|
|
29
29
|
ZERO_OR_MORE_SPACING = /(?:[ \t\r\n]|\z|\#.*?(?:\z|[\r\n]))*/
|
|
30
30
|
SPACING_WITHOUT_COMMENT = /(?:[ \t\r\n]|\z)+/
|
|
31
|
-
LINE_END_WITHOUT_SPACING_AND_COMMENT = /(?:\z|[\r\n])/
|
|
32
31
|
|
|
33
32
|
module ClassMethods
|
|
34
33
|
def symbol(string)
|
|
@@ -21,11 +21,21 @@ require 'yajl'
|
|
|
21
21
|
require 'socket'
|
|
22
22
|
require 'ripper'
|
|
23
23
|
|
|
24
|
+
require 'fluent/env'
|
|
24
25
|
require 'fluent/config/basic_parser'
|
|
25
26
|
|
|
26
27
|
module Fluent
|
|
27
28
|
module Config
|
|
28
29
|
class LiteralParser < BasicParser
|
|
30
|
+
# A physical line break (LF, CR, or CRLF) inside a quoted string.
|
|
31
|
+
# CRLF is normalized to LF so that the same config text does not produce a
|
|
32
|
+
# different value depending on whether the file was saved with LF or CRLF.
|
|
33
|
+
# A lone CR is kept as-is, and an escaped "\r\n" still produces CRLF.
|
|
34
|
+
LINE_BREAK = /\r\n|[\r\n]/
|
|
35
|
+
# A backslash immediately followed by a physical line break.
|
|
36
|
+
# It works as a line continuation, so both are stripped from the value.
|
|
37
|
+
LINE_CONTINUATION = /\\#{LINE_BREAK}/o
|
|
38
|
+
|
|
29
39
|
def self.unescape_char(c)
|
|
30
40
|
case c
|
|
31
41
|
when '"'
|
|
@@ -98,11 +108,10 @@ module Fluent
|
|
|
98
108
|
else
|
|
99
109
|
return string.join
|
|
100
110
|
end
|
|
101
|
-
elsif
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
skip(/[^"]#{LINE_END_WITHOUT_SPACING_AND_COMMENT}/o)
|
|
111
|
+
elsif skip(LINE_CONTINUATION)
|
|
112
|
+
next
|
|
113
|
+
elsif s = scan(LINE_BREAK)
|
|
114
|
+
string << (s == "\r\n" ? "\n" : s)
|
|
106
115
|
elsif s = scan(/\\./)
|
|
107
116
|
string << eval_escape_char(s[1,1])
|
|
108
117
|
elsif skip(/\#\{/)
|
|
@@ -125,6 +134,8 @@ module Fluent
|
|
|
125
134
|
string << "'"
|
|
126
135
|
elsif s = scan(/\\\\/)
|
|
127
136
|
string << "\\"
|
|
137
|
+
elsif s = scan(LINE_BREAK)
|
|
138
|
+
string << (s == "\r\n" ? "\n" : s)
|
|
128
139
|
elsif s = scan(/./)
|
|
129
140
|
string << s
|
|
130
141
|
else
|
|
@@ -241,7 +252,7 @@ EOM
|
|
|
241
252
|
# '{"foo":"bar", #' -> '{"foo":"bar"}' (to check)
|
|
242
253
|
parsed = nil
|
|
243
254
|
begin
|
|
244
|
-
parsed = JSON.parse(buffer + line_buffer.rstrip.sub(/,$/, '') + (is_array ? "]" : "}"))
|
|
255
|
+
parsed = JSON.parse(buffer + line_buffer.rstrip.sub(/,$/, '') + (is_array ? "]" : "}"), **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
245
256
|
rescue JSON::ParserError
|
|
246
257
|
# This '#' is in json string literals
|
|
247
258
|
end
|
|
@@ -276,7 +287,7 @@ EOM
|
|
|
276
287
|
|
|
277
288
|
line_buffer << char
|
|
278
289
|
begin
|
|
279
|
-
result = JSON.parse(buffer + line_buffer)
|
|
290
|
+
result = JSON.parse(buffer + line_buffer, **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
280
291
|
rescue JSON::ParserError
|
|
281
292
|
# Incomplete json string yet
|
|
282
293
|
end
|
data/lib/fluent/config/types.rb
CHANGED
|
@@ -201,7 +201,7 @@ module Fluent
|
|
|
201
201
|
return nil if val.nil?
|
|
202
202
|
|
|
203
203
|
param = if val.is_a?(String)
|
|
204
|
-
val.start_with?('{') ? JSON.parse(val) : Hash[val.strip.split(/\s*,\s*/).map{|v| v.split(':', 2)}]
|
|
204
|
+
val.start_with?('{') ? JSON.parse(val, **Fluent::DEFAULT_JSON_PARSE_OPTIONS) : Hash[val.strip.split(/\s*,\s*/).map{|v| v.split(':', 2)}]
|
|
205
205
|
else
|
|
206
206
|
val
|
|
207
207
|
end
|
|
@@ -228,7 +228,15 @@ module Fluent
|
|
|
228
228
|
return nil if val.nil?
|
|
229
229
|
|
|
230
230
|
param = if val.is_a?(String)
|
|
231
|
-
val.start_with?('[') ? JSON.parse(val) : val.strip.split(/\s*,\s*/)
|
|
231
|
+
val.start_with?('[') ? JSON.parse(val, **Fluent::DEFAULT_JSON_PARSE_OPTIONS) : val.strip.split(/\s*,\s*/)
|
|
232
|
+
elsif val.is_a?(Array)
|
|
233
|
+
val
|
|
234
|
+
elsif val.is_a?(Numeric) || val == true || val == false
|
|
235
|
+
# Wrap only the bare scalars that Psych/YAML actually produces
|
|
236
|
+
# here (nil is handled by the early return above). Any other
|
|
237
|
+
# type (Hash, Symbol, Time, arbitrary objects) falls through and
|
|
238
|
+
# still raises "array required" below.
|
|
239
|
+
[val]
|
|
232
240
|
else
|
|
233
241
|
val
|
|
234
242
|
end
|
data/lib/fluent/configurable.rb
CHANGED
data/lib/fluent/daemon.rb
CHANGED
|
@@ -5,9 +5,10 @@ here = File.dirname(__FILE__)
|
|
|
5
5
|
$LOAD_PATH << File.expand_path(File.join(here, '..'))
|
|
6
6
|
|
|
7
7
|
require 'serverengine'
|
|
8
|
+
require 'fluent/env'
|
|
8
9
|
require 'fluent/supervisor'
|
|
9
10
|
|
|
10
11
|
server_module = Fluent.const_get(ARGV[0])
|
|
11
12
|
worker_module = Fluent.const_get(ARGV[1])
|
|
12
|
-
params = JSON.parse(ARGV[2])
|
|
13
|
+
params = JSON.parse(ARGV[2], **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
13
14
|
ServerEngine::Daemon.run_server(server_module, worker_module) { Fluent::Supervisor.serverengine_config(params) }
|
data/lib/fluent/env.rb
CHANGED
|
@@ -26,6 +26,7 @@ module Fluent
|
|
|
26
26
|
DEFAULT_SOCKET_PATH = ENV['FLUENT_SOCKET'] || '/var/run/fluent/fluent.sock'
|
|
27
27
|
DEFAULT_BACKUP_DIR = ENV['FLUENT_BACKUP_DIR'] || '/tmp/fluent'
|
|
28
28
|
DEFAULT_OJ_OPTIONS = Fluent::OjOptions.load_env
|
|
29
|
+
DEFAULT_JSON_PARSE_OPTIONS = { allow_duplicate_key: true, allow_comments: true }.freeze
|
|
29
30
|
DEFAULT_DIR_PERMISSION = 0755
|
|
30
31
|
DEFAULT_FILE_PERMISSION = 0644
|
|
31
32
|
INSTANCE_ID = ENV['FLUENT_INSTANCE_ID'] || SecureRandom.uuid
|
data/lib/fluent/plugin/base.rb
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
#
|
|
16
16
|
|
|
17
|
+
require 'zlib'
|
|
17
18
|
require 'fluent/plugin'
|
|
18
19
|
require 'fluent/configurable'
|
|
19
20
|
require 'fluent/system_config'
|
|
@@ -69,9 +70,13 @@ module Fluent
|
|
|
69
70
|
true
|
|
70
71
|
end
|
|
71
72
|
|
|
73
|
+
LOCK_FILE_BUCKETS = 65536
|
|
74
|
+
|
|
72
75
|
def get_lock_path(name)
|
|
73
|
-
name
|
|
74
|
-
|
|
76
|
+
# The mapping from a name to a bucket MUST be identical across worker processes.
|
|
77
|
+
# Ruby's String#hash is randomly seeded per process and MUST NOT be used here.
|
|
78
|
+
bucket = Zlib.crc32(name.to_s) % LOCK_FILE_BUCKETS
|
|
79
|
+
File.join(@fluentd_lock_dir, "fluentd-bucket-#{bucket}.lock")
|
|
75
80
|
end
|
|
76
81
|
|
|
77
82
|
def acquire_worker_lock(name)
|
|
@@ -163,7 +163,7 @@ module Fluent
|
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
begin
|
|
166
|
-
chunk = Fluent::Plugin::Buffer::FileChunk.new(m, path, mode, compress: @compress) # file chunk resumes contents of metadata
|
|
166
|
+
chunk = Fluent::Plugin::Buffer::FileChunk.new(m, path, mode, compress: @compress, decompression_size_limit: @decompression_size_limit) # file chunk resumes contents of metadata
|
|
167
167
|
rescue Fluent::Plugin::Buffer::FileChunk::FileChunkError => e
|
|
168
168
|
exist_broken_file = true
|
|
169
169
|
handle_broken_files(path, mode, e)
|
|
@@ -220,9 +220,13 @@ module Fluent
|
|
|
220
220
|
Tempfile.new('decompressed-data')
|
|
221
221
|
end
|
|
222
222
|
output_io.binmode if output_io.is_a?(Tempfile)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
begin
|
|
224
|
+
decompress(input_io: chunk_io, output_io: output_io)
|
|
225
|
+
output_io.seek(0, IO::SEEK_SET)
|
|
226
|
+
yield output_io
|
|
227
|
+
ensure
|
|
228
|
+
output_io.close! if output_io.is_a?(Tempfile)
|
|
229
|
+
end
|
|
226
230
|
end
|
|
227
231
|
end
|
|
228
232
|
end
|
|
@@ -274,9 +278,13 @@ module Fluent
|
|
|
274
278
|
Tempfile.new('decompressed-data')
|
|
275
279
|
end
|
|
276
280
|
output_io.binmode if output_io.is_a?(Tempfile)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
281
|
+
begin
|
|
282
|
+
decompress(input_io: chunk_io, output_io: output_io, type: :zstd)
|
|
283
|
+
output_io.seek(0, IO::SEEK_SET)
|
|
284
|
+
yield output_io
|
|
285
|
+
ensure
|
|
286
|
+
output_io.close! if output_io.is_a?(Tempfile)
|
|
287
|
+
end
|
|
280
288
|
end
|
|
281
289
|
end
|
|
282
290
|
end
|
|
@@ -168,9 +168,11 @@ module Fluent
|
|
|
168
168
|
|
|
169
169
|
def open(**kwargs, &block)
|
|
170
170
|
@chunk.seek(0, IO::SEEK_SET)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
begin
|
|
172
|
+
yield @chunk
|
|
173
|
+
ensure
|
|
174
|
+
@chunk.seek(0, IO::SEEK_END) if self.staged?
|
|
175
|
+
end
|
|
174
176
|
end
|
|
175
177
|
|
|
176
178
|
def self.assume_chunk_state(path)
|
|
@@ -141,9 +141,11 @@ module Fluent
|
|
|
141
141
|
|
|
142
142
|
def open(**kwargs, &block)
|
|
143
143
|
@chunk.seek(0, IO::SEEK_SET)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
begin
|
|
145
|
+
yield @chunk
|
|
146
|
+
ensure
|
|
147
|
+
@chunk.seek(0, IO::SEEK_END) if self.staged?
|
|
148
|
+
end
|
|
147
149
|
end
|
|
148
150
|
|
|
149
151
|
def self.assume_chunk_state(path)
|
data/lib/fluent/plugin/buffer.rb
CHANGED
|
@@ -601,13 +601,19 @@ module Fluent
|
|
|
601
601
|
metadata = chunk.metadata
|
|
602
602
|
log.on_trace { log.trace "purging a chunk", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata }
|
|
603
603
|
|
|
604
|
+
bytesize = chunk.bytesize
|
|
604
605
|
begin
|
|
605
|
-
bytesize = chunk.bytesize
|
|
606
606
|
chunk.purge
|
|
607
|
-
@queue_size_metrics.sub(bytesize)
|
|
608
607
|
rescue => e
|
|
609
608
|
log.error "failed to purge buffer chunk", chunk_id: dump_unique_id_hex(chunk_id), error_class: e.class, error: e
|
|
610
609
|
log.error_backtrace
|
|
610
|
+
ensure
|
|
611
|
+
# Always release the queued byte counter, even when purge raises
|
|
612
|
+
# (e.g. unlink/close failure on the buffer path). Otherwise
|
|
613
|
+
# @queue_size is never decremented for a dequeued chunk that is
|
|
614
|
+
# gone from both @queue and @dequeued, so the leak ratchets toward
|
|
615
|
+
# total_limit_size and storable? becomes permanently false (#5468).
|
|
616
|
+
@queue_size_metrics.sub(bytesize)
|
|
611
617
|
end
|
|
612
618
|
|
|
613
619
|
@dequeued_num[chunk.metadata] -= 1
|
|
@@ -850,7 +856,12 @@ module Fluent
|
|
|
850
856
|
# As already processed content is kept after rollback, then unstaged chunk should be queued.
|
|
851
857
|
# After that, re-process current split again.
|
|
852
858
|
# New chunk should be allocated, to do it, modify @stage and so on.
|
|
853
|
-
synchronize
|
|
859
|
+
synchronize do
|
|
860
|
+
@stage.delete(modified_metadata)
|
|
861
|
+
# Subtract this chunk's already-counted staged bytes here;
|
|
862
|
+
# otherwise @stage_size leaks upward over the buffer's lifetime.
|
|
863
|
+
@stage_size_metrics.sub(original_bytesize) if chunk.staged?
|
|
864
|
+
end
|
|
854
865
|
staged_chunk_used = false
|
|
855
866
|
chunk.unstaged!
|
|
856
867
|
break
|
|
@@ -912,8 +923,15 @@ module Fluent
|
|
|
912
923
|
]
|
|
913
924
|
|
|
914
925
|
def statistics
|
|
915
|
-
|
|
916
|
-
|
|
926
|
+
# Export-only clamp: internal gauges may go transiently negative during
|
|
927
|
+
# the deferred stage_size add vs enqueue_chunk sub race (#5303, #2712).
|
|
928
|
+
# Clamping the gauge store itself would turn that into a permanent
|
|
929
|
+
# over-count and break Buffer#storable? -- keep raw gauge semantics.
|
|
930
|
+
stage_size = [@stage_size_metrics.get, 0].max
|
|
931
|
+
queue_size = [@queue_size_metrics.get, 0].max
|
|
932
|
+
denom = @total_limit_size.to_f
|
|
933
|
+
# denom > 0 already excludes 0/0 NaN; stage/queue are floored above.
|
|
934
|
+
buffer_space = denom > 0.0 ? (1.0 - (stage_size + queue_size).to_f / denom).clamp(0.0, 1.0) : 0.0
|
|
917
935
|
@stage_length_metrics.set(@stage.size)
|
|
918
936
|
@queue_length_metrics.set(@queue.size)
|
|
919
937
|
@available_buffer_space_ratios_metrics.set(buffer_space * 100)
|
|
@@ -75,9 +75,9 @@ module Fluent
|
|
|
75
75
|
|
|
76
76
|
def io_decompress(input, output, type = :gzip)
|
|
77
77
|
if type == :gzip
|
|
78
|
-
Extractor.io_decompress_gzip(input, output)
|
|
78
|
+
Extractor.io_decompress_gzip(input, output, limit: @decompression_size_limit || DEFAULT_DECOMPRESSION_SIZE_LIMIT)
|
|
79
79
|
elsif type == :zstd
|
|
80
|
-
Extractor.io_decompress_zstd(input, output)
|
|
80
|
+
Extractor.io_decompress_zstd(input, output, limit: @decompression_size_limit || DEFAULT_DECOMPRESSION_SIZE_LIMIT)
|
|
81
81
|
else
|
|
82
82
|
raise ArgumentError, "Unknown compression type: #{type}"
|
|
83
83
|
end
|
|
@@ -26,6 +26,9 @@ module Fluent
|
|
|
26
26
|
|
|
27
27
|
BYTES_TO_READ = 64 * 1024
|
|
28
28
|
INFLATE_BYTES_TO_READ = 1024
|
|
29
|
+
# Zstd::StreamReader#read takes a number of compressed bytes, unlike
|
|
30
|
+
# Zlib::GzipReader#read which takes decompressed bytes
|
|
31
|
+
ZSTD_BYTES_TO_READ = 1024
|
|
29
32
|
|
|
30
33
|
def self.decompress_gzip(compressed_data, limit:)
|
|
31
34
|
io = StringIO.new(compressed_data)
|
|
@@ -55,8 +58,7 @@ module Fluent
|
|
|
55
58
|
reader = Zstd::StreamReader.new(io)
|
|
56
59
|
out = ''
|
|
57
60
|
loop do
|
|
58
|
-
|
|
59
|
-
out << reader.read(BYTES_TO_READ)
|
|
61
|
+
out << reader.read(ZSTD_BYTES_TO_READ)
|
|
60
62
|
if out.bytesize > limit
|
|
61
63
|
raise SizeLimitError, "Decompressed data exceeds limit of #{limit} bytes"
|
|
62
64
|
end
|
|
@@ -88,10 +90,15 @@ module Fluent
|
|
|
88
90
|
out
|
|
89
91
|
end
|
|
90
92
|
|
|
91
|
-
def self.io_decompress_gzip(input, output)
|
|
93
|
+
def self.io_decompress_gzip(input, output, limit:)
|
|
94
|
+
size = 0
|
|
92
95
|
loop do
|
|
93
96
|
reader = Zlib::GzipReader.new(input)
|
|
94
97
|
while (chunk = reader.read(BYTES_TO_READ))
|
|
98
|
+
size += chunk.bytesize
|
|
99
|
+
if size > limit
|
|
100
|
+
raise SizeLimitError, "Decompressed data exceeds limit of #{limit} bytes"
|
|
101
|
+
end
|
|
95
102
|
output.write(chunk)
|
|
96
103
|
end
|
|
97
104
|
unused = reader.unused
|
|
@@ -105,11 +112,15 @@ module Fluent
|
|
|
105
112
|
output
|
|
106
113
|
end
|
|
107
114
|
|
|
108
|
-
def self.io_decompress_zstd(input, output)
|
|
115
|
+
def self.io_decompress_zstd(input, output, limit:)
|
|
109
116
|
reader = Zstd::StreamReader.new(input)
|
|
117
|
+
size = 0
|
|
110
118
|
loop do
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
chunk = reader.read(ZSTD_BYTES_TO_READ)
|
|
120
|
+
size += chunk.bytesize
|
|
121
|
+
if size > limit
|
|
122
|
+
raise SizeLimitError, "Decompressed data exceeds limit of #{limit} bytes"
|
|
123
|
+
end
|
|
113
124
|
output.write(chunk)
|
|
114
125
|
# Zstd::StreamReader doesn't provide unused data, so we have to manually adjust the position
|
|
115
126
|
break if input.eof?
|
|
@@ -18,6 +18,7 @@ require 'socket'
|
|
|
18
18
|
require 'json'
|
|
19
19
|
require 'ostruct'
|
|
20
20
|
|
|
21
|
+
require 'fluent/env'
|
|
21
22
|
require 'fluent/plugin/filter'
|
|
22
23
|
require 'fluent/config/error'
|
|
23
24
|
require 'fluent/event'
|
|
@@ -116,7 +117,7 @@ module Fluent::Plugin
|
|
|
116
117
|
|
|
117
118
|
def parse_value(value_str)
|
|
118
119
|
if value_str.start_with?('{', '[')
|
|
119
|
-
JSON.parse(value_str)
|
|
120
|
+
JSON.parse(value_str, **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
120
121
|
else
|
|
121
122
|
value_str
|
|
122
123
|
end
|
|
@@ -24,6 +24,8 @@ require 'webrick/httputils'
|
|
|
24
24
|
require 'uri'
|
|
25
25
|
require 'socket'
|
|
26
26
|
require 'json'
|
|
27
|
+
require 'ipaddr'
|
|
28
|
+
require 'openssl'
|
|
27
29
|
|
|
28
30
|
module Fluent::Plugin
|
|
29
31
|
class InHttpParser < Parser
|
|
@@ -90,6 +92,22 @@ module Fluent::Plugin
|
|
|
90
92
|
desc "Add prefix to incoming tag"
|
|
91
93
|
config_param :add_tag_prefix, :string, default: nil
|
|
92
94
|
|
|
95
|
+
config_section :auth, required: false, multi: false do
|
|
96
|
+
desc 'The method for HTTP authentication'
|
|
97
|
+
config_param :method, :enum, list: [:basic], default: :basic
|
|
98
|
+
desc 'The username for basic authentication'
|
|
99
|
+
config_param :username, :string
|
|
100
|
+
desc 'The password for basic authentication'
|
|
101
|
+
config_param :password, :string, secret: true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
config_section :security, required: false, multi: false do
|
|
105
|
+
config_section :client, param_name: :clients, required: false, multi: true do
|
|
106
|
+
desc 'Address or network of the client, IPv4 or IPv6. e.g. 192.168.1.10, 192.168.0.0/16, fd00::/8'
|
|
107
|
+
config_param :network, :string
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
93
111
|
config_section :parse do
|
|
94
112
|
config_set_default :@type, 'in_http'
|
|
95
113
|
end
|
|
@@ -100,6 +118,7 @@ module Fluent::Plugin
|
|
|
100
118
|
super
|
|
101
119
|
|
|
102
120
|
@km = nil
|
|
121
|
+
@nodes = []
|
|
103
122
|
@format_name = nil
|
|
104
123
|
@parser_time_key = nil
|
|
105
124
|
|
|
@@ -127,6 +146,8 @@ module Fluent::Plugin
|
|
|
127
146
|
|
|
128
147
|
raise Fluent::ConfigError, "'add_tag_prefix' parameter must not be empty" if @add_tag_prefix && @add_tag_prefix.empty?
|
|
129
148
|
|
|
149
|
+
configure_security if @security
|
|
150
|
+
|
|
130
151
|
m = if @parser_configs.first['@type'] == 'in_http'
|
|
131
152
|
@parser_msgpack = parser_create(usage: 'parser_in_http_msgpack', type: 'msgpack')
|
|
132
153
|
@parser_msgpack.time_key = nil
|
|
@@ -260,11 +281,33 @@ module Fluent::Plugin
|
|
|
260
281
|
|
|
261
282
|
private
|
|
262
283
|
|
|
284
|
+
def configure_security
|
|
285
|
+
@nodes = @security.clients.map do |client|
|
|
286
|
+
begin
|
|
287
|
+
IPAddr.new(client.network)
|
|
288
|
+
rescue ArgumentError
|
|
289
|
+
raise Fluent::ConfigError, "network '#{client.network}' address format is invalid"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def allowed_client?(address)
|
|
295
|
+
return true if @nodes.empty?
|
|
296
|
+
@nodes.any? { |node| node.include?(address) rescue false }
|
|
297
|
+
end
|
|
298
|
+
|
|
263
299
|
def on_server_connect(conn)
|
|
300
|
+
unless allowed_client?(conn.remote_addr)
|
|
301
|
+
log.warn "client address does not match any allowed network", address: conn.remote_addr
|
|
302
|
+
conn.data {|_data| }
|
|
303
|
+
conn.close
|
|
304
|
+
return
|
|
305
|
+
end
|
|
306
|
+
|
|
264
307
|
handler = Handler.new(conn, @km, method(:on_request),
|
|
265
308
|
@body_size_limit, @decompression_size_limit, @format_name, log,
|
|
266
309
|
@cors_allow_origins, @cors_allow_credentials,
|
|
267
|
-
@add_query_params)
|
|
310
|
+
@add_query_params, @auth)
|
|
268
311
|
|
|
269
312
|
conn.on(:data) do |data|
|
|
270
313
|
handler.on_read(data)
|
|
@@ -347,7 +390,7 @@ module Fluent::Plugin
|
|
|
347
390
|
attr_reader :content_type
|
|
348
391
|
|
|
349
392
|
def initialize(io, km, callback, body_size_limit, decompression_size_limit, format_name, log,
|
|
350
|
-
cors_allow_origins, cors_allow_credentials, add_query_params)
|
|
393
|
+
cors_allow_origins, cors_allow_credentials, add_query_params, auth = nil)
|
|
351
394
|
@io = io
|
|
352
395
|
@km = km
|
|
353
396
|
@callback = callback
|
|
@@ -360,6 +403,8 @@ module Fluent::Plugin
|
|
|
360
403
|
@cors_allow_credentials = cors_allow_credentials
|
|
361
404
|
@idle = 0
|
|
362
405
|
@add_query_params = add_query_params
|
|
406
|
+
@auth = auth
|
|
407
|
+
@authorization = nil
|
|
363
408
|
@km.add(self)
|
|
364
409
|
|
|
365
410
|
@remote_port, @remote_addr = io.remote_port, io.remote_addr
|
|
@@ -399,6 +444,7 @@ module Fluent::Plugin
|
|
|
399
444
|
@env = {}
|
|
400
445
|
@content_type = ""
|
|
401
446
|
@content_encoding = ""
|
|
447
|
+
@authorization = nil
|
|
402
448
|
headers.each_pair {|k,v|
|
|
403
449
|
@env["HTTP_#{k.tr('-','_').upcase}"] = v
|
|
404
450
|
case k
|
|
@@ -426,8 +472,22 @@ module Fluent::Plugin
|
|
|
426
472
|
@access_control_request_method = v
|
|
427
473
|
when /\AAccess-Control-Request-Headers\Z/i
|
|
428
474
|
@access_control_request_headers = v
|
|
475
|
+
when /\AAuthorization\Z/i
|
|
476
|
+
@authorization = v.is_a?(Array) ? v.first : v
|
|
429
477
|
end
|
|
430
478
|
}
|
|
479
|
+
|
|
480
|
+
if @auth
|
|
481
|
+
# Never let the credential reach a record built by add_http_headers.
|
|
482
|
+
@env.delete("HTTP_AUTHORIZATION")
|
|
483
|
+
|
|
484
|
+
unless preflight_request? || authenticate
|
|
485
|
+
@log.warn "authentication failed", address: @remote_addr
|
|
486
|
+
send_response_and_close(RES_401_STATUS, {'WWW-Authenticate' => AUTH_CHALLENGE}, "")
|
|
487
|
+
return
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
431
491
|
if expect
|
|
432
492
|
if expect == '100-continue'.freeze
|
|
433
493
|
if !size || size < @body_size_limit
|
|
@@ -442,6 +502,8 @@ module Fluent::Plugin
|
|
|
442
502
|
end
|
|
443
503
|
|
|
444
504
|
def on_body(chunk)
|
|
505
|
+
return if closing?
|
|
506
|
+
|
|
445
507
|
if @body.bytesize + chunk.bytesize > @body_size_limit
|
|
446
508
|
unless closing?
|
|
447
509
|
send_response_and_close("413 Request Entity Too Large", {}, "Too large")
|
|
@@ -452,6 +514,8 @@ module Fluent::Plugin
|
|
|
452
514
|
end
|
|
453
515
|
|
|
454
516
|
RES_200_STATUS = "200 OK".freeze
|
|
517
|
+
RES_401_STATUS = "401 Unauthorized".freeze
|
|
518
|
+
AUTH_CHALLENGE = 'Basic realm="fluentd"'.freeze
|
|
455
519
|
RES_403_STATUS = "403 Forbidden".freeze
|
|
456
520
|
|
|
457
521
|
# Azure App Service sends GET requests for health checking purpose.
|
|
@@ -651,6 +715,23 @@ module Fluent::Plugin
|
|
|
651
715
|
def parse_query(query)
|
|
652
716
|
query.nil? ? {} : Hash[URI.decode_www_form(query, Encoding::ASCII_8BIT)]
|
|
653
717
|
end
|
|
718
|
+
|
|
719
|
+
# Browsers never attach Authorization to a CORS preflight.
|
|
720
|
+
def preflight_request?
|
|
721
|
+
@parser.http_method == 'OPTIONS'.freeze
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def authenticate
|
|
725
|
+
scheme, credentials = @authorization.to_s.split(' ', 2)
|
|
726
|
+
return false unless scheme == 'Basic'.freeze
|
|
727
|
+
|
|
728
|
+
username, password = credentials.to_s.unpack1('m').to_s.split(':', 2)
|
|
729
|
+
username_matched = OpenSSL.secure_compare(username.to_s, @auth.username)
|
|
730
|
+
password_matched = OpenSSL.secure_compare(password.to_s, @auth.password)
|
|
731
|
+
username_matched && password_matched
|
|
732
|
+
rescue ArgumentError
|
|
733
|
+
false
|
|
734
|
+
end
|
|
654
735
|
end
|
|
655
736
|
end
|
|
656
737
|
end
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
require 'json'
|
|
18
18
|
|
|
19
|
+
require 'fluent/env'
|
|
19
20
|
require 'fluent/config/types'
|
|
20
21
|
require 'fluent/plugin/input'
|
|
21
22
|
require 'fluent/plugin/output'
|
|
@@ -101,7 +102,7 @@ module Fluent::Plugin
|
|
|
101
102
|
end
|
|
102
103
|
|
|
103
104
|
def render_ltsv(obj, code: 200)
|
|
104
|
-
normalized = JSON.parse(obj.to_json)
|
|
105
|
+
normalized = JSON.parse(obj.to_json, **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
105
106
|
text = ''
|
|
106
107
|
normalized.each do |hash|
|
|
107
108
|
row = []
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
require 'json'
|
|
18
18
|
|
|
19
|
+
require 'fluent/env'
|
|
19
20
|
require 'fluent/plugin/input'
|
|
20
21
|
require 'fluent/config/error'
|
|
21
22
|
|
|
@@ -44,7 +45,7 @@ module Fluent::Plugin
|
|
|
44
45
|
desc "The sample data to be generated. An array of JSON hashes or a single JSON hash."
|
|
45
46
|
config_param :sample, alias: :dummy, default: [{"message" => "sample"}] do |val|
|
|
46
47
|
begin
|
|
47
|
-
parsed = JSON.parse(val)
|
|
48
|
+
parsed = JSON.parse(val, **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
48
49
|
rescue JSON::ParserError => ex
|
|
49
50
|
# Fluent::ConfigParseError, "got incomplete JSON" will be raised
|
|
50
51
|
# at literal_parser.rb with --use-v1-config, but I had to
|
|
@@ -98,8 +98,8 @@ module Fluent::Plugin
|
|
|
98
98
|
desc 'The field name of the facility.'
|
|
99
99
|
config_param :facility_key, :string, default: nil
|
|
100
100
|
|
|
101
|
-
desc "The max bytes of message"
|
|
102
|
-
config_param :message_length_limit, :size, default:
|
|
101
|
+
desc "The max bytes of message. The messages exceeding this limit are dropped (UDP, TCP/TLS with traditional frame_type) or the connection is closed (TCP/TLS with octet_count frame_type)."
|
|
102
|
+
config_param :message_length_limit, :size, default: 8192
|
|
103
103
|
|
|
104
104
|
config_param :blocking_timeout, :time, default: 0.5
|
|
105
105
|
|
|
@@ -190,29 +190,95 @@ module Fluent::Plugin
|
|
|
190
190
|
resolve_name: @resolve_hostname,
|
|
191
191
|
send_keepalive_packet: @send_keepalive_packet
|
|
192
192
|
) do |conn|
|
|
193
|
+
discard_till_next_delimiter = false
|
|
193
194
|
conn.data do |data|
|
|
194
195
|
buffer = conn.buffer
|
|
195
196
|
buffer << data
|
|
196
197
|
pos = 0
|
|
197
198
|
if octet_count_frame
|
|
199
|
+
# Once an octet-counted stream contains an untrustworthy frame header, the stream
|
|
200
|
+
# can't be resynchronized. So close the connection instead of skipping bytes.
|
|
201
|
+
close_connection = false
|
|
198
202
|
while idx = buffer.index(delimiter, pos)
|
|
199
|
-
|
|
203
|
+
begin
|
|
204
|
+
num = Integer(buffer[pos..idx])
|
|
205
|
+
rescue ArgumentError
|
|
206
|
+
log.info "The message length is not a number, closing connection"
|
|
207
|
+
log.on_debug { log.debug "The message length is not a number:", head: buffer[pos, 32].dump }
|
|
208
|
+
close_connection = true
|
|
209
|
+
break
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if num < 0
|
|
213
|
+
log.info "The message length is negative, closing connection:", size: num
|
|
214
|
+
close_connection = true
|
|
215
|
+
break
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
if num > @message_length_limit
|
|
219
|
+
log.info "The message length exceeds 'message_length_limit', closing connection:", limit: @message_length_limit, size: num
|
|
220
|
+
close_connection = true
|
|
221
|
+
break
|
|
222
|
+
end
|
|
223
|
+
|
|
200
224
|
msg = buffer[idx + delimiter_size, num]
|
|
201
225
|
if msg.size != num
|
|
226
|
+
# The frame is not fully received yet.
|
|
202
227
|
break
|
|
203
228
|
end
|
|
204
229
|
|
|
205
230
|
pos = idx + delimiter_size + num
|
|
206
231
|
message_handler(msg, conn)
|
|
207
232
|
end
|
|
233
|
+
buffer.slice!(0, pos) if pos > 0
|
|
234
|
+
|
|
235
|
+
# If the remaining buffer has no delimiter, no message length can be parsed from it.
|
|
236
|
+
# Since a valid frame header is much shorter than the limit, such a buffer never
|
|
237
|
+
# becomes a valid frame. (Note that a partially received valid frame always has a
|
|
238
|
+
# delimiter after its length, so this doesn't drop it even if it slightly exceeds
|
|
239
|
+
# the limit with its length header.)
|
|
240
|
+
if !close_connection && !buffer.index(delimiter) && buffer.bytesize > @message_length_limit
|
|
241
|
+
log.info "The buffer size exceeds 'message_length_limit', closing connection:", limit: @message_length_limit, size: buffer.bytesize
|
|
242
|
+
log.on_debug { log.debug "The buffer size exceeds 'message_length_limit':", head: buffer[...32] }
|
|
243
|
+
close_connection = true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if close_connection
|
|
247
|
+
buffer.clear
|
|
248
|
+
conn.close
|
|
249
|
+
next
|
|
250
|
+
end
|
|
208
251
|
else
|
|
209
252
|
while idx = buffer.index(delimiter, pos)
|
|
210
253
|
msg = buffer[pos...idx]
|
|
211
254
|
pos = idx + delimiter_size
|
|
255
|
+
|
|
256
|
+
if discard_till_next_delimiter
|
|
257
|
+
discard_till_next_delimiter = false
|
|
258
|
+
next
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if msg.bytesize > @message_length_limit
|
|
262
|
+
log.info "The received data is larger than 'message_length_limit', dropped:", limit: @message_length_limit, size: msg.bytesize
|
|
263
|
+
log.on_debug { log.debug "The received data is larger than 'message_length_limit':", head: msg[...32] }
|
|
264
|
+
next
|
|
265
|
+
end
|
|
266
|
+
|
|
212
267
|
message_handler(msg, conn)
|
|
213
268
|
end
|
|
269
|
+
buffer.slice!(0, pos) if pos > 0
|
|
270
|
+
|
|
271
|
+
# If the buffer size exceeds the limit here, it means that the next message will
|
|
272
|
+
# definitely exceed the limit. So we should clear the buffer here. Otherwise, it will
|
|
273
|
+
# keep storing useless data until the next delimiter comes.
|
|
274
|
+
if buffer.bytesize > @message_length_limit
|
|
275
|
+
log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buffer.bytesize
|
|
276
|
+
log.on_debug { log.debug "The buffer size exceeds 'message_length_limit':", head: buffer[...32] }
|
|
277
|
+
buffer.clear
|
|
278
|
+
# We should discard the subsequent data until the next delimiter comes.
|
|
279
|
+
discard_till_next_delimiter = true
|
|
280
|
+
end
|
|
214
281
|
end
|
|
215
|
-
buffer.slice!(0, pos) if pos > 0
|
|
216
282
|
end
|
|
217
283
|
end
|
|
218
284
|
end
|
|
@@ -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)
|
|
@@ -525,17 +525,28 @@ module Fluent::Plugin
|
|
|
525
525
|
|
|
526
526
|
def ack_check(select_interval)
|
|
527
527
|
@ack_handler.collect_response(select_interval) do |chunk_id, node, sock, result|
|
|
528
|
-
@connection_manager.close(sock)
|
|
529
|
-
|
|
530
528
|
case result
|
|
531
529
|
when AckHandler::Result::SUCCESS
|
|
530
|
+
@connection_manager.close(sock)
|
|
532
531
|
commit_write(chunk_id)
|
|
533
532
|
when AckHandler::Result::FAILED
|
|
533
|
+
# The ack timed out or the peer closed the connection. A keepalive
|
|
534
|
+
# socket may still have an in-flight ack for this chunk, so drop it
|
|
535
|
+
# instead of returning it to the reuse pool; otherwise a late ack
|
|
536
|
+
# could later be read against a chunk sent on the reused socket (#4057).
|
|
537
|
+
@connection_manager.discard(sock)
|
|
534
538
|
node&.disable!
|
|
535
539
|
rollback_write(chunk_id, update_retry: false) if chunk_id
|
|
536
540
|
when AckHandler::Result::CHUNKID_UNMATCHED
|
|
541
|
+
# The ack read from this socket belongs to a different chunk than the
|
|
542
|
+
# one tracked for it: the keepalive connection's ack stream is out of
|
|
543
|
+
# sync (e.g. a stale ack left over from before an aggregator restart).
|
|
544
|
+
# Discard the socket so the following acks are not each offset by one,
|
|
545
|
+
# which would otherwise flood the log with mismatch warnings (#4057).
|
|
546
|
+
@connection_manager.discard(sock)
|
|
537
547
|
rollback_write(chunk_id, update_retry: false)
|
|
538
548
|
else
|
|
549
|
+
@connection_manager.close(sock)
|
|
539
550
|
log.warn("BUG: invalid status #{result} #{chunk_id}")
|
|
540
551
|
|
|
541
552
|
if chunk_id
|
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
|
|
@@ -1212,7 +1227,7 @@ module Fluent
|
|
|
1212
1227
|
end
|
|
1213
1228
|
end
|
|
1214
1229
|
|
|
1215
|
-
UNRECOVERABLE_ERRORS = [Fluent::UnrecoverableError, TypeError, ArgumentError, NoMethodError, MessagePack::UnpackError, EncodingError]
|
|
1230
|
+
UNRECOVERABLE_ERRORS = [Fluent::UnrecoverableError, TypeError, ArgumentError, NoMethodError, MessagePack::UnpackError, EncodingError, JSON::GeneratorError]
|
|
1216
1231
|
|
|
1217
1232
|
def try_flush
|
|
1218
1233
|
chunk = @buffer.dequeue_chunk
|
|
@@ -1319,9 +1334,14 @@ module Fluent
|
|
|
1319
1334
|
if @buffer.disable_chunk_backup
|
|
1320
1335
|
log.warn "disable_chunk_backup is true. #{dump_unique_id_hex(chunk.unique_id)} chunk is thrown away"
|
|
1321
1336
|
else
|
|
1322
|
-
|
|
1323
|
-
chunk.
|
|
1324
|
-
|
|
1337
|
+
begin
|
|
1338
|
+
@buffer.backup(chunk.unique_id) { |f|
|
|
1339
|
+
chunk.write_to(f)
|
|
1340
|
+
}
|
|
1341
|
+
rescue => e
|
|
1342
|
+
log.warn "failed to back up #{dump_unique_id_hex(chunk.unique_id)} chunk. it is thrown away", error: e
|
|
1343
|
+
log.warn_backtrace
|
|
1344
|
+
end
|
|
1325
1345
|
end
|
|
1326
1346
|
commit_write(chunk.unique_id, secondary: using_secondary, delayed: delayed_commit)
|
|
1327
1347
|
end
|
|
@@ -14,6 +14,7 @@
|
|
|
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'
|
|
@@ -38,6 +39,10 @@ module Fluent
|
|
|
38
39
|
|
|
39
40
|
config_set_default :time_type, :float
|
|
40
41
|
|
|
42
|
+
# Use a shared proc rather than a per-call lambda so that
|
|
43
|
+
# configure_json_parser returns the same object every time.
|
|
44
|
+
JSON_PARSE_PROC = ->(text) { JSON.parse(text, **Fluent::DEFAULT_JSON_PARSE_OPTIONS) }
|
|
45
|
+
|
|
41
46
|
def configure(conf)
|
|
42
47
|
if conf.has_key?('time_format')
|
|
43
48
|
conf['time_type'] ||= 'string'
|
|
@@ -54,7 +59,7 @@ module Fluent
|
|
|
54
59
|
|
|
55
60
|
log&.info "Oj is not installed, and failing back to JSON for json parser"
|
|
56
61
|
configure_json_parser(:json)
|
|
57
|
-
when :json then [
|
|
62
|
+
when :json then [JSON_PARSE_PROC, JSON::ParserError]
|
|
58
63
|
when :yajl then [Yajl.method(:load), Yajl::ParseError]
|
|
59
64
|
else
|
|
60
65
|
raise "BUG: unknown json parser specified: #{name}"
|
|
@@ -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
|
|
|
@@ -314,7 +314,7 @@ module Fluent
|
|
|
314
314
|
i = text.index(SPLIT_CHAR, i + 1)
|
|
315
315
|
end
|
|
316
316
|
|
|
317
|
-
time_str = sq ? text.slice(
|
|
317
|
+
time_str = sq ? text.slice(cursor, i - cursor).squeeze(SPLIT_CHAR) : text.slice(cursor, i - cursor)
|
|
318
318
|
cursor = i + 1
|
|
319
319
|
end
|
|
320
320
|
|
|
@@ -406,7 +406,7 @@ module Fluent
|
|
|
406
406
|
i = text.index(SPLIT_CHAR, i + 1)
|
|
407
407
|
end
|
|
408
408
|
|
|
409
|
-
time_str = sq ? text.slice(
|
|
409
|
+
time_str = sq ? text.slice(cursor, i - cursor).squeeze(SPLIT_CHAR) : text.slice(cursor, i - cursor)
|
|
410
410
|
cursor = i + 1
|
|
411
411
|
end
|
|
412
412
|
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
require 'cool.io'
|
|
18
18
|
|
|
19
|
+
require 'fluent/env'
|
|
19
20
|
require 'fluent/plugin_helper'
|
|
20
21
|
require 'fluent/plugin/service_discovery'
|
|
21
22
|
|
|
@@ -75,7 +76,7 @@ module Fluent
|
|
|
75
76
|
-> (v) { YAML.safe_load(v).map }
|
|
76
77
|
when :json
|
|
77
78
|
require 'json'
|
|
78
|
-
-> (v) { JSON.parse(v) }
|
|
79
|
+
-> (v) { JSON.parse(v, **Fluent::DEFAULT_JSON_PARSE_OPTIONS) }
|
|
79
80
|
end
|
|
80
81
|
end
|
|
81
82
|
|
|
@@ -90,7 +90,7 @@ module Fluent
|
|
|
90
90
|
log.warn "detect empty plugin storage file during startup. Ignored: #{@path}"
|
|
91
91
|
return
|
|
92
92
|
end
|
|
93
|
-
data = JSON.parse(data)
|
|
93
|
+
data = JSON.parse(data, **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
94
94
|
raise Fluent::ConfigError, "Invalid contents (not object) in plugin storage file: '#{@path}'" unless data.is_a?(Hash)
|
|
95
95
|
rescue => e
|
|
96
96
|
log.error "failed to read data from plugin storage file", path: @path, error: e
|
|
@@ -114,7 +114,7 @@ module Fluent
|
|
|
114
114
|
return unless File.exist?(@path)
|
|
115
115
|
begin
|
|
116
116
|
json_string = File.open(@path, 'r:utf-8:utf-8'){ |io| io.read }
|
|
117
|
-
json = JSON.parse(json_string)
|
|
117
|
+
json = JSON.parse(json_string, **Fluent::DEFAULT_JSON_PARSE_OPTIONS)
|
|
118
118
|
unless json.is_a?(Hash)
|
|
119
119
|
log.error "broken content for plugin storage (Hash required: ignored)", type: json.class
|
|
120
120
|
log.debug "broken content", content: json_string
|
data/lib/fluent/supervisor.rb
CHANGED
|
@@ -100,7 +100,14 @@ module Fluent
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def cleanup_lock_dir
|
|
103
|
-
|
|
103
|
+
begin
|
|
104
|
+
Dir.each_child(@fluentd_lock_dir) do |name|
|
|
105
|
+
FileUtils.rm_f(File.join(@fluentd_lock_dir, name)) if File.fnmatch?("fluentd-*.lock", name)
|
|
106
|
+
end
|
|
107
|
+
rescue Errno::ENOENT
|
|
108
|
+
# Directory is already missing. Fall through and let rmdir below
|
|
109
|
+
# fail in the same way as the current implementation.
|
|
110
|
+
end
|
|
104
111
|
FileUtils.rmdir(@fluentd_lock_dir)
|
|
105
112
|
end
|
|
106
113
|
|
data/lib/fluent/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fluentd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.19.
|
|
4
|
+
version: 1.19.4
|
|
5
5
|
platform: x64-mingw32
|
|
6
6
|
authors:
|
|
7
7
|
- Sadayuki Furuhashi
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: bundler
|
|
@@ -954,7 +953,6 @@ metadata:
|
|
|
954
953
|
source_code_uri: https://github.com/fluent/fluentd
|
|
955
954
|
changelog_uri: https://github.com/fluent/fluentd/blob/master/CHANGELOG.md
|
|
956
955
|
bug_tracker_uri: https://github.com/fluent/fluentd/issues
|
|
957
|
-
post_install_message:
|
|
958
956
|
rdoc_options: []
|
|
959
957
|
require_paths:
|
|
960
958
|
- lib
|
|
@@ -969,8 +967,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
969
967
|
- !ruby/object:Gem::Version
|
|
970
968
|
version: '0'
|
|
971
969
|
requirements: []
|
|
972
|
-
rubygems_version:
|
|
973
|
-
signing_key:
|
|
970
|
+
rubygems_version: 4.0.20
|
|
974
971
|
specification_version: 4
|
|
975
972
|
summary: Fluentd event collector
|
|
976
973
|
test_files: []
|