logstash-integration-aws 7.1.1-java

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