logstash-integration-aws 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.PRE.MERGE.md +658 -0
  3. data/CHANGELOG.md +15 -0
  4. data/CONTRIBUTORS +40 -0
  5. data/Gemfile +11 -0
  6. data/LICENSE +202 -0
  7. data/NOTICE.TXT +5 -0
  8. data/README.md +205 -0
  9. data/docs/codec-cloudfront.asciidoc +53 -0
  10. data/docs/codec-cloudtrail.asciidoc +45 -0
  11. data/docs/index.asciidoc +38 -0
  12. data/docs/input-cloudwatch.asciidoc +320 -0
  13. data/docs/input-s3.asciidoc +346 -0
  14. data/docs/input-sqs.asciidoc +287 -0
  15. data/docs/output-cloudwatch.asciidoc +321 -0
  16. data/docs/output-s3.asciidoc +442 -0
  17. data/docs/output-sns.asciidoc +166 -0
  18. data/docs/output-sqs.asciidoc +242 -0
  19. data/lib/logstash/codecs/cloudfront.rb +84 -0
  20. data/lib/logstash/codecs/cloudtrail.rb +47 -0
  21. data/lib/logstash/inputs/cloudwatch.rb +338 -0
  22. data/lib/logstash/inputs/s3.rb +466 -0
  23. data/lib/logstash/inputs/sqs.rb +196 -0
  24. data/lib/logstash/outputs/cloudwatch.rb +346 -0
  25. data/lib/logstash/outputs/s3/file_repository.rb +121 -0
  26. data/lib/logstash/outputs/s3/path_validator.rb +18 -0
  27. data/lib/logstash/outputs/s3/size_and_time_rotation_policy.rb +24 -0
  28. data/lib/logstash/outputs/s3/size_rotation_policy.rb +26 -0
  29. data/lib/logstash/outputs/s3/temporary_file.rb +71 -0
  30. data/lib/logstash/outputs/s3/temporary_file_factory.rb +129 -0
  31. data/lib/logstash/outputs/s3/time_rotation_policy.rb +26 -0
  32. data/lib/logstash/outputs/s3/uploader.rb +74 -0
  33. data/lib/logstash/outputs/s3/writable_directory_validator.rb +17 -0
  34. data/lib/logstash/outputs/s3/write_bucket_permission_validator.rb +60 -0
  35. data/lib/logstash/outputs/s3.rb +405 -0
  36. data/lib/logstash/outputs/sns.rb +133 -0
  37. data/lib/logstash/outputs/sqs.rb +167 -0
  38. data/lib/logstash/plugin_mixins/aws_config/generic.rb +54 -0
  39. data/lib/logstash/plugin_mixins/aws_config/v2.rb +93 -0
  40. data/lib/logstash/plugin_mixins/aws_config.rb +8 -0
  41. data/logstash-integration-aws.gemspec +52 -0
  42. data/spec/codecs/cloudfront_spec.rb +92 -0
  43. data/spec/codecs/cloudtrail_spec.rb +56 -0
  44. data/spec/fixtures/aws_credentials_file_sample_test.yml +2 -0
  45. data/spec/fixtures/aws_temporary_credentials_file_sample_test.yml +3 -0
  46. data/spec/fixtures/cloudfront.log +4 -0
  47. data/spec/fixtures/compressed.log.gee.zip +0 -0
  48. data/spec/fixtures/compressed.log.gz +0 -0
  49. data/spec/fixtures/compressed.log.gzip +0 -0
  50. data/spec/fixtures/invalid_utf8.gbk.log +2 -0
  51. data/spec/fixtures/json.log +2 -0
  52. data/spec/fixtures/json_with_message.log +2 -0
  53. data/spec/fixtures/multiline.log +6 -0
  54. data/spec/fixtures/multiple_compressed_streams.gz +0 -0
  55. data/spec/fixtures/uncompressed.log +2 -0
  56. data/spec/inputs/cloudwatch_spec.rb +85 -0
  57. data/spec/inputs/s3_spec.rb +610 -0
  58. data/spec/inputs/sincedb_spec.rb +17 -0
  59. data/spec/inputs/sqs_spec.rb +324 -0
  60. data/spec/integration/cloudwatch_spec.rb +25 -0
  61. data/spec/integration/dynamic_prefix_spec.rb +92 -0
  62. data/spec/integration/gzip_file_spec.rb +62 -0
  63. data/spec/integration/gzip_size_rotation_spec.rb +63 -0
  64. data/spec/integration/outputs/sqs_spec.rb +98 -0
  65. data/spec/integration/restore_from_crash_spec.rb +67 -0
  66. data/spec/integration/s3_spec.rb +66 -0
  67. data/spec/integration/size_rotation_spec.rb +59 -0
  68. data/spec/integration/sqs_spec.rb +110 -0
  69. data/spec/integration/stress_test_spec.rb +60 -0
  70. data/spec/integration/time_based_rotation_with_constant_write_spec.rb +60 -0
  71. data/spec/integration/time_based_rotation_with_stale_write_spec.rb +64 -0
  72. data/spec/integration/upload_current_file_on_shutdown_spec.rb +51 -0
  73. data/spec/outputs/cloudwatch_spec.rb +38 -0
  74. data/spec/outputs/s3/file_repository_spec.rb +143 -0
  75. data/spec/outputs/s3/size_and_time_rotation_policy_spec.rb +77 -0
  76. data/spec/outputs/s3/size_rotation_policy_spec.rb +41 -0
  77. data/spec/outputs/s3/temporary_file_factory_spec.rb +89 -0
  78. data/spec/outputs/s3/temporary_file_spec.rb +47 -0
  79. data/spec/outputs/s3/time_rotation_policy_spec.rb +60 -0
  80. data/spec/outputs/s3/uploader_spec.rb +69 -0
  81. data/spec/outputs/s3/writable_directory_validator_spec.rb +40 -0
  82. data/spec/outputs/s3/write_bucket_permission_validator_spec.rb +49 -0
  83. data/spec/outputs/s3_spec.rb +232 -0
  84. data/spec/outputs/sns_spec.rb +160 -0
  85. data/spec/plugin_mixin/aws_config_spec.rb +217 -0
  86. data/spec/spec_helper.rb +8 -0
  87. data/spec/support/helpers.rb +119 -0
  88. data/spec/unit/outputs/sqs_spec.rb +247 -0
  89. metadata +467 -0
