logstash-integration-aws 7.1.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.PRE.MERGE.md +658 -0
  3. data/CHANGELOG.md +33 -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/VERSION +1 -0
  10. data/docs/codec-cloudfront.asciidoc +53 -0
  11. data/docs/codec-cloudtrail.asciidoc +45 -0
  12. data/docs/index.asciidoc +36 -0
  13. data/docs/input-cloudwatch.asciidoc +320 -0
  14. data/docs/input-s3.asciidoc +346 -0
  15. data/docs/input-sqs.asciidoc +287 -0
  16. data/docs/output-cloudwatch.asciidoc +321 -0
  17. data/docs/output-s3.asciidoc +442 -0
  18. data/docs/output-sns.asciidoc +166 -0
  19. data/docs/output-sqs.asciidoc +242 -0
  20. data/lib/logstash/codecs/cloudfront.rb +84 -0
  21. data/lib/logstash/codecs/cloudtrail.rb +47 -0
  22. data/lib/logstash/inputs/cloudwatch.rb +338 -0
  23. data/lib/logstash/inputs/s3.rb +466 -0
  24. data/lib/logstash/inputs/sqs.rb +196 -0
  25. data/lib/logstash/outputs/cloudwatch.rb +346 -0
  26. data/lib/logstash/outputs/s3/file_repository.rb +193 -0
  27. data/lib/logstash/outputs/s3/path_validator.rb +18 -0
  28. data/lib/logstash/outputs/s3/size_and_time_rotation_policy.rb +24 -0
  29. data/lib/logstash/outputs/s3/size_rotation_policy.rb +26 -0
  30. data/lib/logstash/outputs/s3/temporary_file.rb +114 -0
  31. data/lib/logstash/outputs/s3/temporary_file_factory.rb +126 -0
  32. data/lib/logstash/outputs/s3/time_rotation_policy.rb +26 -0
  33. data/lib/logstash/outputs/s3/uploader.rb +76 -0
  34. data/lib/logstash/outputs/s3/writable_directory_validator.rb +17 -0
  35. data/lib/logstash/outputs/s3/write_bucket_permission_validator.rb +60 -0
  36. data/lib/logstash/outputs/s3.rb +442 -0
  37. data/lib/logstash/outputs/sns.rb +133 -0
  38. data/lib/logstash/outputs/sqs.rb +167 -0
  39. data/lib/logstash/plugin_mixins/aws_config/generic.rb +54 -0
  40. data/lib/logstash/plugin_mixins/aws_config/v2.rb +93 -0
  41. data/lib/logstash/plugin_mixins/aws_config.rb +8 -0
  42. data/lib/logstash-integration-aws_jars.rb +4 -0
  43. data/lib/tasks/build.rake +15 -0
  44. data/logstash-integration-aws.gemspec +55 -0
  45. data/spec/codecs/cloudfront_spec.rb +92 -0
  46. data/spec/codecs/cloudtrail_spec.rb +56 -0
  47. data/spec/fixtures/aws_credentials_file_sample_test.yml +2 -0
  48. data/spec/fixtures/aws_temporary_credentials_file_sample_test.yml +3 -0
  49. data/spec/fixtures/cloudfront.log +4 -0
  50. data/spec/fixtures/compressed.log.gee.zip +0 -0
  51. data/spec/fixtures/compressed.log.gz +0 -0
  52. data/spec/fixtures/compressed.log.gzip +0 -0
  53. data/spec/fixtures/invalid_utf8.gbk.log +2 -0
  54. data/spec/fixtures/json.log +2 -0
  55. data/spec/fixtures/json_with_message.log +2 -0
  56. data/spec/fixtures/multiline.log +6 -0
  57. data/spec/fixtures/multiple_compressed_streams.gz +0 -0
  58. data/spec/fixtures/uncompressed.log +2 -0
  59. data/spec/inputs/cloudwatch_spec.rb +85 -0
  60. data/spec/inputs/s3_spec.rb +610 -0
  61. data/spec/inputs/sincedb_spec.rb +17 -0
  62. data/spec/inputs/sqs_spec.rb +324 -0
  63. data/spec/integration/cloudwatch_spec.rb +25 -0
  64. data/spec/integration/dynamic_prefix_spec.rb +92 -0
  65. data/spec/integration/gzip_file_spec.rb +62 -0
  66. data/spec/integration/gzip_size_rotation_spec.rb +63 -0
  67. data/spec/integration/outputs/sqs_spec.rb +98 -0
  68. data/spec/integration/restore_from_crash_spec.rb +133 -0
  69. data/spec/integration/s3_spec.rb +66 -0
  70. data/spec/integration/size_rotation_spec.rb +59 -0
  71. data/spec/integration/sqs_spec.rb +110 -0
  72. data/spec/integration/stress_test_spec.rb +60 -0
  73. data/spec/integration/time_based_rotation_with_constant_write_spec.rb +60 -0
  74. data/spec/integration/time_based_rotation_with_stale_write_spec.rb +64 -0
  75. data/spec/integration/upload_current_file_on_shutdown_spec.rb +51 -0
  76. data/spec/outputs/cloudwatch_spec.rb +38 -0
  77. data/spec/outputs/s3/file_repository_spec.rb +143 -0
  78. data/spec/outputs/s3/size_and_time_rotation_policy_spec.rb +77 -0
  79. data/spec/outputs/s3/size_rotation_policy_spec.rb +41 -0
  80. data/spec/outputs/s3/temporary_file_factory_spec.rb +89 -0
  81. data/spec/outputs/s3/temporary_file_spec.rb +47 -0
  82. data/spec/outputs/s3/time_rotation_policy_spec.rb +60 -0
  83. data/spec/outputs/s3/uploader_spec.rb +69 -0
  84. data/spec/outputs/s3/writable_directory_validator_spec.rb +40 -0
  85. data/spec/outputs/s3/write_bucket_permission_validator_spec.rb +49 -0
  86. data/spec/outputs/s3_spec.rb +232 -0
  87. data/spec/outputs/sns_spec.rb +160 -0
  88. data/spec/plugin_mixin/aws_config_spec.rb +217 -0
  89. data/spec/spec_helper.rb +8 -0
  90. data/spec/support/helpers.rb +121 -0
  91. data/spec/unit/outputs/sqs_spec.rb +247 -0
  92. data/vendor/jar-dependencies/org/logstash/plugins/integration/aws/logstash-integration-aws/7.1.1/logstash-integration-aws-7.1.1.jar +0 -0
  93. metadata +472 -0
