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
data/lib/fluent/log.rb
CHANGED
|
@@ -221,7 +221,7 @@ module Fluent
|
|
|
221
221
|
if wid = get_worker_id(type)
|
|
222
222
|
r['worker_id'] = wid
|
|
223
223
|
end
|
|
224
|
-
|
|
224
|
+
JSON.generate(r)
|
|
225
225
|
}
|
|
226
226
|
end
|
|
227
227
|
|
|
@@ -554,7 +554,7 @@ module Fluent
|
|
|
554
554
|
Thread.current[:last_repeated_stacktrace] = CachedLog.new(backtrace, time) if @suppress_repeated_stacktrace
|
|
555
555
|
end
|
|
556
556
|
|
|
557
|
-
puts
|
|
557
|
+
puts JSON.generate(r)
|
|
558
558
|
end
|
|
559
559
|
|
|
560
560
|
nil
|
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)
|
|
@@ -174,7 +174,7 @@ module Fluent
|
|
|
174
174
|
when :staged
|
|
175
175
|
# unstaged chunk created at Buffer#write_step_by_step is identified as the staged chunk here because FileChunk#assume_chunk_state checks only the file name.
|
|
176
176
|
# https://github.com/fluent/fluentd/blob/9d113029d4550ce576d8825bfa9612aa3e55bff0/lib/fluent/plugin/buffer.rb#L663
|
|
177
|
-
# This case can happen when fluentd process is killed by signal or other reasons between creating unstaged chunks and
|
|
177
|
+
# This case can happen when fluentd process is killed by signal or other reasons between creating unstaged chunks and enqueueing them (which renames them) in Buffer#write
|
|
178
178
|
# these chunks(unstaged chunks) has shared the same metadata
|
|
179
179
|
# So perform enqueue step again https://github.com/fluent/fluentd/blob/9d113029d4550ce576d8825bfa9612aa3e55bff0/lib/fluent/plugin/buffer.rb#L364
|
|
180
180
|
if chunk_size_full?(chunk) || stage.key?(chunk.metadata)
|
|
@@ -234,7 +234,7 @@ module Fluent
|
|
|
234
234
|
raise ArgumentError, "The chunk must be FileChunk, but it was #{chunk.class}."
|
|
235
235
|
end
|
|
236
236
|
|
|
237
|
-
backup_dir =
|
|
237
|
+
backup_dir = evacuate_dir
|
|
238
238
|
FileUtils.mkdir_p(backup_dir, mode: system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION) unless Dir.exist?(backup_dir)
|
|
239
239
|
|
|
240
240
|
FileUtils.copy([chunk.path, chunk.meta_path], backup_dir)
|
|
@@ -193,7 +193,11 @@ module Fluent
|
|
|
193
193
|
|
|
194
194
|
case chunk.state
|
|
195
195
|
when :staged
|
|
196
|
-
stage
|
|
196
|
+
if chunk_size_full?(chunk) || stage.key?(chunk.metadata)
|
|
197
|
+
queue << chunk.enqueued!
|
|
198
|
+
else
|
|
199
|
+
stage[chunk.metadata] = chunk
|
|
200
|
+
end
|
|
197
201
|
when :queued
|
|
198
202
|
queue << chunk
|
|
199
203
|
end
|
|
@@ -246,7 +250,7 @@ module Fluent
|
|
|
246
250
|
raise ArgumentError, "The chunk must be FileSingleChunk, but it was #{chunk.class}."
|
|
247
251
|
end
|
|
248
252
|
|
|
249
|
-
backup_dir =
|
|
253
|
+
backup_dir = evacuate_dir
|
|
250
254
|
FileUtils.mkdir_p(backup_dir, mode: system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION) unless Dir.exist?(backup_dir)
|
|
251
255
|
|
|
252
256
|
FileUtils.copy(chunk.path, backup_dir)
|
|
@@ -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
|
|
@@ -100,8 +100,14 @@ module Fluent
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def enqueued!
|
|
103
|
-
return unless self.
|
|
103
|
+
return self unless self.writable?
|
|
104
104
|
|
|
105
|
+
super
|
|
106
|
+
rename_to_queued_path
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def rename_to_queued_path
|
|
105
111
|
new_chunk_path = self.class.generate_queued_chunk_path(@path, @unique_id)
|
|
106
112
|
new_meta_path = new_chunk_path + '.meta'
|
|
107
113
|
|
|
@@ -139,9 +145,8 @@ module Fluent
|
|
|
139
145
|
|
|
140
146
|
@path = new_chunk_path
|
|
141
147
|
@meta_path = new_meta_path
|
|
142
|
-
|
|
143
|
-
super
|
|
144
148
|
end
|
|
149
|
+
private :rename_to_queued_path
|
|
145
150
|
|
|
146
151
|
def close
|
|
147
152
|
super
|
|
@@ -168,9 +173,11 @@ module Fluent
|
|
|
168
173
|
|
|
169
174
|
def open(**kwargs, &block)
|
|
170
175
|
@chunk.seek(0, IO::SEEK_SET)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
176
|
+
begin
|
|
177
|
+
yield @chunk
|
|
178
|
+
ensure
|
|
179
|
+
@chunk.seek(0, IO::SEEK_END) if self.staged?
|
|
180
|
+
end
|
|
174
181
|
end
|
|
175
182
|
|
|
176
183
|
def self.assume_chunk_state(path)
|
|
@@ -264,19 +271,27 @@ module Fluent
|
|
|
264
271
|
|
|
265
272
|
def file_rename(file, old_path, new_path, callback=nil)
|
|
266
273
|
pos = file.pos
|
|
274
|
+
setup = ->(f) {
|
|
275
|
+
f.set_encoding(Encoding::ASCII_8BIT)
|
|
276
|
+
f.sync = true
|
|
277
|
+
f.binmode
|
|
278
|
+
f.pos = pos
|
|
279
|
+
callback.call(f) if callback
|
|
280
|
+
}
|
|
267
281
|
if Fluent.windows?
|
|
268
282
|
file.close
|
|
269
|
-
|
|
270
|
-
|
|
283
|
+
renamed = false
|
|
284
|
+
begin
|
|
285
|
+
File.rename(old_path, new_path)
|
|
286
|
+
renamed = true
|
|
287
|
+
ensure
|
|
288
|
+
setup.call(File.open(renamed ? new_path : old_path, 'rb', @permission))
|
|
289
|
+
end
|
|
271
290
|
else
|
|
272
291
|
File.rename(old_path, new_path)
|
|
273
292
|
file.reopen(new_path, 'rb')
|
|
293
|
+
setup.call(file)
|
|
274
294
|
end
|
|
275
|
-
file.set_encoding(Encoding::ASCII_8BIT)
|
|
276
|
-
file.sync = true
|
|
277
|
-
file.binmode
|
|
278
|
-
file.pos = pos
|
|
279
|
-
callback.call(file) if callback
|
|
280
295
|
end
|
|
281
296
|
|
|
282
297
|
def create_new_chunk(path, perm)
|
|
@@ -93,8 +93,14 @@ module Fluent
|
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def enqueued!
|
|
96
|
-
return unless self.
|
|
96
|
+
return self unless self.writable?
|
|
97
97
|
|
|
98
|
+
super
|
|
99
|
+
rename_to_queued_path
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def rename_to_queued_path
|
|
98
104
|
new_chunk_path = self.class.generate_queued_chunk_path(@path, @unique_id)
|
|
99
105
|
|
|
100
106
|
begin
|
|
@@ -114,9 +120,8 @@ module Fluent
|
|
|
114
120
|
end
|
|
115
121
|
|
|
116
122
|
@path = new_chunk_path
|
|
117
|
-
|
|
118
|
-
super
|
|
119
123
|
end
|
|
124
|
+
private :rename_to_queued_path
|
|
120
125
|
|
|
121
126
|
def close
|
|
122
127
|
super
|
|
@@ -141,9 +146,11 @@ module Fluent
|
|
|
141
146
|
|
|
142
147
|
def open(**kwargs, &block)
|
|
143
148
|
@chunk.seek(0, IO::SEEK_SET)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
begin
|
|
150
|
+
yield @chunk
|
|
151
|
+
ensure
|
|
152
|
+
@chunk.seek(0, IO::SEEK_END) if self.staged?
|
|
153
|
+
end
|
|
147
154
|
end
|
|
148
155
|
|
|
149
156
|
def self.assume_chunk_state(path)
|
|
@@ -223,19 +230,27 @@ module Fluent
|
|
|
223
230
|
|
|
224
231
|
def file_rename(file, old_path, new_path, callback = nil)
|
|
225
232
|
pos = file.pos
|
|
233
|
+
setup = ->(f) {
|
|
234
|
+
f.set_encoding(Encoding::ASCII_8BIT)
|
|
235
|
+
f.sync = true
|
|
236
|
+
f.binmode
|
|
237
|
+
f.pos = pos
|
|
238
|
+
callback.call(f) if callback
|
|
239
|
+
}
|
|
226
240
|
if Fluent.windows?
|
|
227
241
|
file.close
|
|
228
|
-
|
|
229
|
-
|
|
242
|
+
renamed = false
|
|
243
|
+
begin
|
|
244
|
+
File.rename(old_path, new_path)
|
|
245
|
+
renamed = true
|
|
246
|
+
ensure
|
|
247
|
+
setup.call(File.open(renamed ? new_path : old_path, 'rb', @permission))
|
|
248
|
+
end
|
|
230
249
|
else
|
|
231
250
|
File.rename(old_path, new_path)
|
|
232
251
|
file.reopen(new_path, 'rb')
|
|
252
|
+
setup.call(file)
|
|
233
253
|
end
|
|
234
|
-
file.set_encoding(Encoding::ASCII_8BIT)
|
|
235
|
-
file.sync = true
|
|
236
|
-
file.binmode
|
|
237
|
-
file.pos = pos
|
|
238
|
-
callback.call(file) if callback
|
|
239
254
|
end
|
|
240
255
|
|
|
241
256
|
ESCAPE_REGEXP = /[^-_.a-zA-Z0-9]/n
|
data/lib/fluent/plugin/buffer.rb
CHANGED
|
@@ -72,6 +72,9 @@ module Fluent
|
|
|
72
72
|
desc 'If true, chunks are thrown away when unrecoverable error happens'
|
|
73
73
|
config_param :disable_chunk_backup, :bool, default: false
|
|
74
74
|
|
|
75
|
+
desc 'The approximate size limit of the evacuated chunk files in the evacuation directory. When the retry limit is reached, queued chunks that fit within the remaining capacity are evacuated, and the others are purged without being evacuated. The total size may exceed this limit due to chunk metadata or concurrent evacuation by multiple workers. Set to 0 to disable evacuation. nil (the default) means no limit.'
|
|
76
|
+
config_param :evacuate_limit_size, :size, default: nil
|
|
77
|
+
|
|
75
78
|
Metadata = Struct.new(:timekey, :tag, :variables, :seq) do
|
|
76
79
|
def initialize(timekey, tag, variables)
|
|
77
80
|
super(timekey, tag, variables, 0)
|
|
@@ -227,6 +230,20 @@ module Fluent
|
|
|
227
230
|
unless @queue_limit_length.nil?
|
|
228
231
|
@total_limit_size = @chunk_limit_size * @queue_limit_length
|
|
229
232
|
end
|
|
233
|
+
if @evacuate_limit_size
|
|
234
|
+
raise Fluent::ConfigError, "evacuate_limit_size must be 0 or larger" if @evacuate_limit_size < 0
|
|
235
|
+
if !persistent?
|
|
236
|
+
log.warn "evacuate_limit_size is ignored because this buffer does not evacuate chunks"
|
|
237
|
+
@evacuate_limit_size = nil
|
|
238
|
+
elsif @evacuate_limit_size > 0
|
|
239
|
+
unless owner.plugin_id_configured?
|
|
240
|
+
log.warn "evacuate_limit_size should be used with @id, because the evacuation directory without @id can change when the configuration is changed or reloaded"
|
|
241
|
+
end
|
|
242
|
+
if @evacuate_limit_size < @chunk_limit_size
|
|
243
|
+
log.warn "evacuate_limit_size is smaller than chunk_limit_size, so full chunks may not be evacuated", evacuate_limit_size: @evacuate_limit_size, chunk_limit_size: @chunk_limit_size
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
230
247
|
@stage_length_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "stage_length",
|
|
231
248
|
help_text: 'Length of stage buffers', prefer_gauge: true)
|
|
232
249
|
@stage_length_metrics.set(0)
|
|
@@ -383,6 +400,12 @@ module Fluent
|
|
|
383
400
|
if enqueue || first_chunk.unstaged? || chunk_size_full?(first_chunk)
|
|
384
401
|
chunks_to_enqueue << first_chunk
|
|
385
402
|
end
|
|
403
|
+
if (bytesize = staged_bytesizes_by_chunk[first_chunk])
|
|
404
|
+
# Account while the chunk lock is still held so enqueue_chunk
|
|
405
|
+
# cannot remove the chunk and subtract its bytes first.
|
|
406
|
+
@stage_size_metrics.add(bytesize)
|
|
407
|
+
log.on_trace { log.trace { "chunk #{first_chunk.path} size_added: #{bytesize} new_size: #{first_chunk.bytesize}" } }
|
|
408
|
+
end
|
|
386
409
|
first_chunk.mon_exit
|
|
387
410
|
rescue
|
|
388
411
|
operated_chunks.unshift(first_chunk)
|
|
@@ -397,6 +420,10 @@ module Fluent
|
|
|
397
420
|
if enqueue || chunk.unstaged? || chunk_size_full?(chunk)
|
|
398
421
|
chunks_to_enqueue << chunk
|
|
399
422
|
end
|
|
423
|
+
if (bytesize = staged_bytesizes_by_chunk[chunk])
|
|
424
|
+
@stage_size_metrics.add(bytesize)
|
|
425
|
+
log.on_trace { log.trace { "chunk #{chunk.path} size_added: #{bytesize} new_size: #{chunk.bytesize}" } }
|
|
426
|
+
end
|
|
400
427
|
chunk.mon_exit
|
|
401
428
|
rescue => e
|
|
402
429
|
chunk.rollback
|
|
@@ -405,44 +432,46 @@ module Fluent
|
|
|
405
432
|
end
|
|
406
433
|
end
|
|
407
434
|
|
|
408
|
-
# All locks about chunks are released.
|
|
409
|
-
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
# FIX FOR stage_size miscomputation - https://github.com/fluent/fluentd/issues/2712
|
|
413
|
-
#
|
|
414
|
-
staged_bytesizes_by_chunk.each do |chunk, bytesize|
|
|
415
|
-
chunk.synchronize do
|
|
416
|
-
synchronize { @stage_size_metrics.add(bytesize) }
|
|
417
|
-
log.on_trace { log.trace { "chunk #{chunk.path} size_added: #{bytesize} new_size: #{chunk.bytesize}" } }
|
|
418
|
-
end
|
|
419
|
-
end
|
|
435
|
+
# All locks about chunks are released. Stage-size accounting for the
|
|
436
|
+
# staged chunks happened inside each chunk monitor above, so a
|
|
437
|
+
# concurrent enqueue_chunk cannot observe a transiently negative
|
|
438
|
+
# stage_size (issue #2712 race).
|
|
420
439
|
|
|
440
|
+
enqueue_errors = []
|
|
421
441
|
chunks_to_enqueue.each do |c|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
442
|
+
begin
|
|
443
|
+
if c.staged? && (enqueue || chunk_size_full?(c))
|
|
444
|
+
m = c.metadata
|
|
445
|
+
enqueue_chunk(m)
|
|
446
|
+
if unstaged_chunks[m] && !unstaged_chunks[m].empty?
|
|
447
|
+
u = unstaged_chunks[m].pop
|
|
448
|
+
u.synchronize do
|
|
449
|
+
if u.unstaged? && !chunk_size_full?(u)
|
|
450
|
+
# `u.metadata.seq` and `m.seq` can be different but Buffer#enqueue_chunk expect them to be the same value
|
|
451
|
+
u.metadata.seq = 0
|
|
452
|
+
synchronize {
|
|
453
|
+
@stage[m] = u.staged!
|
|
454
|
+
@stage_size_metrics.add(u.bytesize)
|
|
455
|
+
}
|
|
456
|
+
end
|
|
435
457
|
end
|
|
436
458
|
end
|
|
459
|
+
elsif c.unstaged?
|
|
460
|
+
enqueue_unstaged_chunk(c)
|
|
461
|
+
else
|
|
462
|
+
# previously staged chunk is already enqueued, closed or purged.
|
|
463
|
+
# no problem.
|
|
437
464
|
end
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
else
|
|
441
|
-
# previously staged chunk is already enqueued, closed or purged.
|
|
442
|
-
# no problem.
|
|
465
|
+
rescue => e
|
|
466
|
+
enqueue_errors << e
|
|
443
467
|
end
|
|
444
468
|
end
|
|
445
469
|
|
|
470
|
+
enqueue_errors.each do |e|
|
|
471
|
+
log.warn "error occurs in enqueueing a chunk", error: e
|
|
472
|
+
log.warn_backtrace e.backtrace
|
|
473
|
+
end
|
|
474
|
+
|
|
446
475
|
operated_chunks.clear if errors.empty?
|
|
447
476
|
|
|
448
477
|
if errors.size > 0
|
|
@@ -492,6 +521,9 @@ module Fluent
|
|
|
492
521
|
|
|
493
522
|
chunk.synchronize do
|
|
494
523
|
synchronize do
|
|
524
|
+
bytesize = chunk.bytesize
|
|
525
|
+
@stage_size_metrics.sub(bytesize)
|
|
526
|
+
@queue_size_metrics.add(bytesize)
|
|
495
527
|
if chunk.empty?
|
|
496
528
|
chunk.close
|
|
497
529
|
else
|
|
@@ -500,9 +532,6 @@ module Fluent
|
|
|
500
532
|
@queued_num[metadata] = @queued_num.fetch(metadata, 0) + 1
|
|
501
533
|
chunk.enqueued!
|
|
502
534
|
end
|
|
503
|
-
bytesize = chunk.bytesize
|
|
504
|
-
@stage_size_metrics.sub(bytesize)
|
|
505
|
-
@queue_size_metrics.add(bytesize)
|
|
506
535
|
end
|
|
507
536
|
end
|
|
508
537
|
nil
|
|
@@ -517,9 +546,9 @@ module Fluent
|
|
|
517
546
|
metadata.seq = 0 # metadata.seq should be 0 for counting @queued_num
|
|
518
547
|
@queue << chunk
|
|
519
548
|
@queued_num[metadata] = @queued_num.fetch(metadata, 0) + 1
|
|
549
|
+
@queue_size_metrics.add(chunk.bytesize)
|
|
520
550
|
chunk.enqueued!
|
|
521
551
|
end
|
|
522
|
-
@queue_size_metrics.add(chunk.bytesize)
|
|
523
552
|
end
|
|
524
553
|
end
|
|
525
554
|
|
|
@@ -601,13 +630,19 @@ module Fluent
|
|
|
601
630
|
metadata = chunk.metadata
|
|
602
631
|
log.on_trace { log.trace "purging a chunk", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata }
|
|
603
632
|
|
|
633
|
+
bytesize = chunk.bytesize
|
|
604
634
|
begin
|
|
605
|
-
bytesize = chunk.bytesize
|
|
606
635
|
chunk.purge
|
|
607
|
-
@queue_size_metrics.sub(bytesize)
|
|
608
636
|
rescue => e
|
|
609
637
|
log.error "failed to purge buffer chunk", chunk_id: dump_unique_id_hex(chunk_id), error_class: e.class, error: e
|
|
610
638
|
log.error_backtrace
|
|
639
|
+
ensure
|
|
640
|
+
# Always release the queued byte counter, even when purge raises
|
|
641
|
+
# (e.g. unlink/close failure on the buffer path). Otherwise
|
|
642
|
+
# @queue_size is never decremented for a dequeued chunk that is
|
|
643
|
+
# gone from both @queue and @dequeued, so the leak ratchets toward
|
|
644
|
+
# total_limit_size and storable? becomes permanently false (#5468).
|
|
645
|
+
@queue_size_metrics.sub(bytesize)
|
|
611
646
|
end
|
|
612
647
|
|
|
613
648
|
@dequeued_num[chunk.metadata] -= 1
|
|
@@ -624,12 +659,27 @@ module Fluent
|
|
|
624
659
|
def clear_queue!
|
|
625
660
|
log.on_trace { log.trace "clearing queue", instance: self.object_id }
|
|
626
661
|
|
|
662
|
+
evacuate_budget = nil
|
|
663
|
+
skipped_num = 0
|
|
664
|
+
skipped_bytesize = 0
|
|
665
|
+
|
|
627
666
|
synchronize do
|
|
628
667
|
until @queue.empty?
|
|
629
668
|
begin
|
|
630
669
|
q = @queue.shift
|
|
631
|
-
|
|
632
|
-
|
|
670
|
+
if @evacuate_limit_size.nil?
|
|
671
|
+
evacuate_chunk(q)
|
|
672
|
+
elsif @evacuate_limit_size > 0
|
|
673
|
+
evacuate_budget ||= remaining_evacuate_size
|
|
674
|
+
if q.bytesize <= evacuate_budget
|
|
675
|
+
evacuate_chunk(q)
|
|
676
|
+
evacuate_budget -= q.bytesize
|
|
677
|
+
else
|
|
678
|
+
skipped_num += 1
|
|
679
|
+
skipped_bytesize += q.bytesize
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
log.trace("purging a chunk in queue"){ {id: dump_unique_id_hex(q.unique_id), bytesize: q.bytesize, size: q.size} }
|
|
633
683
|
q.purge
|
|
634
684
|
rescue => e
|
|
635
685
|
log.error "unexpected error while clearing buffer queue", error_class: e.class, error: e
|
|
@@ -638,6 +688,10 @@ module Fluent
|
|
|
638
688
|
end
|
|
639
689
|
@queue_size_metrics.set(0)
|
|
640
690
|
end
|
|
691
|
+
|
|
692
|
+
if skipped_num > 0
|
|
693
|
+
log.warn "some chunks are purged without being evacuated to stay within evacuate_limit_size.", evacuate_limit_size: @evacuate_limit_size, chunks: skipped_num, bytesize: skipped_bytesize
|
|
694
|
+
end
|
|
641
695
|
end
|
|
642
696
|
|
|
643
697
|
def evacuate_chunk(chunk)
|
|
@@ -850,7 +904,12 @@ module Fluent
|
|
|
850
904
|
# As already processed content is kept after rollback, then unstaged chunk should be queued.
|
|
851
905
|
# After that, re-process current split again.
|
|
852
906
|
# New chunk should be allocated, to do it, modify @stage and so on.
|
|
853
|
-
synchronize
|
|
907
|
+
synchronize do
|
|
908
|
+
@stage.delete(modified_metadata)
|
|
909
|
+
# Subtract this chunk's already-counted staged bytes here;
|
|
910
|
+
# otherwise @stage_size leaks upward over the buffer's lifetime.
|
|
911
|
+
@stage_size_metrics.sub(original_bytesize) if chunk.staged?
|
|
912
|
+
end
|
|
854
913
|
staged_chunk_used = false
|
|
855
914
|
chunk.unstaged!
|
|
856
915
|
break
|
|
@@ -912,8 +971,15 @@ module Fluent
|
|
|
912
971
|
]
|
|
913
972
|
|
|
914
973
|
def statistics
|
|
915
|
-
|
|
916
|
-
|
|
974
|
+
# Export-only clamp: internal gauges may go transiently negative during
|
|
975
|
+
# the deferred stage_size add vs enqueue_chunk sub race (#5303, #2712).
|
|
976
|
+
# Clamping the gauge store itself would turn that into a permanent
|
|
977
|
+
# over-count and break Buffer#storable? -- keep raw gauge semantics.
|
|
978
|
+
stage_size = [@stage_size_metrics.get, 0].max
|
|
979
|
+
queue_size = [@queue_size_metrics.get, 0].max
|
|
980
|
+
denom = @total_limit_size.to_f
|
|
981
|
+
# denom > 0 already excludes 0/0 NaN; stage/queue are floored above.
|
|
982
|
+
buffer_space = denom > 0.0 ? (1.0 - (stage_size + queue_size).to_f / denom).clamp(0.0, 1.0) : 0.0
|
|
917
983
|
@stage_length_metrics.set(@stage.size)
|
|
918
984
|
@queue_length_metrics.set(@queue.size)
|
|
919
985
|
@available_buffer_space_ratios_metrics.set(buffer_space * 100)
|
|
@@ -974,6 +1040,24 @@ module Fluent
|
|
|
974
1040
|
def backup_base_dir
|
|
975
1041
|
system_config.root_dir || DEFAULT_BACKUP_DIR
|
|
976
1042
|
end
|
|
1043
|
+
|
|
1044
|
+
def evacuate_dir
|
|
1045
|
+
File.join(backup_base_dir, 'buffer', safe_owner_id)
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
def evacuated_bytesize
|
|
1049
|
+
dir = evacuate_dir
|
|
1050
|
+
Dir.children(dir, encoding: dir.encoding).sum { |name| File.size?(File.join(dir, name)).to_i }
|
|
1051
|
+
rescue Errno::ENOENT
|
|
1052
|
+
0
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
def remaining_evacuate_size
|
|
1056
|
+
@evacuate_limit_size - evacuated_bytesize
|
|
1057
|
+
rescue => e
|
|
1058
|
+
log.warn "failed to get the size of the evacuated chunk files. queued chunks are purged without being evacuated.", error: e
|
|
1059
|
+
0
|
|
1060
|
+
end
|
|
977
1061
|
end
|
|
978
1062
|
end
|
|
979
1063
|
end
|
|
@@ -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
|
|
@@ -44,10 +44,9 @@ module Fluent
|
|
|
44
44
|
|
|
45
45
|
def format(tag, time, record)
|
|
46
46
|
json_str = JSON.generate(record)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"#{header}#{json_str}#{@newline}"
|
|
47
|
+
time_str = @output_time ? "#{@timef.format(time)}#{@delimiter}" : ''
|
|
48
|
+
tag_str = @output_tag ? "#{tag}#{@delimiter}" : ''
|
|
49
|
+
"#{time_str}#{tag_str}#{json_str}#{@newline}"
|
|
51
50
|
ensure
|
|
52
51
|
json_str&.clear
|
|
53
52
|
end
|