7
7
require "thread"
8
8
require "tmpdir"
9
9
require "fileutils"
10
+ require 'pathname'
10
11
11
12
12
13
# INFORMATION:
60
61
# time_file => 5 (optional)
61
62
# format => "plain" (optional)
62
63
# canned_acl => "private" (optional. Options are "private", "public_read", "public_read_write", "authenticated_read". Defaults to "private" )
64
+ # no_event_wait => 5 (optional. Defines the number of time_file s3 upload events that may go with no eventns for the prefix, before cleaning up the watch on that)
63
65
# }
64
66
#
65
67
class LogStash ::Outputs ::S3 < LogStash ::Outputs ::Base
@@ -110,6 +112,9 @@ class LogStash::Outputs::S3 < LogStash::Outputs::Base
110
112
# Specify how many workers to use to upload the files to S3
111
113
config :upload_workers_count , :validate => :number , :default => 1
112
114
115
+ # Specify after how many interval of time_file, a prefix directory should be cleaned up locally if no events happing for it
116
+ config :no_event_wait , :validate => :number , :default => 5
117
+
113
118
# Exposed attributes for testing purpose.
114
119
attr_accessor :tempfile
115
120
attr_reader :page_counter
@@ -139,8 +144,13 @@ def aws_service_endpoint(region)
139
144
def write_on_bucket ( file )
140
145
# find and use the bucket
141
146
bucket = @s3 . buckets [ @bucket ]
147
+
148
+ first = Pathname . new @temporary_directory
149
+ second = Pathname . new file
142
150
143
- remote_filename = "#{ @prefix } #{ File . basename ( file ) } "
151
+ remote_filename_path = second . relative_path_from first
152
+
153
+ remote_filename = remote_filename_path . to_s
144
154
145
155
@logger . debug ( "S3: ready to write file in bucket" , :remote_filename => remote_filename , :bucket => @bucket )
146
156
@@ -160,17 +170,21 @@ def write_on_bucket(file)
160
170
161
171
# This method is used for create new empty temporary files for use. Flag is needed for indicate new subsection time_file.
162
172
public
163
- def create_temporary_file
164
- filename = File . join ( @temporary_directory , get_temporary_filename ( @page_counter ) )
165
-
166
- @logger . debug ( "S3: Creating a new temporary file" , :filename => filename )
167
-
168
- @file_rotation_lock . synchronize do
169
- unless @tempfile . nil?
170
- @tempfile . close
173
+ def create_temporary_file ( prefix )
174
+ filename = File . join ( @temporary_directory , prefix , get_temporary_filename ( @page_counter [ prefix ] ) )
175
+ @file_rotation_lock [ prefix ] . synchronize do
176
+ unless @tempfile [ prefix ] . nil?
177
+ @tempfile [ prefix ] . close
178
+ end
179
+
180
+ if @prefixes . include? prefix
181
+ dirname = File . dirname ( filename )
182
+ unless File . directory? ( dirname )
183
+ FileUtils . mkdir_p ( dirname )
184
+ end
185
+ @logger . debug ( "S3: Creating a new temporary file" , :filename => filename )
186
+ @tempfile [ prefix ] = File . open ( filename , "a" )
171
187
end
172
-
173
- @tempfile = File . open ( filename , "a" )
174
188
end
175
189
end
176
190
@@ -185,7 +199,11 @@ def register
185
199
186
200
@s3 = aws_s3_config
187
201
@upload_queue = Queue . new
188
- @file_rotation_lock = Mutex . new
202
+ @file_rotation_lock = Hash . new
203
+ @tempfile = Hash . new
204
+ @page_counter = Hash . new
205
+ @prefixes = Set . new
206
+ @empty_uploads = Hash . new
189
207
190
208
if @prefix && @prefix =~ S3_INVALID_CHARACTERS
191
209
@logger . error ( "S3: prefix contains invalid characters" , :prefix => @prefix , :contains => S3_INVALID_CHARACTERS )
@@ -197,15 +215,14 @@ def register
197
215
end
198
216
199
217
test_s3_write
200
-
201
218
restore_from_crashes if @restore == true
202
- reset_page_counter
203
- create_temporary_file
219
+ # reset_page_counter
220
+ # create_temporary_file
204
221
configure_periodic_rotation if time_file != 0
205
222
configure_upload_workers
206
223
207
224
@codec . on_event do |event , encoded_event |
208
- handle_event ( encoded_event )
225
+ handle_event ( encoded_event , event )
209
226
end
210
227
end
211
228
@@ -242,13 +259,36 @@ def restore_from_crashes
242
259
end
243
260
end
244
261
262
+ public
263
+ def shouldcleanup ( prefix )
264
+ return @empty_uploads [ prefix ] > @no_event_wait
265
+ end
266
+
245
267
public
246
268
def move_file_to_bucket ( file )
269
+
270
+ @logger . debug ( "S3: moving to bucket " , :file => file )
271
+
272
+ basepath = Pathname . new @temporary_directory
273
+ dirname = Pathname . new File . dirname ( file )
274
+ prefixpath = dirname . relative_path_from basepath
275
+ prefix = prefixpath . to_s
276
+ @logger . debug ( "S3: moving the file for prefix" , :prefix => prefix )
277
+
247
278
if !File . zero? ( file )
279
+ if @prefixes . include? prefix
280
+ @empty_uploads [ prefix ] = 0
281
+ end
248
282
write_on_bucket ( file )
249
283
@logger . debug ( "S3: file was put on the upload thread" , :filename => File . basename ( file ) , :bucket => @bucket )
284
+ else
285
+ if @prefixes . include? prefix
286
+ @empty_uploads [ prefix ] += 1
287
+ end
250
288
end
251
289
290
+ @logger . debug ( "S3: empty_uploads for the prefix " , :prefix => prefix , :empty_uploads => @empty_uploads [ prefix ] )
291
+
252
292
begin
253
293
File . delete ( file )
254
294
rescue Errno ::ENOENT
@@ -257,6 +297,10 @@ def move_file_to_bucket(file)
257
297
rescue Errno ::EACCES
258
298
@logger . error ( "S3: Logstash doesnt have the permission to delete the file in the temporary directory." , :filename => File . basename ( file ) , :temporary_directory => @temporary_directory )
259
299
end
300
+
301
+ if shouldcleanup ( prefix )
302
+ cleanprefix ( prefix )
303
+ end
260
304
end
261
305
262
306
public
@@ -283,9 +327,10 @@ def receive(event)
283
327
end
284
328
285
329
public
286
- def rotate_events_log?
287
- @file_rotation_lock . synchronize do
288
- @tempfile . size > @size_file
330
+
331
+ def rotate_events_log ( prefix )
332
+ @file_rotation_lock [ prefix ] . synchronize do
333
+ @tempfile [ prefix ] . size > @size_file
289
334
end
290
335
end
291
336
@@ -295,12 +340,13 @@ def write_events_to_multiple_files?
295
340
end
296
341
297
342
public
298
- def write_to_tempfile ( event )
343
+ def write_to_tempfile ( event , prefix )
344
+
299
345
begin
300
- @logger . debug ( "S3: put event into tempfile " , :tempfile => File . basename ( @tempfile ) )
346
+ @logger . debug ( "S3: put event into tempfile " , :tempfile => File . basename ( @tempfile [ prefix ] ) )
301
347
302
- @file_rotation_lock . synchronize do
303
- @tempfile . syswrite ( event )
348
+ @file_rotation_lock [ prefix ] . synchronize do
349
+ @tempfile [ prefix ] . syswrite ( event )
304
350
end
305
351
rescue Errno ::ENOSPC
306
352
@logger . error ( "S3: No space left in temporary directory" , :temporary_directory => @temporary_directory )
@@ -309,13 +355,16 @@ def write_to_tempfile(event)
309
355
end
310
356
311
357
public
312
- def teardown
358
+ def teardown ( )
313
359
shutdown_upload_workers
314
360
@periodic_rotation_thread . stop! if @periodic_rotation_thread
315
-
316
- @file_rotation_lock . synchronize do
317
- @tempfile . close unless @tempfile . nil? && @tempfile . closed?
361
+
362
+ for prefix in @prefixes
363
+ @file_rotation_lock [ prefix ] . synchronize do
364
+ @tempfile [ prefix ] . close unless @tempfile [ prefix ] . nil? && @tempfile [ prefix ] . closed?
365
+ end
318
366
end
367
+
319
368
finished
320
369
end
321
370
@@ -326,37 +375,63 @@ def shutdown_upload_workers
326
375
end
327
376
328
377
private
329
- def handle_event ( encoded_event )
378
+ def handle_event ( encoded_event , event )
379
+ actualprefix = event . sprintf ( @prefix )
380
+ if not @prefixes . to_a ( ) . include? actualprefix
381
+ @file_rotation_lock [ actualprefix ] = Mutex . new
382
+ @prefixes . add ( actualprefix )
383
+ reset_page_counter ( actualprefix )
384
+ create_temporary_file ( actualprefix )
385
+ @empty_uploads [ actualprefix ] = 0
386
+ end
387
+
330
388
if write_events_to_multiple_files?
331
- if rotate_events_log?
332
- @logger . debug ( "S3: tempfile is too large, let's bucket it and create new file" , :tempfile => File . basename ( @tempfile ) )
389
+ if rotate_events_log ( actualprefix )
390
+ @logger . debug ( "S3: tempfile is too large, let's bucket it and create new file" , :tempfile => File . basename ( @tempfile [ actualprefix ] ) )
333
391
334
- move_file_to_bucket_async ( @tempfile . path )
335
- next_page
336
- create_temporary_file
392
+ move_file_to_bucket_async ( @tempfile [ actualprefix ] . path )
393
+ next_page ( actualprefix )
394
+ create_temporary_file ( actualprefix )
337
395
else
338
- @logger . debug ( "S3: tempfile file size report." , :tempfile_size => @tempfile . size , :size_file => @size_file )
396
+ @logger . debug ( "S3: tempfile file size report." , :tempfile_size => @tempfile [ actualprefix ] . size , :size_file => @size_file )
339
397
end
340
398
end
341
399
342
- write_to_tempfile ( encoded_event )
400
+ write_to_tempfile ( encoded_event , actualprefix )
343
401
end
344
402
345
403
private
346
404
def configure_periodic_rotation
347
405
@periodic_rotation_thread = Stud ::Task . new do
348
406
LogStash ::Util ::set_thread_name ( "<S3 periodic uploader" )
349
407
350
- Stud . interval ( periodic_interval , :sleep_then_run => true ) do
351
- @logger . debug ( "S3: time_file triggered, bucketing the file" , :filename => @tempfile . path )
352
-
353
- move_file_to_bucket_async ( @tempfile . path )
354
- next_page
355
- create_temporary_file
408
+ Stud . interval ( periodic_interval , :sleep_then_run => true ) do
409
+
410
+ @tempfile . keys . each do |key |
411
+ @logger . debug ( "S3: time_file triggered, bucketing the file" , :filename => @tempfile [ key ] . path )
412
+ move_file_to_bucket_async ( @tempfile [ key ] . path )
413
+ next_page ( key )
414
+ create_temporary_file ( key )
415
+ end
416
+
356
417
end
357
418
end
358
419
end
359
420
421
+ private
422
+ def cleanprefix ( prefix )
423
+ path = File . join ( @temporary_directory , prefix )
424
+ @logger . debug ( "cleaning the directory and prefix " , :dir => path , :prefix => prefix )
425
+ @file_rotation_lock [ prefix ] . synchronize do
426
+ @tempfile [ prefix ] . close
427
+ Dir . foreach ( path ) { |f | fn = File . join ( path , f ) ; File . delete ( fn ) if f != '.' && f != '..' }
428
+ FileUtils . remove_dir ( path )
429
+ @prefixes . delete ( prefix )
430
+ @tempfile . delete ( prefix )
431
+ @empty_uploads [ prefix ] = 0
432
+ end
433
+ end
434
+
360
435
private
361
436
def configure_upload_workers
362
437
@logger . debug ( "S3: Configure upload workers" )
@@ -389,13 +464,13 @@ def upload_worker
389
464
end
390
465
391
466
private
392
- def next_page
393
- @page_counter += 1
467
+ def next_page ( key )
468
+ @page_counter [ key ] += 1
394
469
end
395
470
396
471
private
397
- def reset_page_counter
398
- @page_counter = 0
472
+ def reset_page_counter ( key )
473
+ @page_counter [ key ] = 0
399
474
end
400
475
401
476
private
0 commit comments