fluentd 1.6.3 → 1.7.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.drone.yml +35 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +2 -0
- data/README.md +5 -1
- data/fluentd.gemspec +1 -1
- data/lib/fluent/clock.rb +4 -0
- data/lib/fluent/compat/output.rb +3 -3
- data/lib/fluent/compat/socket_util.rb +1 -1
- data/lib/fluent/config/element.rb +3 -3
- data/lib/fluent/config/literal_parser.rb +1 -1
- data/lib/fluent/config/section.rb +4 -1
- data/lib/fluent/error.rb +4 -0
- data/lib/fluent/event.rb +28 -24
- data/lib/fluent/event_router.rb +2 -1
- data/lib/fluent/log.rb +1 -1
- data/lib/fluent/msgpack_factory.rb +8 -0
- data/lib/fluent/plugin/bare_output.rb +4 -4
- data/lib/fluent/plugin/buf_file_single.rb +211 -0
- data/lib/fluent/plugin/buffer.rb +62 -63
- data/lib/fluent/plugin/buffer/chunk.rb +21 -3
- data/lib/fluent/plugin/buffer/file_chunk.rb +37 -12
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +314 -0
- data/lib/fluent/plugin/buffer/memory_chunk.rb +2 -1
- data/lib/fluent/plugin/compressable.rb +10 -6
- data/lib/fluent/plugin/filter_grep.rb +2 -2
- data/lib/fluent/plugin/formatter_csv.rb +10 -6
- data/lib/fluent/plugin/in_syslog.rb +10 -3
- data/lib/fluent/plugin/in_tail.rb +7 -2
- data/lib/fluent/plugin/in_tcp.rb +34 -7
- data/lib/fluent/plugin/multi_output.rb +4 -4
- data/lib/fluent/plugin/out_exec_filter.rb +1 -0
- data/lib/fluent/plugin/out_file.rb +13 -3
- data/lib/fluent/plugin/out_forward.rb +126 -588
- data/lib/fluent/plugin/out_forward/ack_handler.rb +161 -0
- data/lib/fluent/plugin/out_forward/connection_manager.rb +113 -0
- data/lib/fluent/plugin/out_forward/error.rb +28 -0
- data/lib/fluent/plugin/out_forward/failure_detector.rb +84 -0
- data/lib/fluent/plugin/out_forward/handshake_protocol.rb +121 -0
- data/lib/fluent/plugin/out_forward/load_balancer.rb +111 -0
- data/lib/fluent/plugin/out_forward/socket_cache.rb +138 -0
- data/lib/fluent/plugin/out_http.rb +231 -0
- data/lib/fluent/plugin/output.rb +29 -35
- data/lib/fluent/plugin/parser.rb +77 -0
- data/lib/fluent/plugin/parser_csv.rb +75 -0
- data/lib/fluent/plugin_helper/server.rb +1 -1
- data/lib/fluent/plugin_helper/thread.rb +1 -0
- data/lib/fluent/root_agent.rb +1 -1
- data/lib/fluent/time.rb +4 -2
- data/lib/fluent/timezone.rb +21 -7
- data/lib/fluent/version.rb +1 -1
- data/test/command/test_fluentd.rb +1 -1
- data/test/command/test_plugin_generator.rb +18 -2
- data/test/config/test_configurable.rb +78 -40
- data/test/counter/test_store.rb +1 -1
- data/test/helper.rb +1 -0
- data/test/helpers/process_extenstion.rb +33 -0
- data/test/plugin/out_forward/test_ack_handler.rb +101 -0
- data/test/plugin/out_forward/test_connection_manager.rb +145 -0
- data/test/plugin/out_forward/test_handshake_protocol.rb +103 -0
- data/test/plugin/out_forward/test_load_balancer.rb +60 -0
- data/test/plugin/out_forward/test_socket_cache.rb +139 -0
- data/test/plugin/test_buf_file.rb +118 -2
- data/test/plugin/test_buf_file_single.rb +734 -0
- data/test/plugin/test_buffer.rb +4 -48
- data/test/plugin/test_buffer_file_chunk.rb +19 -1
- data/test/plugin/test_buffer_file_single_chunk.rb +620 -0
- data/test/plugin/test_formatter_csv.rb +16 -0
- data/test/plugin/test_in_syslog.rb +56 -6
- data/test/plugin/test_in_tail.rb +1 -1
- data/test/plugin/test_in_tcp.rb +25 -0
- data/test/plugin/test_out_forward.rb +75 -201
- data/test/plugin/test_out_http.rb +352 -0
- data/test/plugin/test_output_as_buffered.rb +27 -24
- data/test/plugin/test_parser.rb +40 -0
- data/test/plugin/test_parser_csv.rb +83 -0
- data/test/plugin_helper/test_record_accessor.rb +1 -1
- data/test/test_time_formatter.rb +140 -121
- metadata +35 -6
data/lib/fluent/plugin/buffer.rb
CHANGED
@@ -133,6 +133,15 @@ module Fluent
|
|
133
133
|
cmp_variables(variables, variables2)
|
134
134
|
end
|
135
135
|
end
|
136
|
+
|
137
|
+
# This is an optimization code. Current Struct's implementation is comparing all data.
|
138
|
+
# https://github.com/ruby/ruby/blob/0623e2b7cc621b1733a760b72af246b06c30cf96/struct.c#L1200-L1203
|
139
|
+
# Actually this overhead is very small but this class is generated *per chunk* (and used in hash object).
|
140
|
+
# This means that this class is one of the most called object in Fluentd.
|
141
|
+
# See https://github.com/fluent/fluentd/pull/2560
|
142
|
+
def hash
|
143
|
+
timekey.object_id
|
144
|
+
end
|
136
145
|
end
|
137
146
|
|
138
147
|
# for tests
|
@@ -155,7 +164,7 @@ module Fluent
|
|
155
164
|
|
156
165
|
@stage_size = @queue_size = 0
|
157
166
|
@timekeys = Hash.new(0)
|
158
|
-
@
|
167
|
+
@mutex = Mutex.new
|
159
168
|
end
|
160
169
|
|
161
170
|
def persistent?
|
@@ -175,16 +184,18 @@ module Fluent
|
|
175
184
|
|
176
185
|
@stage, @queue = resume
|
177
186
|
@stage.each_pair do |metadata, chunk|
|
178
|
-
@metadata_list << metadata unless @metadata_list.include?(metadata)
|
179
187
|
@stage_size += chunk.bytesize
|
180
|
-
|
188
|
+
if chunk.metadata && chunk.metadata.timekey
|
189
|
+
add_timekey(metadata.timekey)
|
190
|
+
end
|
181
191
|
end
|
182
192
|
@queue.each do |chunk|
|
183
|
-
@metadata_list << chunk.metadata unless @metadata_list.include?(chunk.metadata)
|
184
193
|
@queued_num[chunk.metadata] ||= 0
|
185
194
|
@queued_num[chunk.metadata] += 1
|
186
195
|
@queue_size += chunk.bytesize
|
187
|
-
|
196
|
+
if chunk.metadata && chunk.metadata.timekey
|
197
|
+
add_timekey(chunk.metadata.timekey)
|
198
|
+
end
|
188
199
|
end
|
189
200
|
log.debug "buffer started", instance: self.object_id, stage_size: @stage_size, queue_size: @queue_size
|
190
201
|
end
|
@@ -207,7 +218,7 @@ module Fluent
|
|
207
218
|
|
208
219
|
def terminate
|
209
220
|
super
|
210
|
-
@dequeued = @stage = @queue = @queued_num =
|
221
|
+
@dequeued = @stage = @queue = @queued_num = nil
|
211
222
|
@stage_size = @queue_size = 0
|
212
223
|
@timekeys.clear
|
213
224
|
end
|
@@ -230,61 +241,17 @@ module Fluent
|
|
230
241
|
raise NotImplementedError, "Implement this method in child class"
|
231
242
|
end
|
232
243
|
|
233
|
-
def metadata_list
|
234
|
-
synchronize do
|
235
|
-
@metadata_list.dup
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
# it's too dangerous, and use it so carefully to remove metadata for tests
|
240
|
-
def metadata_list_clear!
|
241
|
-
synchronize do
|
242
|
-
@metadata_list.clear
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
244
|
def new_metadata(timekey: nil, tag: nil, variables: nil)
|
247
245
|
Metadata.new(timekey, tag, variables)
|
248
246
|
end
|
249
247
|
|
250
|
-
def add_metadata(metadata)
|
251
|
-
log.on_trace { log.trace "adding metadata", instance: self.object_id, metadata: metadata }
|
252
|
-
|
253
|
-
synchronize do
|
254
|
-
if i = @metadata_list.index(metadata)
|
255
|
-
@metadata_list[i]
|
256
|
-
else
|
257
|
-
@metadata_list << metadata
|
258
|
-
add_timekey(metadata)
|
259
|
-
metadata
|
260
|
-
end
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
248
|
def metadata(timekey: nil, tag: nil, variables: nil)
|
265
|
-
meta =
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
def add_timekey(metadata)
|
270
|
-
if t = metadata.timekey
|
271
|
-
@timekeys[t] += 1
|
249
|
+
meta = Metadata.new(timekey, tag, variables)
|
250
|
+
if (t = meta.timekey)
|
251
|
+
add_timekey(t)
|
272
252
|
end
|
273
|
-
|
253
|
+
meta
|
274
254
|
end
|
275
|
-
private :add_timekey
|
276
|
-
|
277
|
-
def del_timekey(metadata)
|
278
|
-
if t = metadata.timekey
|
279
|
-
if @timekeys[t] <= 1
|
280
|
-
@timekeys.delete(t)
|
281
|
-
else
|
282
|
-
@timekeys[t] -= 1
|
283
|
-
end
|
284
|
-
end
|
285
|
-
nil
|
286
|
-
end
|
287
|
-
private :del_timekey
|
288
255
|
|
289
256
|
def timekeys
|
290
257
|
@timekeys.keys
|
@@ -408,13 +375,12 @@ module Fluent
|
|
408
375
|
synchronize { @queue.reduce(0){|r, chunk| r + chunk.size } }
|
409
376
|
end
|
410
377
|
|
411
|
-
def queued?(metadata=nil)
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
!@queue.empty?
|
378
|
+
def queued?(metadata = nil, optimistic: false)
|
379
|
+
if optimistic
|
380
|
+
optimistic_queued?(metadata)
|
381
|
+
else
|
382
|
+
synchronize do
|
383
|
+
optimistic_queued?(metadata)
|
418
384
|
end
|
419
385
|
end
|
420
386
|
end
|
@@ -514,6 +480,7 @@ module Fluent
|
|
514
480
|
end
|
515
481
|
|
516
482
|
def purge_chunk(chunk_id)
|
483
|
+
metadata = nil
|
517
484
|
synchronize do
|
518
485
|
chunk = @dequeued.delete(chunk_id)
|
519
486
|
return nil unless chunk # purged by other threads
|
@@ -532,13 +499,16 @@ module Fluent
|
|
532
499
|
|
533
500
|
@dequeued_num[chunk.metadata] -= 1
|
534
501
|
if metadata && !@stage[metadata] && (!@queued_num[metadata] || @queued_num[metadata] < 1) && @dequeued_num[metadata].zero?
|
535
|
-
@metadata_list.delete(metadata)
|
536
502
|
@queued_num.delete(metadata)
|
537
503
|
@dequeued_num.delete(metadata)
|
538
|
-
del_timekey(metadata)
|
539
504
|
end
|
540
505
|
log.trace "chunk purged", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata
|
541
506
|
end
|
507
|
+
|
508
|
+
if metadata && metadata.timekey
|
509
|
+
del_timekey(metadata.timekey)
|
510
|
+
end
|
511
|
+
|
542
512
|
nil
|
543
513
|
end
|
544
514
|
|
@@ -774,6 +744,35 @@ module Fluent
|
|
774
744
|
|
775
745
|
{ 'buffer' => stats }
|
776
746
|
end
|
747
|
+
|
748
|
+
private
|
749
|
+
|
750
|
+
def optimistic_queued?(metadata = nil)
|
751
|
+
if metadata
|
752
|
+
n = @queued_num[metadata]
|
753
|
+
n && n.nonzero?
|
754
|
+
else
|
755
|
+
!@queue.empty?
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
759
|
+
def add_timekey(t)
|
760
|
+
@mutex.synchronize do
|
761
|
+
@timekeys[t] += 1
|
762
|
+
end
|
763
|
+
nil
|
764
|
+
end
|
765
|
+
|
766
|
+
def del_timekey(t)
|
767
|
+
@mutex.synchronize do
|
768
|
+
if @timekeys[t] <= 1
|
769
|
+
@timekeys.delete(t)
|
770
|
+
else
|
771
|
+
@timekeys[t] -= 1
|
772
|
+
end
|
773
|
+
end
|
774
|
+
nil
|
775
|
+
end
|
777
776
|
end
|
778
777
|
end
|
779
778
|
end
|
@@ -57,13 +57,31 @@ module Fluent
|
|
57
57
|
@state = :unstaged
|
58
58
|
|
59
59
|
@size = 0
|
60
|
-
@created_at =
|
61
|
-
@modified_at =
|
60
|
+
@created_at = Fluent::Clock.real_now
|
61
|
+
@modified_at = Fluent::Clock.real_now
|
62
62
|
|
63
63
|
extend Decompressable if compress == :gzip
|
64
64
|
end
|
65
65
|
|
66
|
-
attr_reader :unique_id, :metadata, :
|
66
|
+
attr_reader :unique_id, :metadata, :state
|
67
|
+
|
68
|
+
def raw_create_at
|
69
|
+
@created_at
|
70
|
+
end
|
71
|
+
|
72
|
+
def raw_modified_at
|
73
|
+
@modified_at
|
74
|
+
end
|
75
|
+
|
76
|
+
# for compatibility
|
77
|
+
def created_at
|
78
|
+
@created_at_object ||= Time.at(@created_at)
|
79
|
+
end
|
80
|
+
|
81
|
+
# for compatibility
|
82
|
+
def modified_at
|
83
|
+
@modified_at_object ||= Time.at(@modified_at)
|
84
|
+
end
|
67
85
|
|
68
86
|
# data is array of formatted record string
|
69
87
|
def append(data, **kwargs)
|
@@ -24,6 +24,8 @@ module Fluent
|
|
24
24
|
class FileChunk < Chunk
|
25
25
|
class FileChunkError < StandardError; end
|
26
26
|
|
27
|
+
BUFFER_HEADER = "\xc1\x00".force_encoding(Encoding::ASCII_8BIT).freeze
|
28
|
+
|
27
29
|
### buffer path user specified : /path/to/directory/user_specified_prefix.*.log
|
28
30
|
### buffer chunk path : /path/to/directory/user_specified_prefix.b513b61c9791029c2513b61c9791029c2.log
|
29
31
|
### buffer chunk metadata path : /path/to/directory/user_specified_prefix.b513b61c9791029c2513b61c9791029c2.log.meta
|
@@ -74,7 +76,8 @@ module Fluent
|
|
74
76
|
@size += @adding_size
|
75
77
|
@bytesize += @adding_bytes
|
76
78
|
@adding_bytes = @adding_size = 0
|
77
|
-
@modified_at =
|
79
|
+
@modified_at = Fluent::Clock.real_now
|
80
|
+
@modified_at_object = nil
|
78
81
|
|
79
82
|
true
|
80
83
|
end
|
@@ -214,14 +217,19 @@ module Fluent
|
|
214
217
|
end
|
215
218
|
|
216
219
|
def restore_metadata(bindata)
|
217
|
-
data =
|
220
|
+
data = restore_metadata_with_new_format(bindata)
|
221
|
+
|
222
|
+
unless data
|
223
|
+
# old type of restore
|
224
|
+
data = msgpack_unpacker(symbolize_keys: true).feed(bindata).read rescue {}
|
225
|
+
end
|
218
226
|
|
219
|
-
now =
|
227
|
+
now = Fluent::Clock.real_now
|
220
228
|
|
221
229
|
@unique_id = data[:id] || self.class.unique_id_from_path(@path) || @unique_id
|
222
230
|
@size = data[:s] || 0
|
223
|
-
@created_at =
|
224
|
-
@modified_at =
|
231
|
+
@created_at = data.fetch(:c, now.to_i)
|
232
|
+
@modified_at = data.fetch(:m, now.to_i)
|
225
233
|
|
226
234
|
@metadata.timekey = data[:timekey]
|
227
235
|
@metadata.tag = data[:tag]
|
@@ -231,8 +239,8 @@ module Fluent
|
|
231
239
|
def restore_metadata_partially(chunk)
|
232
240
|
@unique_id = self.class.unique_id_from_path(chunk.path) || @unique_id
|
233
241
|
@size = 0
|
234
|
-
@created_at = chunk.ctime # birthtime isn't supported on Windows (and Travis?)
|
235
|
-
@modified_at = chunk.mtime
|
242
|
+
@created_at = chunk.ctime.to_i # birthtime isn't supported on Windows (and Travis?)
|
243
|
+
@modified_at = chunk.mtime.to_i
|
236
244
|
|
237
245
|
@metadata.timekey = nil
|
238
246
|
@metadata.tag = nil
|
@@ -243,13 +251,13 @@ module Fluent
|
|
243
251
|
data = @metadata.to_h.merge({
|
244
252
|
id: @unique_id,
|
245
253
|
s: (update ? @size + @adding_size : @size),
|
246
|
-
c: @created_at
|
247
|
-
m: (update ?
|
254
|
+
c: @created_at,
|
255
|
+
m: (update ? Fluent::Clock.real_now : @modified_at),
|
248
256
|
})
|
249
|
-
bin =
|
257
|
+
bin = Fluent::MessagePackFactory.thread_local_msgpack_packer.pack(data).full_pack
|
258
|
+
size = [bin.bytesize].pack('N')
|
250
259
|
@meta.seek(0, IO::SEEK_SET)
|
251
|
-
@meta.write(bin)
|
252
|
-
@meta.truncate(bin.bytesize)
|
260
|
+
@meta.write(BUFFER_HEADER + size + bin)
|
253
261
|
end
|
254
262
|
|
255
263
|
def file_rename(file, old_path, new_path, callback=nil)
|
@@ -377,6 +385,23 @@ module Fluent
|
|
377
385
|
end
|
378
386
|
@state = :queued
|
379
387
|
end
|
388
|
+
|
389
|
+
private
|
390
|
+
|
391
|
+
def restore_metadata_with_new_format(chunk)
|
392
|
+
if chunk.size <= 6 # size of BUFFER_HEADER (2) + size of data size(4)
|
393
|
+
return nil
|
394
|
+
end
|
395
|
+
|
396
|
+
if chunk.slice(0, 2) == BUFFER_HEADER
|
397
|
+
size = chunk.slice(2, 4).unpack('N').first
|
398
|
+
if size
|
399
|
+
return msgpack_unpacker(symbolize_keys: true).feed(chunk.slice(6, size)).read rescue nil
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
nil
|
404
|
+
end
|
380
405
|
end
|
381
406
|
end
|
382
407
|
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'uri'
|
18
|
+
require 'fluent/plugin/buffer/chunk'
|
19
|
+
require 'fluent/unique_id'
|
20
|
+
require 'fluent/msgpack_factory'
|
21
|
+
|
22
|
+
module Fluent
|
23
|
+
module Plugin
|
24
|
+
class Buffer
|
25
|
+
class FileSingleChunk < Chunk
|
26
|
+
class FileChunkError < StandardError; end
|
27
|
+
|
28
|
+
## buffer path user specified : /path/to/directory
|
29
|
+
## buffer chunk path : /path/to/directory/fsb.key.b513b61c9791029c2513b61c9791029c2.buf
|
30
|
+
## state: b/q - 'b'(on stage), 'q'(enqueued)
|
31
|
+
|
32
|
+
include SystemConfig::Mixin
|
33
|
+
include MessagePackFactory::Mixin
|
34
|
+
|
35
|
+
PATH_EXT = 'buf'
|
36
|
+
PATH_SUFFIX = ".#{PATH_EXT}"
|
37
|
+
PATH_REGEXP = /\.(b|q)([0-9a-f]+)\.#{PATH_EXT}*\Z/n # //n switch means explicit 'ASCII-8BIT' pattern
|
38
|
+
FILE_PERMISSION = 0644
|
39
|
+
|
40
|
+
attr_reader :path, :permission
|
41
|
+
|
42
|
+
def initialize(metadata, path, mode, key, perm: system_config.file_permission || FILE_PERMISSION, compress: :text)
|
43
|
+
super(metadata, compress: compress)
|
44
|
+
@key = key
|
45
|
+
@permission = perm.is_a?(String) ? perm.to_i(8) : perm
|
46
|
+
@bytesize = @size = @adding_bytes = @adding_size = 0
|
47
|
+
|
48
|
+
case mode
|
49
|
+
when :create then create_new_chunk(path, metadata, @permission)
|
50
|
+
when :staged then load_existing_staged_chunk(path)
|
51
|
+
when :queued then load_existing_enqueued_chunk(path)
|
52
|
+
else
|
53
|
+
raise ArgumentError, "Invalid file chunk mode: #{mode}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def concat(bulk, bulk_size)
|
58
|
+
raise "BUG: concatenating to unwritable chunk, now '#{self.state}'" unless self.writable?
|
59
|
+
|
60
|
+
bulk.force_encoding(Encoding::ASCII_8BIT)
|
61
|
+
@chunk.write(bulk)
|
62
|
+
@adding_bytes += bulk.bytesize
|
63
|
+
@adding_size += bulk_size
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def commit
|
68
|
+
@commit_position = @chunk.pos
|
69
|
+
@size += @adding_size
|
70
|
+
@bytesize += @adding_bytes
|
71
|
+
@adding_bytes = @adding_size = 0
|
72
|
+
@modified_at = Fluent::Clock.real_now
|
73
|
+
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def rollback
|
78
|
+
if @chunk.pos != @commit_position
|
79
|
+
@chunk.seek(@commit_position, IO::SEEK_SET)
|
80
|
+
@chunk.truncate(@commit_position)
|
81
|
+
end
|
82
|
+
@adding_bytes = @adding_size = 0
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def bytesize
|
87
|
+
@bytesize + @adding_bytes
|
88
|
+
end
|
89
|
+
|
90
|
+
def size
|
91
|
+
@size + @adding_size
|
92
|
+
end
|
93
|
+
|
94
|
+
def empty?
|
95
|
+
@bytesize.zero?
|
96
|
+
end
|
97
|
+
|
98
|
+
def enqueued!
|
99
|
+
return unless self.staged?
|
100
|
+
|
101
|
+
new_chunk_path = self.class.generate_queued_chunk_path(@path, @unique_id)
|
102
|
+
|
103
|
+
begin
|
104
|
+
file_rename(@chunk, @path, new_chunk_path, ->(new_io) { @chunk = new_io })
|
105
|
+
rescue => e
|
106
|
+
begin
|
107
|
+
file_rename(@chunk, new_chunk_path, @path, ->(new_io) { @chunk = new_io }) if File.exist?(new_chunk_path)
|
108
|
+
rescue => re
|
109
|
+
# In this point, restore buffer state is hard because previous `file_rename` failed by resource problem.
|
110
|
+
# Retry is one possible approach but it may cause livelock under limited resources or high load environment.
|
111
|
+
# So we ignore such errors for now and log better message instead.
|
112
|
+
# "Too many open files" should be fixed by proper buffer configuration and system setting.
|
113
|
+
raise "can't enqueue buffer file and failed to restore. This may causes inconsistent state: path = #{@path}, error = '#{e}', retry error = '#{re}'"
|
114
|
+
else
|
115
|
+
raise "can't enqueue buffer file: path = #{@path}, error = '#{e}'"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@path = new_chunk_path
|
120
|
+
|
121
|
+
super
|
122
|
+
end
|
123
|
+
|
124
|
+
def close
|
125
|
+
super
|
126
|
+
size = @chunk.size
|
127
|
+
@chunk.close
|
128
|
+
if size == 0
|
129
|
+
File.unlink(@path)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def purge
|
134
|
+
super
|
135
|
+
@chunk.close
|
136
|
+
@bytesize = @size = @adding_bytes = @adding_size = 0
|
137
|
+
File.unlink(@path)
|
138
|
+
end
|
139
|
+
|
140
|
+
def read(**kwargs)
|
141
|
+
@chunk.seek(0, IO::SEEK_SET)
|
142
|
+
@chunk.read
|
143
|
+
end
|
144
|
+
|
145
|
+
def open(**kwargs, &block)
|
146
|
+
@chunk.seek(0, IO::SEEK_SET)
|
147
|
+
val = yield @chunk
|
148
|
+
@chunk.seek(0, IO::SEEK_END) if self.staged?
|
149
|
+
val
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.assume_chunk_state(path)
|
153
|
+
return :unknown unless path.end_with?(PATH_SUFFIX)
|
154
|
+
|
155
|
+
if PATH_REGEXP =~ path
|
156
|
+
$1 == 'b' ? :staged : :queued
|
157
|
+
else
|
158
|
+
# files which matches to glob of buffer file pattern
|
159
|
+
# it includes files which are created by out_file
|
160
|
+
:unknown
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.unique_id_and_key_from_path(path)
|
165
|
+
base = File.basename(path)
|
166
|
+
res = PATH_REGEXP =~ base
|
167
|
+
return nil unless res
|
168
|
+
|
169
|
+
key = base[4..res - 1] # remove 'fsb.' and '.'
|
170
|
+
hex_id = $2 # remove '.' and '.buf'
|
171
|
+
unique_id = hex_id.scan(/../).map {|x| x.to_i(16) }.pack('C*')
|
172
|
+
[unique_id, key]
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.generate_stage_chunk_path(path, key, unique_id)
|
176
|
+
pos = path.index('.*.')
|
177
|
+
raise "BUG: buffer chunk path on stage MUST have '.*.'" unless pos
|
178
|
+
|
179
|
+
prefix = path[0...pos]
|
180
|
+
suffix = path[(pos + 3)..-1]
|
181
|
+
|
182
|
+
chunk_id = Fluent::UniqueId.hex(unique_id)
|
183
|
+
"#{prefix}.#{key}.b#{chunk_id}.#{suffix}"
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.generate_queued_chunk_path(path, unique_id)
|
187
|
+
chunk_id = Fluent::UniqueId.hex(unique_id)
|
188
|
+
staged_path = ".b#{chunk_id}."
|
189
|
+
if path.index(staged_path)
|
190
|
+
path.sub(staged_path, ".q#{chunk_id}.")
|
191
|
+
else # for unexpected cases (ex: users rename files while opened by fluentd)
|
192
|
+
path + ".q#{chunk_id}.chunk"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def restore_metadata
|
197
|
+
if res = self.class.unique_id_and_key_from_path(@path)
|
198
|
+
@unique_id = res.first
|
199
|
+
key = decode_key(res.last)
|
200
|
+
if @key
|
201
|
+
@metadata.variables = {@key => key}
|
202
|
+
else
|
203
|
+
@metadata.tag = key
|
204
|
+
end
|
205
|
+
else
|
206
|
+
raise FileChunkError, "Invalid chunk found. unique_id and key not exist: #{@path}"
|
207
|
+
end
|
208
|
+
@size = 0
|
209
|
+
|
210
|
+
stat = File.stat(@path)
|
211
|
+
@created_at = stat.ctime.to_i
|
212
|
+
@modified_at = stat.mtime.to_i
|
213
|
+
end
|
214
|
+
|
215
|
+
def restore_size(chunk_format)
|
216
|
+
count = 0
|
217
|
+
File.open(@path, 'rb') { |f|
|
218
|
+
if chunk_format == :msgpack
|
219
|
+
msgpack_unpacker(f).each { |d| count += 1 }
|
220
|
+
else
|
221
|
+
f.each_line { |l| count += 1 }
|
222
|
+
end
|
223
|
+
}
|
224
|
+
@size = count
|
225
|
+
end
|
226
|
+
|
227
|
+
def file_rename(file, old_path, new_path, callback = nil)
|
228
|
+
pos = file.pos
|
229
|
+
if Fluent.windows?
|
230
|
+
file.close
|
231
|
+
File.rename(old_path, new_path)
|
232
|
+
file = File.open(new_path, 'rb', @permission)
|
233
|
+
else
|
234
|
+
File.rename(old_path, new_path)
|
235
|
+
file.reopen(new_path, 'rb')
|
236
|
+
end
|
237
|
+
file.set_encoding(Encoding::ASCII_8BIT)
|
238
|
+
file.sync = true
|
239
|
+
file.binmode
|
240
|
+
file.pos = pos
|
241
|
+
callback.call(file) if callback
|
242
|
+
end
|
243
|
+
|
244
|
+
URI_PARSER = URI::Parser.new
|
245
|
+
ESCAPE_REGEXP = /[^-_.a-zA-Z0-9]/n
|
246
|
+
|
247
|
+
def encode_key(metadata)
|
248
|
+
k = @key ? metadata.variables[@key] : metadata.tag
|
249
|
+
k ||= ''
|
250
|
+
URI_PARSER.escape(k, ESCAPE_REGEXP)
|
251
|
+
end
|
252
|
+
|
253
|
+
def decode_key(key)
|
254
|
+
URI_PARSER.unescape(key)
|
255
|
+
end
|
256
|
+
|
257
|
+
def create_new_chunk(path, metadata, perm)
|
258
|
+
@path = self.class.generate_stage_chunk_path(path, encode_key(metadata), @unique_id)
|
259
|
+
begin
|
260
|
+
@chunk = File.open(@path, 'wb+', perm)
|
261
|
+
@chunk.set_encoding(Encoding::ASCII_8BIT)
|
262
|
+
@chunk.sync = true
|
263
|
+
@chunk.binmode
|
264
|
+
rescue => e
|
265
|
+
# Here assumes "Too many open files" like recoverable error so raising BufferOverflowError.
|
266
|
+
# If other cases are possible, we will change erorr handling with proper classes.
|
267
|
+
raise BufferOverflowError, "can't create buffer file for #{path}. Stop creating buffer files: error = #{e}"
|
268
|
+
end
|
269
|
+
|
270
|
+
@state = :unstaged
|
271
|
+
@bytesize = 0
|
272
|
+
@commit_position = @chunk.pos # must be 0
|
273
|
+
@adding_bytes = 0
|
274
|
+
@adding_size = 0
|
275
|
+
end
|
276
|
+
|
277
|
+
def load_existing_staged_chunk(path)
|
278
|
+
@path = path
|
279
|
+
raise FileChunkError, "staged file chunk is empty" if File.size(@path).zero?
|
280
|
+
|
281
|
+
@chunk = File.open(@path, 'rb+')
|
282
|
+
@chunk.set_encoding(Encoding::ASCII_8BIT)
|
283
|
+
@chunk.sync = true
|
284
|
+
@chunk.binmode
|
285
|
+
@chunk.seek(0, IO::SEEK_END)
|
286
|
+
|
287
|
+
restore_metadata
|
288
|
+
|
289
|
+
@state = :staged
|
290
|
+
@bytesize = @chunk.size
|
291
|
+
@commit_position = @chunk.pos
|
292
|
+
@adding_bytes = 0
|
293
|
+
@adding_size = 0
|
294
|
+
end
|
295
|
+
|
296
|
+
def load_existing_enqueued_chunk(path)
|
297
|
+
@path = path
|
298
|
+
raise FileChunkError, "enqueued file chunk is empty" if File.size(@path).zero?
|
299
|
+
|
300
|
+
@chunk = File.open(@path, 'rb')
|
301
|
+
@chunk.set_encoding(Encoding::ASCII_8BIT)
|
302
|
+
@chunk.binmode
|
303
|
+
@chunk.seek(0, IO::SEEK_SET)
|
304
|
+
|
305
|
+
restore_metadata
|
306
|
+
|
307
|
+
@state = :queued
|
308
|
+
@bytesize = @chunk.size
|
309
|
+
@commit_position = @chunk.size
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|