logstash-integration-aws 0.1.0.pre

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.
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