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.

Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.drone.yml +35 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +2 -0
  4. data/README.md +5 -1
  5. data/fluentd.gemspec +1 -1
  6. data/lib/fluent/clock.rb +4 -0
  7. data/lib/fluent/compat/output.rb +3 -3
  8. data/lib/fluent/compat/socket_util.rb +1 -1
  9. data/lib/fluent/config/element.rb +3 -3
  10. data/lib/fluent/config/literal_parser.rb +1 -1
  11. data/lib/fluent/config/section.rb +4 -1
  12. data/lib/fluent/error.rb +4 -0
  13. data/lib/fluent/event.rb +28 -24
  14. data/lib/fluent/event_router.rb +2 -1
  15. data/lib/fluent/log.rb +1 -1
  16. data/lib/fluent/msgpack_factory.rb +8 -0
  17. data/lib/fluent/plugin/bare_output.rb +4 -4
  18. data/lib/fluent/plugin/buf_file_single.rb +211 -0
  19. data/lib/fluent/plugin/buffer.rb +62 -63
  20. data/lib/fluent/plugin/buffer/chunk.rb +21 -3
  21. data/lib/fluent/plugin/buffer/file_chunk.rb +37 -12
  22. data/lib/fluent/plugin/buffer/file_single_chunk.rb +314 -0
  23. data/lib/fluent/plugin/buffer/memory_chunk.rb +2 -1
  24. data/lib/fluent/plugin/compressable.rb +10 -6
  25. data/lib/fluent/plugin/filter_grep.rb +2 -2
  26. data/lib/fluent/plugin/formatter_csv.rb +10 -6
  27. data/lib/fluent/plugin/in_syslog.rb +10 -3
  28. data/lib/fluent/plugin/in_tail.rb +7 -2
  29. data/lib/fluent/plugin/in_tcp.rb +34 -7
  30. data/lib/fluent/plugin/multi_output.rb +4 -4
  31. data/lib/fluent/plugin/out_exec_filter.rb +1 -0
  32. data/lib/fluent/plugin/out_file.rb +13 -3
  33. data/lib/fluent/plugin/out_forward.rb +126 -588
  34. data/lib/fluent/plugin/out_forward/ack_handler.rb +161 -0
  35. data/lib/fluent/plugin/out_forward/connection_manager.rb +113 -0
  36. data/lib/fluent/plugin/out_forward/error.rb +28 -0
  37. data/lib/fluent/plugin/out_forward/failure_detector.rb +84 -0
  38. data/lib/fluent/plugin/out_forward/handshake_protocol.rb +121 -0
  39. data/lib/fluent/plugin/out_forward/load_balancer.rb +111 -0
  40. data/lib/fluent/plugin/out_forward/socket_cache.rb +138 -0
  41. data/lib/fluent/plugin/out_http.rb +231 -0
  42. data/lib/fluent/plugin/output.rb +29 -35
  43. data/lib/fluent/plugin/parser.rb +77 -0
  44. data/lib/fluent/plugin/parser_csv.rb +75 -0
  45. data/lib/fluent/plugin_helper/server.rb +1 -1
  46. data/lib/fluent/plugin_helper/thread.rb +1 -0
  47. data/lib/fluent/root_agent.rb +1 -1
  48. data/lib/fluent/time.rb +4 -2
  49. data/lib/fluent/timezone.rb +21 -7
  50. data/lib/fluent/version.rb +1 -1
  51. data/test/command/test_fluentd.rb +1 -1
  52. data/test/command/test_plugin_generator.rb +18 -2
  53. data/test/config/test_configurable.rb +78 -40
  54. data/test/counter/test_store.rb +1 -1
  55. data/test/helper.rb +1 -0
  56. data/test/helpers/process_extenstion.rb +33 -0
  57. data/test/plugin/out_forward/test_ack_handler.rb +101 -0
  58. data/test/plugin/out_forward/test_connection_manager.rb +145 -0
  59. data/test/plugin/out_forward/test_handshake_protocol.rb +103 -0
  60. data/test/plugin/out_forward/test_load_balancer.rb +60 -0
  61. data/test/plugin/out_forward/test_socket_cache.rb +139 -0
  62. data/test/plugin/test_buf_file.rb +118 -2
  63. data/test/plugin/test_buf_file_single.rb +734 -0
  64. data/test/plugin/test_buffer.rb +4 -48
  65. data/test/plugin/test_buffer_file_chunk.rb +19 -1
  66. data/test/plugin/test_buffer_file_single_chunk.rb +620 -0
  67. data/test/plugin/test_formatter_csv.rb +16 -0
  68. data/test/plugin/test_in_syslog.rb +56 -6
  69. data/test/plugin/test_in_tail.rb +1 -1
  70. data/test/plugin/test_in_tcp.rb +25 -0
  71. data/test/plugin/test_out_forward.rb +75 -201
  72. data/test/plugin/test_out_http.rb +352 -0
  73. data/test/plugin/test_output_as_buffered.rb +27 -24
  74. data/test/plugin/test_parser.rb +40 -0
  75. data/test/plugin/test_parser_csv.rb +83 -0
  76. data/test/plugin_helper/test_record_accessor.rb +1 -1
  77. data/test/test_time_formatter.rb +140 -121
  78. metadata +35 -6
@@ -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
- @metadata_list = [] # keys of @stage
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
- add_timekey(metadata)
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
- add_timekey(chunk.metadata)
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 = @metadata_list = nil
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 = new_metadata(timekey: timekey, tag: tag, variables: variables)
266
- add_metadata(meta)
267
- end
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
- nil
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
- synchronize do
413
- if metadata
414
- n = @queued_num[metadata]
415
- n && n.nonzero?
416
- else
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 = Time.now
61
- @modified_at = Time.now
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, :created_at, :modified_at, :state
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 = Time.now
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 = msgpack_unpacker(symbolize_keys: true).feed(bindata).read rescue {}
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 = Time.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 = Time.at(data.fetch(:c, now.to_i))
224
- @modified_at = Time.at(data.fetch(:m, now.to_i))
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.to_i,
247
- m: (update ? Time.now : @modified_at).to_i,
254
+ c: @created_at,
255
+ m: (update ? Fluent::Clock.real_now : @modified_at),
248
256
  })
249
- bin = msgpack_packer.pack(data).to_s
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