@@ -0,0 +1,405 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/base"
3
+ require "logstash/namespace"
4
+ require "logstash/plugin_mixins/aws_config"
5
+ require "stud/temporary"
6
+ require "stud/task"
7
+ require "concurrent"
8
+ require "socket"
9
+ require "thread"
10
+ require "tmpdir"
11
+ require "fileutils"
12
+ require "set"
13
+ require "pathname"
14
+ require "aws-sdk-s3"
15
+
16
+ # INFORMATION:
17
+ #
18
+ # This plugin batches and uploads logstash events into Amazon Simple Storage Service (Amazon S3).
19
+ #
20
+ # Requirements:
21
+ # * Amazon S3 Bucket and S3 Access Permissions (Typically access_key_id and secret_access_key)
22
+ # * S3 PutObject permission
23
+ #
24
+ # S3 outputs create temporary files into the OS' temporary directory, you can specify where to save them using the `temporary_directory` option.
25
+ #
26
+ # S3 output files have the following format
27
+ #
28
+ # ls.s3.312bc026-2f5d-49bc-ae9f-5940cf4ad9a6.2013-04-18T10.00.tag_hello.part0.txt
29
+ #
30
+ #
31
+ # |=======
32
+ # | ls.s3 | indicate logstash plugin s3 |
33
+ # | 312bc026-2f5d-49bc-ae9f-5940cf4ad9a6 | a new, random uuid per file. |
34
+ # | 2013-04-18T10.00 | represents the time whenever you specify time_file. |
35
+ # | tag_hello | this indicates the event's tag. |
36
+ # | part0 | this means if you indicate size_file then it will generate more parts if you file.size > size_file. When a file is full it will be pushed to the bucket and then deleted from the temporary directory. If a file is empty, it is simply deleted. Empty files will not be pushed |
37
+ # |=======
38
+ #
39
+ # Crash Recovery:
40
+ # * This plugin will recover and upload temporary log files after crash/abnormal termination when using `restore` set to true
41
+ #
42
+ ##[Note regarding time_file and size_file] :
43
+ #
44
+ ## Both time_file and size_file settings can trigger a log "file rotation"
45
+ ## A log rotation pushes the current log "part" to s3 and deleted from local temporary storage.
46
+ #
47
+ ## If you specify BOTH size_file and time_file then it will create file for each tag (if specified).
48
+ ## When EITHER time_file minutes have elapsed OR log file size > size_file, a log rotation is triggered.
49
+ ##
50
+ ## If you ONLY specify time_file but NOT file_size, one file for each tag (if specified) will be created.
51
+ ## When time_file minutes elapses, a log rotation will be triggered.
52
+ #
53
+ ## If you ONLY specify size_file, but NOT time_file, one files for each tag (if specified) will be created.
54
+ ## When size of log file part > size_file, a log rotation will be triggered.
55
+ #
56
+ ## If NEITHER size_file nor time_file is specified, ONLY one file for each tag (if specified) will be created.
57
+ ## WARNING: Since no log rotation is triggered, S3 Upload will only occur when logstash restarts.
58
+ #
59
+ #
60
+ # #### Usage:
61
+ # This is an example of logstash config:
62
+ # [source,ruby]
63
+ # output {
64
+ # s3{
65
+ # access_key_id => "crazy_key" (required)
66
+ # secret_access_key => "monkey_access_key" (required)
67
+ # region => "eu-west-1" (optional, default = "us-east-1")
68
+ # bucket => "your_bucket" (required)
69
+ # size_file => 2048 (optional) - Bytes
70
+ # time_file => 5 (optional) - Minutes
71
+ # codec => "plain" (optional)
72
+ # canned_acl => "private" (optional. Options are "private", "public-read", "public-read-write", "authenticated-read", "aws-exec-read", "bucket-owner-read", "bucket-owner-full-control", "log-delivery-write". Defaults to "private" )
73
+ # }
74
+ #
75
+ class LogStash::Outputs::S3 < LogStash::Outputs::Base
76
+ require "logstash/outputs/s3/writable_directory_validator"
77
+ require "logstash/outputs/s3/path_validator"
78
+ require "logstash/outputs/s3/write_bucket_permission_validator"
79
+ require "logstash/outputs/s3/size_rotation_policy"
80
+ require "logstash/outputs/s3/time_rotation_policy"
81
+ require "logstash/outputs/s3/size_and_time_rotation_policy"
82
+ require "logstash/outputs/s3/temporary_file"
83
+ require "logstash/outputs/s3/temporary_file_factory"
84
+ require "logstash/outputs/s3/uploader"
85
+ require "logstash/outputs/s3/file_repository"
86
+
87
+ include LogStash::PluginMixins::AwsConfig::V2
88
+
89
+ PREFIX_KEY_NORMALIZE_CHARACTER = "_"
90
+ PERIODIC_CHECK_INTERVAL_IN_SECONDS = 15
91
+ CRASH_RECOVERY_THREADPOOL = Concurrent::ThreadPoolExecutor.new({
92
+ :min_threads => 1,
93
+ :max_threads => 2,
94
+ :fallback_policy => :caller_runs
95
+ })
96
+
97
+
98
+ config_name "s3"
99
+ default :codec, "line"
100
+
101
+ concurrency :shared
102
+
103
+ # S3 bucket
104
+ config :bucket, :validate => :string, :required => true
105
+
106
+ config :additional_settings, :validate => :hash, :default => {}
107
+
108
+ # Set the size of file in bytes, this means that files on bucket when have dimension > file_size, they are stored in two or more file.
109
+ # If you have tags then it will generate a specific size file for every tags
110
+ ##NOTE: define size of file is the better thing, because generate a local temporary file on disk and then put it in bucket.
111
+ config :size_file, :validate => :number, :default => 1024 * 1024 * 5
112
+
113
+ # Set the time, in MINUTES, to close the current sub_time_section of bucket.
114
+ # If you also define file_size you have a number of files related to the section and the current tag.
115
+ # If it's valued 0 and rotation_strategy is 'time' or 'size_and_time' then the plugin reaise a configuration error.
116
+ config :time_file, :validate => :number, :default => 15
117
+
118
+ ## IMPORTANT: if you use multiple instance of s3, you should specify on one of them the "restore=> true" and on the others "restore => false".
119
+ ## This is hack for not destroy the new files after restoring the initial files.
120
+ ## If you do not specify "restore => true" when logstash crashes or is restarted, the files are not sent into the bucket,
121
+ ## for example if you have single Instance.
122
+ config :restore, :validate => :boolean, :default => true
123
+
124
+ # The S3 canned ACL to use when putting the file. Defaults to "private".
125
+ config :canned_acl, :validate => ["private", "public-read", "public-read-write", "authenticated-read", "aws-exec-read", "bucket-owner-read", "bucket-owner-full-control", "log-delivery-write"],
126
+ :default => "private"
127
+
128
+ # Specifies whether or not to use S3's server side encryption. Defaults to no encryption.
129
+ config :server_side_encryption, :validate => :boolean, :default => false
130
+
131
+ # Specifies what type of encryption to use when SSE is enabled.
132
+ config :server_side_encryption_algorithm, :validate => ["AES256", "aws:kms"], :default => "AES256"
133
+
134
+ # The key to use when specified along with server_side_encryption => aws:kms.
135
+ # If server_side_encryption => aws:kms is set but this is not default KMS key is used.
136
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html
137
+ config :ssekms_key_id, :validate => :string
138
+
139
+ # Specifies what S3 storage class to use when uploading the file.
140
+ # More information about the different storage classes can be found:
141
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/storage-class-intro.html
142
+ # Defaults to STANDARD.
143
+ config :storage_class, :validate => ["STANDARD", "REDUCED_REDUNDANCY", "STANDARD_IA", "ONEZONE_IA"], :default => "STANDARD"
144
+
145
+ # Set the directory where logstash will store the tmp files before sending it to S3
146
+ # default to the current OS temporary directory in linux /tmp/logstash
147
+ config :temporary_directory, :validate => :string, :default => File.join(Dir.tmpdir, "logstash")
148
+
149
+ # Specify a prefix to the uploaded filename, this can simulate directories on S3. Prefix does not require leading slash.
150
+ # This option support string interpolation, be warned this can created a lot of temporary local files.
151
+ config :prefix, :validate => :string, :default => ''
152
+
153
+ # Specify how many workers to use to upload the files to S3
154
+ config :upload_workers_count, :validate => :number, :default => (Concurrent.processor_count * 0.5).ceil
155
+
156
+ # Number of items we can keep in the local queue before uploading them
157
+ config :upload_queue_size, :validate => :number, :default => 2 * (Concurrent.processor_count * 0.25).ceil
158
+
159
+ # Files larger than this number are uploaded using the S3 multipart APIs. Default threshold is 15MB.
160
+ config :upload_multipart_threshold, :validate => :number, :default => 15 * 1024 * 1024
161
+
162
+ # The version of the S3 signature hash to use. Normally uses the internal client default, can be explicitly
163
+ # specified here
164
+ config :signature_version, :validate => ['v2', 'v4']
165
+
166
+ # Define tags to be appended to the file on the S3 bucket.
167
+ #
168
+ # Example:
169
+ # tags => ["elasticsearch", "logstash", "kibana"]
170
+ #
171
+ # Will generate this file:
172
+ # "ls.s3.logstash.local.2015-01-01T00.00.tag_elasticsearch.logstash.kibana.part0.txt"
173
+ #
174
+ config :tags, :validate => :array, :default => []
175
+
176
+ # Specify the content encoding. Supports ("gzip"). Defaults to "none"
177
+ config :encoding, :validate => ["none", "gzip"], :default => "none"
178
+
179
+ # Define the strategy to use to decide when we need to rotate the file and push it to S3,
180
+ # The default strategy is to check for both size and time, the first one to match will rotate the file.
181
+ config :rotation_strategy, :validate => ["size_and_time", "size", "time"], :default => "size_and_time"
182
+
183
+ # The common use case is to define permission on the root bucket and give Logstash full access to write its logs.
184
+ # In some circonstances you need finer grained permission on subfolder, this allow you to disable the check at startup.
185
+ config :validate_credentials_on_root_bucket, :validate => :boolean, :default => true
186
+
187
+ # The number of times to retry a failed S3 upload.
188
+ config :retry_count, :validate => :number, :default => Float::INFINITY
189
+
190
+ # The amount of time to wait in seconds before attempting to retry a failed upload.
191
+ config :retry_delay, :validate => :number, :default => 1
192
+
193
+ def register
194
+ # I've move the validation of the items into custom classes
195
+ # to prepare for the new config validation that will be part of the core so the core can
196
+ # be moved easily.
197
+ unless @prefix.empty?
198
+ if !PathValidator.valid?(prefix)
199
+ raise LogStash::ConfigurationError, "Prefix must not contains: #{PathValidator::INVALID_CHARACTERS}"
200
+ end
201
+ end
202
+
203
+ if !WritableDirectoryValidator.valid?(@temporary_directory)
204
+ raise LogStash::ConfigurationError, "Logstash must have the permissions to write to the temporary directory: #{@temporary_directory}"
205
+ end
206
+
207
+ if @validate_credentials_on_root_bucket && !WriteBucketPermissionValidator.new(@logger).valid?(bucket_resource, upload_options)
208
+ raise LogStash::ConfigurationError, "Logstash must have the privileges to write to root bucket `#{@bucket}`, check your credentials or your permissions."
209
+ end
210
+
211
+ if @time_file.nil? && @size_file.nil? || @size_file == 0 && @time_file == 0
212
+ raise LogStash::ConfigurationError, "The S3 plugin must have at least one of time_file or size_file set to a value greater than 0"
213
+ end
214
+
215
+ @file_repository = FileRepository.new(@tags, @encoding, @temporary_directory)
216
+
217
+ @rotation = rotation_strategy
218
+
219
+ executor = Concurrent::ThreadPoolExecutor.new({ :min_threads => 1,
220
+ :max_threads => @upload_workers_count,
221
+ :max_queue => @upload_queue_size,
222
+ :fallback_policy => :caller_runs })
223
+
224
+ @uploader = Uploader.new(bucket_resource, @logger, executor, retry_count: @retry_count, retry_delay: @retry_delay)
225
+
226
+ # Restoring from crash will use a new threadpool to slowly recover
227
+ # New events should have more priority.
228
+ restore_from_crash if @restore
229
+
230
+ # If we need time based rotation we need to do periodic check on the file
231
+ # to take care of file that were not updated recently
232
+ start_periodic_check if @rotation.needs_periodic?
233
+ end
234
+
235
+ def multi_receive_encoded(events_and_encoded)
236
+ prefix_written_to = Set.new
237
+
238
+ events_and_encoded.each do |event, encoded|
239
+ prefix_key = normalize_key(event.sprintf(@prefix))
240
+ prefix_written_to << prefix_key
241
+
242
+ begin
243
+ @file_repository.get_file(prefix_key) { |file| file.write(encoded) }
244
+ # The output should stop accepting new events coming in, since it cannot do anything with them anymore.
245
+ # Log the error and rethrow it.
246
+ rescue Errno::ENOSPC => e
247
+ @logger.error("S3: No space left in temporary directory", :temporary_directory => @temporary_directory)
248
+ raise e
249
+ end
250
+ end
251
+
252
+ # Groups IO calls to optimize fstat checks
253
+ rotate_if_needed(prefix_written_to)
254
+ end
255
+
256
+ def close
257
+ stop_periodic_check if @rotation.needs_periodic?
258
+
259
+ @logger.debug("Uploading current workspace")
260
+
261
+ # The plugin has stopped receiving new events, but we still have
262
+ # data on disk, lets make sure it get to S3.
263
+ # If Logstash get interrupted, the `restore_from_crash` (when set to true) method will pickup
264
+ # the content in the temporary directly and upload it.
265
+ # This will block the shutdown until all upload are done or the use force quit.
266
+ @file_repository.each_files do |file|
267
+ upload_file(file)
268
+ end
269
+
270
+ @file_repository.shutdown
271
+
272
+ @uploader.stop # wait until all the current upload are complete
273
+ @crash_uploader.stop if @restore # we might have still work to do for recovery so wait until we are done
274
+ end
275
+
276
+ def full_options
277
+ options = aws_options_hash || {}
278
+ options[:signature_version] = @signature_version if @signature_version
279
+ symbolized_settings.merge(options)
280
+ end
281
+
282
+ def symbolized_settings
283
+ @symbolized_settings ||= symbolize_keys_and_cast_true_false(@additional_settings)
284
+ end
285
+
286
+ def symbolize_keys_and_cast_true_false(hash)
287
+ case hash
288
+ when Hash
289
+ symbolized = {}
290
+ hash.each { |key, value| symbolized[key.to_sym] = symbolize_keys_and_cast_true_false(value) }
291
+ symbolized
292
+ when 'true'
293
+ true
294
+ when 'false'
295
+ false
296
+ else
297
+ hash
298
+ end
299
+ end
300
+
301
+ def normalize_key(prefix_key)
302
+ prefix_key.gsub(PathValidator.matches_re, PREFIX_KEY_NORMALIZE_CHARACTER)
303
+ end
304
+
305
+ def upload_options
306
+ {
307
+ :acl => @canned_acl,
308
+ :server_side_encryption => @server_side_encryption ? @server_side_encryption_algorithm : nil,
309
+ :ssekms_key_id => @server_side_encryption_algorithm == "aws:kms" ? @ssekms_key_id : nil,
310
+ :storage_class => @storage_class,
311
+ :content_encoding => @encoding == "gzip" ? "gzip" : nil,
312
+ :multipart_threshold => @upload_multipart_threshold
313
+ }
314
+ end
315
+
316
+ private
317
+ # We start a task in the background for check for stale files and make sure we rotate them to S3 if needed.
318
+ def start_periodic_check
319
+ @logger.debug("Start periodic rotation check")
320
+
321
+ @periodic_check = Concurrent::TimerTask.new(:execution_interval => PERIODIC_CHECK_INTERVAL_IN_SECONDS) do
322
+ @logger.debug("Periodic check for stale files")
323
+
324
+ rotate_if_needed(@file_repository.keys)
325
+ end
326
+
327
+ @periodic_check.execute
328
+ end
329
+
330
+ def stop_periodic_check
331
+ @periodic_check.shutdown
332
+ end
333
+
334
+ def bucket_resource
335
+ Aws::S3::Bucket.new(@bucket, full_options)
336
+ end
337
+
338
+ def rotate_if_needed(prefixes)
339
+ prefixes.each do |prefix|
340
+ # Each file access is thread safe,
341
+ # until the rotation is done then only
342
+ # one thread has access to the resource.
343
+ @file_repository.get_factory(prefix) do |factory|
344
+ temp_file = factory.current
345
+
346
+ if @rotation.rotate?(temp_file)
347
+ @logger.debug("Rotate file",
348
+ :strategy => @rotation.class.name,
349
+ :key => temp_file.key,
350
+ :path => temp_file.path)
351
+
352
+ upload_file(temp_file)
353
+ factory.rotate!
354
+ end
355
+ end
356
+ end
357
+ end
358
+
359
+ def upload_file(temp_file)
360
+ @logger.debug("Queue for upload", :path => temp_file.path)
361
+
362
+ # if the queue is full the calling thread will be used to upload
363
+ temp_file.close # make sure the content is on disk
364
+ if temp_file.size > 0
365
+ @uploader.upload_async(temp_file,
366
+ :on_complete => method(:clean_temporary_file),
367
+ :upload_options => upload_options )
368
+ end
369
+ end
370
+
371
+ def rotation_strategy
372
+ case @rotation_strategy
373
+ when "size"
374
+ SizeRotationPolicy.new(size_file)
375
+ when "time"
376
+ TimeRotationPolicy.new(time_file)
377
+ when "size_and_time"
378
+ SizeAndTimeRotationPolicy.new(size_file, time_file)
379
+ end
380
+ end
381
+
382
+ def clean_temporary_file(file)
383
+ @logger.debug("Removing temporary file", :file => file.path)
384
+ file.delete!
385
+ end
386
+
387
+ # The upload process will use a separate uploader/threadpool with less resource allocated to it.
388
+ # but it will use an unbounded queue for the work, it may take some time before all the older files get processed.
389
+ def restore_from_crash
390
+ @crash_uploader = Uploader.new(bucket_resource, @logger, CRASH_RECOVERY_THREADPOOL)
391
+
392
+ temp_folder_path = Pathname.new(@temporary_directory)
393
+ Dir.glob(::File.join(@temporary_directory, "**/*"))
394
+ .select { |file| ::File.file?(file) }
395
+ .each do |file|
396
+ temp_file = TemporaryFile.create_from_existing_file(file, temp_folder_path)
397
+ if temp_file.size > 0
398
+ @logger.debug("Recovering from crash and uploading", :file => temp_file.path)
399
+ @crash_uploader.upload_async(temp_file, :on_complete => method(:clean_temporary_file), :upload_options => upload_options)
400
+ else
401
+ clean_temporary_file(temp_file)
402
+ end
403
+ end
404
+ end
405
+ end
@@ -0,0 +1,133 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/base"
3
+ require "logstash/namespace"
4
+ require "logstash/plugin_mixins/aws_config"
5
+ require "logstash/util"
6
+ require "logstash/util/unicode_trimmer"
7
+
8
+ # SNS output.
9
+ #
10
+ # Send events to Amazon's Simple Notification Service, a hosted pub/sub
11
+ # framework. It supports various subscription types, including email, HTTP/S, SMS, and SQS.
12
+ #
13
+ # For further documentation about the service see:
14
+ #
15
+ # http://docs.amazonwebservices.com/sns/latest/api/
16
+ #
17
+ # This plugin looks for the following fields on events it receives:
18
+ #
19
+ # * `sns` - If no ARN is found in the configuration file, this will be used as
20
+ # the ARN to publish.
21
+ # * `sns_subject` - The subject line that should be used.
22
+ # Optional. The "%{host}" will be used if `sns_subject` is not present. The subject
23
+ # will be truncated to 100 characters. If `sns_subject` is set to a non-string value a JSON version of that value will be saved.
24
+ # * `sns_message` - Optional string of message to be sent. If this is set to a non-string value it will be encoded with the specified `codec`. If this is not set the entire event will be encoded with the codec.
25
+ # with the @message truncated so that the length of the JSON fits in
26
+ # `32768` bytes.
27
+ #
28
+ # ==== Upgrading to 2.0.0
29
+ #
30
+ # This plugin used to have a `format` option for controlling the encoding of messages prior to being sent to SNS.
31
+ # This plugin now uses the logstash standard <<codec,codec>> option for encoding instead.
32
+ # If you want the same 'plain' format as the v0/1 codec (`format => "plain"`) use `codec => "s3_plain"`.
33
+ #
34
+ class LogStash::Outputs::Sns < LogStash::Outputs::Base
35
+ include LogStash::PluginMixins::AwsConfig::V2
36
+
37
+ MAX_SUBJECT_SIZE_IN_CHARACTERS = 100
38
+ MAX_MESSAGE_SIZE_IN_BYTES = 32768
39
+ NO_SUBJECT = "NO SUBJECT"
40
+
41
+ config_name "sns"
42
+
43
+ concurrency :shared
44
+
45
+ # Optional ARN to send messages to. If you do not set this you must
46
+ # include the `sns` field in your events to set the ARN on a per-message basis!
47
+ config :arn, :validate => :string
48
+
49
+ # When an ARN for an SNS topic is specified here, the message
50
+ # "Logstash successfully booted" will be sent to it when this plugin
51
+ # is registered.
52
+ #
53
+ # Example: arn:aws:sns:us-east-1:770975001275:logstash-testing
54
+ #
55
+ config :publish_boot_message_arn, :validate => :string
56
+
57
+ public
58
+ def register
59
+ require "aws-sdk-sns"
60
+
61
+ @sns = Aws::SNS::Client.new(aws_options_hash)
62
+
63
+ publish_boot_message_arn()
64
+
65
+ @codec.on_event do |event, encoded|
66
+ send_sns_message(event_arn(event), event_subject(event), encoded)
67
+ end
68
+ end
69
+
70
+ public
71
+ def receive(event)
72
+
73
+
74
+ if (sns_msg = event.get("sns_message"))
75
+ if sns_msg.is_a?(String)
76
+ send_sns_message(event_arn(event), event_subject(event), sns_msg)
77
+ else
78
+ @codec.encode(sns_msg)
79
+ end
80
+ else
81
+ @codec.encode(event)
82
+ end
83
+ end
84
+
85
+ private
86
+ def publish_boot_message_arn
87
+ # Try to publish a "Logstash booted" message to the ARN provided to
88
+ # cause an error ASAP if the credentials are bad.
89
+ if @publish_boot_message_arn
90
+ send_sns_message(@publish_boot_message_arn, 'Logstash booted', 'Logstash successfully booted')
91
+ end
92
+ end
93
+
94
+ private
95
+ def send_sns_message(arn, subject, message)
96
+ raise ArgumentError, 'An SNS ARN is required.' unless arn
97
+
98
+ trunc_subj = LogStash::Util::UnicodeTrimmer.trim_bytes(subject, MAX_SUBJECT_SIZE_IN_CHARACTERS)
99
+ trunc_msg = LogStash::Util::UnicodeTrimmer.trim_bytes(message, MAX_MESSAGE_SIZE_IN_BYTES)
100
+
101
+ @logger.debug? && @logger.debug("Sending event to SNS topic [#{arn}] with subject [#{trunc_subj}] and message: #{trunc_msg}")
102
+
103
+ @sns.publish({
104
+ :topic_arn => arn,
105
+ :subject => trunc_subj,
106
+ :message => trunc_msg
107
+ })
108
+ end
109
+
110
+ private
111
+ def event_subject(event)
112
+ sns_subject = event.get("sns_subject")
113
+ if sns_subject.is_a?(String)
114
+ sns_subject
115
+ elsif sns_subject
116
+ LogStash::Json.dump(sns_subject)
117
+ elsif event.get("host")
118
+ host = event.get("host")
119
+ if host.is_a?(Hash) # ECS mode
120
+ host['name'] || host['hostname'] || host['ip'] || NO_SUBJECT
121
+ else
122
+ host.to_s
123
+ end
124
+ else
125
+ NO_SUBJECT
126
+ end
127
+ end
128
+
129
+ private
130
+ def event_arn(event)
131
+ event.get("sns") || @arn
132
+ end
133
+ end