@@ -0,0 +1,442 @@
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
+ GZIP_ENCODING = "gzip"
98
+
99
+ config_name "s3"
100
+ default :codec, "line"
101
+
102
+ concurrency :shared
103
+
104
+ # S3 bucket
105
+ config :bucket, :validate => :string, :required => true
106
+
107
+ config :additional_settings, :validate => :hash, :default => {}
108
+
109
+ # 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.
110
+ # If you have tags then it will generate a specific size file for every tags
111
+ #
112
+ # NOTE: define size of file is the better thing, because generate a local temporary file on disk and then put it in bucket.
113
+ config :size_file, :validate => :number, :default => 1024 * 1024 * 5
114
+
115
+ # Set the time, in MINUTES, to close the current sub_time_section of bucket.
116
+ # If you also define file_size you have a number of files related to the section and the current tag.
117
+ # If it's valued 0 and rotation_strategy is 'time' or 'size_and_time' then the plugin reaise a configuration error.
118
+ config :time_file, :validate => :number, :default => 15
119
+
120
+ # If `restore => false` is specified and Logstash crashes, the unprocessed files are not sent into the bucket.
121
+ #
122
+ # NOTE: that the `recovery => true` default assumes multiple S3 outputs would set a unique `temporary_directory => ...`
123
+ # if they do not than only a single S3 output is safe to recover (since let-over files are processed and deleted).
124
+ config :restore, :validate => :boolean, :default => true
125
+
126
+ # The S3 canned ACL to use when putting the file. Defaults to "private".
127
+ 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"],
128
+ :default => "private"
129
+
130
+ # Specifies whether or not to use S3's server side encryption. Defaults to no encryption.
131
+ config :server_side_encryption, :validate => :boolean, :default => false
132
+
133
+ # Specifies what type of encryption to use when SSE is enabled.
134
+ config :server_side_encryption_algorithm, :validate => ["AES256", "aws:kms"], :default => "AES256"
135
+
136
+ # The key to use when specified along with server_side_encryption => aws:kms.
137
+ # If server_side_encryption => aws:kms is set but this is not default KMS key is used.
138
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html
139
+ config :ssekms_key_id, :validate => :string
140
+
141
+ # Specifies what S3 storage class to use when uploading the file.
142
+ # More information about the different storage classes can be found:
143
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/storage-class-intro.html
144
+ # Defaults to STANDARD.
145
+ config :storage_class, :validate => ["STANDARD", "REDUCED_REDUNDANCY", "STANDARD_IA", "ONEZONE_IA"], :default => "STANDARD"
146
+
147
+ # Set the directory where logstash will store the tmp files before sending it to S3
148
+ # default to the current OS temporary directory in linux /tmp/logstash
149
+ #
150
+ # NOTE: the reason we do not have a unique (isolated) temporary directory as a default, to support multiple plugin instances,
151
+ # is that we would have to rely on something static that does not change between restarts (e.g. a user set id => ...).
152
+ config :temporary_directory, :validate => :string, :default => File.join(Dir.tmpdir, "logstash")
153
+
154
+ # Specify a prefix to the uploaded filename, this can simulate directories on S3. Prefix does not require leading slash.
155
+ # This option support string interpolation, be warned this can created a lot of temporary local files.
156
+ config :prefix, :validate => :string, :default => ''
157
+
158
+ # Specify how many workers to use to upload the files to S3
159
+ config :upload_workers_count, :validate => :number, :default => (Concurrent.processor_count * 0.5).ceil
160
+
161
+ # Number of items we can keep in the local queue before uploading them
162
+ config :upload_queue_size, :validate => :number, :default => 2 * (Concurrent.processor_count * 0.25).ceil
163
+
164
+ # Files larger than this number are uploaded using the S3 multipart APIs. Default threshold is 15MB.
165
+ config :upload_multipart_threshold, :validate => :number, :default => 15 * 1024 * 1024
166
+
167
+ # The version of the S3 signature hash to use. Normally uses the internal client default, can be explicitly
168
+ # specified here
169
+ config :signature_version, :validate => ['v2', 'v4']
170
+
171
+ # Define tags to be appended to the file on the S3 bucket.
172
+ #
173
+ # Example:
174
+ # tags => ["elasticsearch", "logstash", "kibana"]
175
+ #
176
+ # Will generate this file:
177
+ # "ls.s3.logstash.local.2015-01-01T00.00.tag_elasticsearch.logstash.kibana.part0.txt"
178
+ #
179
+ config :tags, :validate => :array, :default => []
180
+
181
+ # Specify the content encoding. Supports ("gzip"). Defaults to "none"
182
+ config :encoding, :validate => ["none", GZIP_ENCODING], :default => "none"
183
+
184
+ # Define the strategy to use to decide when we need to rotate the file and push it to S3,
185
+ # The default strategy is to check for both size and time, the first one to match will rotate the file.
186
+ config :rotation_strategy, :validate => ["size_and_time", "size", "time"], :default => "size_and_time"
187
+
188
+ # The common use case is to define permission on the root bucket and give Logstash full access to write its logs.
189
+ # In some circonstances you need finer grained permission on subfolder, this allow you to disable the check at startup.
190
+ config :validate_credentials_on_root_bucket, :validate => :boolean, :default => true
191
+
192
+ # The number of times to retry a failed S3 upload.
193
+ config :retry_count, :validate => :number, :default => Float::INFINITY
194
+
195
+ # The amount of time to wait in seconds before attempting to retry a failed upload.
196
+ config :retry_delay, :validate => :number, :default => 1
197
+
198
+ def register
199
+ # I've move the validation of the items into custom classes
200
+ # to prepare for the new config validation that will be part of the core so the core can
201
+ # be moved easily.
202
+ unless @prefix.empty?
203
+ if !PathValidator.valid?(prefix)
204
+ raise LogStash::ConfigurationError, "Prefix must not contains: #{PathValidator::INVALID_CHARACTERS}"
205
+ end
206
+ end
207
+
208
+ if !WritableDirectoryValidator.valid?(@temporary_directory)
209
+ raise LogStash::ConfigurationError, "Logstash must have the permissions to write to the temporary directory: #{@temporary_directory}"
210
+ end
211
+
212
+ if @validate_credentials_on_root_bucket && !WriteBucketPermissionValidator.new(@logger).valid?(bucket_resource, upload_options)
213
+ raise LogStash::ConfigurationError, "Logstash must have the privileges to write to root bucket `#{@bucket}`, check your credentials or your permissions."
214
+ end
215
+
216
+ if @time_file.nil? && @size_file.nil? || @size_file == 0 && @time_file == 0
217
+ raise LogStash::ConfigurationError, "The S3 plugin must have at least one of time_file or size_file set to a value greater than 0"
218
+ end
219
+
220
+ @file_repository = FileRepository.new(@tags, @encoding, @temporary_directory)
221
+
222
+ @rotation = rotation_strategy
223
+
224
+ executor = Concurrent::ThreadPoolExecutor.new({ :min_threads => 1,
225
+ :max_threads => @upload_workers_count,
226
+ :max_queue => @upload_queue_size,
227
+ :fallback_policy => :caller_runs })
228
+
229
+ @uploader = Uploader.new(bucket_resource, @logger, executor, retry_count: @retry_count, retry_delay: @retry_delay)
230
+
231
+ # Restoring from crash will use a new threadpool to slowly recover
232
+ # New events should have more priority.
233
+ restore_from_crash if @restore
234
+
235
+ # If we need time based rotation we need to do periodic check on the file
236
+ # to take care of file that were not updated recently
237
+ start_periodic_check if @rotation.needs_periodic?
238
+ end
239
+
240
+ def multi_receive_encoded(events_and_encoded)
241
+ prefix_written_to = Set.new
242
+
243
+ events_and_encoded.each do |event, encoded|
244
+ prefix_key = normalize_key(event.sprintf(@prefix))
245
+ prefix_written_to << prefix_key
246
+
247
+ begin
248
+ @file_repository.get_file(prefix_key) { |file| file.write(encoded) }
249
+ # The output should stop accepting new events coming in, since it cannot do anything with them anymore.
250
+ # Log the error and rethrow it.
251
+ rescue Errno::ENOSPC => e
252
+ @logger.error("S3: No space left in temporary directory", :temporary_directory => @temporary_directory)
253
+ raise e
254
+ end
255
+ end
256
+
257
+ # Groups IO calls to optimize fstat checks
258
+ rotate_if_needed(prefix_written_to)
259
+ end
260
+
261
+ def close
262
+ stop_periodic_check if @rotation.needs_periodic?
263
+
264
+ @logger.debug("Uploading current workspace")
265
+
266
+ @file_repository.shutdown # stop stale sweeps
267
+
268
+ # The plugin has stopped receiving new events, but we still have
269
+ # data on disk, lets make sure it get to S3.
270
+ # If Logstash get interrupted, the `restore_from_crash` (when set to true) method will pickup
271
+ # the content in the temporary directly and upload it.
272
+ # This will block the shutdown until all upload are done or the use force quit.
273
+ @file_repository.each_files do |file|
274
+ upload_file(file)
275
+ end
276
+
277
+ @uploader.stop # wait until all the current upload are complete
278
+ @crash_uploader.stop if @restore # we might have still work to do for recovery so wait until we are done
279
+ end
280
+
281
+ def full_options
282
+ options = aws_options_hash || {}
283
+ options[:signature_version] = @signature_version if @signature_version
284
+ symbolized_settings.merge(options)
285
+ end
286
+
287
+ def symbolized_settings
288
+ @symbolized_settings ||= symbolize_keys_and_cast_true_false(@additional_settings)
289
+ end
290
+
291
+ def symbolize_keys_and_cast_true_false(hash)
292
+ case hash
293
+ when Hash
294
+ symbolized = {}
295
+ hash.each { |key, value| symbolized[key.to_sym] = symbolize_keys_and_cast_true_false(value) }
296
+ symbolized
297
+ when 'true'
298
+ true
299
+ when 'false'
300
+ false
301
+ else
302
+ hash
303
+ end
304
+ end
305
+
306
+ def normalize_key(prefix_key)
307
+ prefix_key.gsub(PathValidator.matches_re, PREFIX_KEY_NORMALIZE_CHARACTER)
308
+ end
309
+
310
+ def upload_options
311
+ {
312
+ :acl => @canned_acl,
313
+ :server_side_encryption => @server_side_encryption ? @server_side_encryption_algorithm : nil,
314
+ :ssekms_key_id => @server_side_encryption_algorithm == "aws:kms" ? @ssekms_key_id : nil,
315
+ :storage_class => @storage_class,
316
+ :content_encoding => @encoding == GZIP_ENCODING ? GZIP_ENCODING : nil,
317
+ :multipart_threshold => @upload_multipart_threshold
318
+ }
319
+ end
320
+
321
+ private
322
+ # We start a task in the background for check for stale files and make sure we rotate them to S3 if needed.
323
+ def start_periodic_check
324
+ @logger.debug("Start periodic rotation check")
325
+
326
+ @periodic_check = Concurrent::TimerTask.new(:execution_interval => PERIODIC_CHECK_INTERVAL_IN_SECONDS) do
327
+ @logger.debug("Periodic check for stale files")
328
+
329
+ rotate_if_needed(@file_repository.keys)
330
+ end
331
+
332
+ @periodic_check.execute
333
+ end
334
+
335
+ def stop_periodic_check
336
+ @periodic_check.shutdown
337
+ end
338
+
339
+ def bucket_resource
340
+ Aws::S3::Bucket.new(@bucket, full_options)
341
+ end
342
+
343
+ def rotate_if_needed(prefixes)
344
+ # Each file access is thread safe,
345
+ # until the rotation is done then only
346
+ # one thread has access to the resource.
347
+ @file_repository.each_factory(prefixes) do |factory|
348
+ # we have exclusive access to the one-and-only
349
+ # prefix WRAPPER for this factory.
350
+ temp_file = factory.current
351
+
352
+ if @rotation.rotate?(temp_file)
353
+ @logger.debug? && @logger.debug("Rotate file",
354
+ :key => temp_file.key,
355
+ :path => temp_file.path,
356
+ :strategy => @rotation.class.name)
357
+
358
+ upload_file(temp_file) # may be async or blocking
359
+ factory.rotate!
360
+ end
361
+ end
362
+ end
363
+
364
+ def upload_file(temp_file)
365
+ @logger.debug? && @logger.debug("Queue for upload", :path => temp_file.path)
366
+
367
+ # if the queue is full the calling thread will be used to upload
368
+ temp_file.close # make sure the content is on disk
369
+ if temp_file.size > 0
370
+ @uploader.upload_async(temp_file,
371
+ :on_complete => method(:clean_temporary_file),
372
+ :upload_options => upload_options )
373
+ end
374
+ end
375
+
376
+ def rotation_strategy
377
+ case @rotation_strategy
378
+ when "size"
379
+ SizeRotationPolicy.new(size_file)
380
+ when "time"
381
+ TimeRotationPolicy.new(time_file)
382
+ when "size_and_time"
383
+ SizeAndTimeRotationPolicy.new(size_file, time_file)
384
+ end
385
+ end
386
+
387
+ def clean_temporary_file(file)
388
+ @logger.debug? && @logger.debug("Removing temporary file", :path => file.path)
389
+ file.delete!
390
+ end
391
+
392
+ # The upload process will use a separate uploader/threadpool with less resource allocated to it.
393
+ # but it will use an unbounded queue for the work, it may take some time before all the older files get processed.
394
+ def restore_from_crash
395
+ @crash_uploader = Uploader.new(bucket_resource, @logger, CRASH_RECOVERY_THREADPOOL)
396
+
397
+ temp_folder_path = Pathname.new(@temporary_directory)
398
+ files = Dir.glob(::File.join(@temporary_directory, "**/*"))
399
+ .select { |file_path| ::File.file?(file_path) }
400
+ under_recovery_files = get_under_recovery_files(files)
401
+
402
+ files.each do |file_path|
403
+ # when encoding is GZIP, if file is already recovering or recovered and uploading to S3, log and skip
404
+ if under_recovery_files.include?(file_path)
405
+ unless file_path.include?(TemporaryFile.gzip_extension)
406
+ @logger.warn("The #{file_path} file either under recover process or failed to recover before.")
407
+ end
408
+ else
409
+ temp_file = TemporaryFile.create_from_existing_file(file_path, temp_folder_path)
410
+ # do not remove or upload if Logstash tries to recover file but fails
411
+ if temp_file.recoverable?
412
+ if temp_file.size > 0
413
+ @logger.debug? && @logger.debug("Recovering from crash and uploading", :path => temp_file.path)
414
+ @crash_uploader.upload_async(temp_file,
415
+ :on_complete => method(:clean_temporary_file),
416
+ :upload_options => upload_options)
417
+ else
418
+ clean_temporary_file(temp_file)
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ # figures out the recovering files and
426
+ # creates a skip list to ignore for the rest of processes
427
+ def get_under_recovery_files(files)
428
+ skip_files = Set.new
429
+ return skip_files unless @encoding == GZIP_ENCODING
430
+
431
+ files.each do |file_path|
432
+ if file_path.include?(TemporaryFile.recovery_file_name_tag)
433
+ skip_files << file_path
434
+ if file_path.include?(TemporaryFile.gzip_extension)
435
+ # also include the original corrupted gzip file
436
+ skip_files << file_path.gsub(TemporaryFile.recovery_file_name_tag, "")
437
+ end
438
+ end
439
+ end
440
+ skip_files
441
+ end
442
+ 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