logstash-integration-aws 7.1.1-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.PRE.MERGE.md +658 -0
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTORS +40 -0
- data/Gemfile +11 -0
- data/LICENSE +202 -0
- data/NOTICE.TXT +5 -0
- data/README.md +205 -0
- data/VERSION +1 -0
- data/docs/codec-cloudfront.asciidoc +53 -0
- data/docs/codec-cloudtrail.asciidoc +45 -0
- data/docs/index.asciidoc +36 -0
- data/docs/input-cloudwatch.asciidoc +320 -0
- data/docs/input-s3.asciidoc +346 -0
- data/docs/input-sqs.asciidoc +287 -0
- data/docs/output-cloudwatch.asciidoc +321 -0
- data/docs/output-s3.asciidoc +442 -0
- data/docs/output-sns.asciidoc +166 -0
- data/docs/output-sqs.asciidoc +242 -0
- data/lib/logstash/codecs/cloudfront.rb +84 -0
- data/lib/logstash/codecs/cloudtrail.rb +47 -0
- data/lib/logstash/inputs/cloudwatch.rb +338 -0
- data/lib/logstash/inputs/s3.rb +466 -0
- data/lib/logstash/inputs/sqs.rb +196 -0
- data/lib/logstash/outputs/cloudwatch.rb +346 -0
- data/lib/logstash/outputs/s3/file_repository.rb +193 -0
- data/lib/logstash/outputs/s3/path_validator.rb +18 -0
- data/lib/logstash/outputs/s3/size_and_time_rotation_policy.rb +24 -0
- data/lib/logstash/outputs/s3/size_rotation_policy.rb +26 -0
- data/lib/logstash/outputs/s3/temporary_file.rb +114 -0
- data/lib/logstash/outputs/s3/temporary_file_factory.rb +126 -0
- data/lib/logstash/outputs/s3/time_rotation_policy.rb +26 -0
- data/lib/logstash/outputs/s3/uploader.rb +76 -0
- data/lib/logstash/outputs/s3/writable_directory_validator.rb +17 -0
- data/lib/logstash/outputs/s3/write_bucket_permission_validator.rb +60 -0
- data/lib/logstash/outputs/s3.rb +442 -0
- data/lib/logstash/outputs/sns.rb +133 -0
- data/lib/logstash/outputs/sqs.rb +167 -0
- data/lib/logstash/plugin_mixins/aws_config/generic.rb +54 -0
- data/lib/logstash/plugin_mixins/aws_config/v2.rb +93 -0
- data/lib/logstash/plugin_mixins/aws_config.rb +8 -0
- data/lib/logstash-integration-aws_jars.rb +4 -0
- data/lib/tasks/build.rake +15 -0
- data/logstash-integration-aws.gemspec +55 -0
- data/spec/codecs/cloudfront_spec.rb +92 -0
- data/spec/codecs/cloudtrail_spec.rb +56 -0
- data/spec/fixtures/aws_credentials_file_sample_test.yml +2 -0
- data/spec/fixtures/aws_temporary_credentials_file_sample_test.yml +3 -0
- data/spec/fixtures/cloudfront.log +4 -0
- data/spec/fixtures/compressed.log.gee.zip +0 -0
- data/spec/fixtures/compressed.log.gz +0 -0
- data/spec/fixtures/compressed.log.gzip +0 -0
- data/spec/fixtures/invalid_utf8.gbk.log +2 -0
- data/spec/fixtures/json.log +2 -0
- data/spec/fixtures/json_with_message.log +2 -0
- data/spec/fixtures/multiline.log +6 -0
- data/spec/fixtures/multiple_compressed_streams.gz +0 -0
- data/spec/fixtures/uncompressed.log +2 -0
- data/spec/inputs/cloudwatch_spec.rb +85 -0
- data/spec/inputs/s3_spec.rb +610 -0
- data/spec/inputs/sincedb_spec.rb +17 -0
- data/spec/inputs/sqs_spec.rb +324 -0
- data/spec/integration/cloudwatch_spec.rb +25 -0
- data/spec/integration/dynamic_prefix_spec.rb +92 -0
- data/spec/integration/gzip_file_spec.rb +62 -0
- data/spec/integration/gzip_size_rotation_spec.rb +63 -0
- data/spec/integration/outputs/sqs_spec.rb +98 -0
- data/spec/integration/restore_from_crash_spec.rb +133 -0
- data/spec/integration/s3_spec.rb +66 -0
- data/spec/integration/size_rotation_spec.rb +59 -0
- data/spec/integration/sqs_spec.rb +110 -0
- data/spec/integration/stress_test_spec.rb +60 -0
- data/spec/integration/time_based_rotation_with_constant_write_spec.rb +60 -0
- data/spec/integration/time_based_rotation_with_stale_write_spec.rb +64 -0
- data/spec/integration/upload_current_file_on_shutdown_spec.rb +51 -0
- data/spec/outputs/cloudwatch_spec.rb +38 -0
- data/spec/outputs/s3/file_repository_spec.rb +143 -0
- data/spec/outputs/s3/size_and_time_rotation_policy_spec.rb +77 -0
- data/spec/outputs/s3/size_rotation_policy_spec.rb +41 -0
- data/spec/outputs/s3/temporary_file_factory_spec.rb +89 -0
- data/spec/outputs/s3/temporary_file_spec.rb +47 -0
- data/spec/outputs/s3/time_rotation_policy_spec.rb +60 -0
- data/spec/outputs/s3/uploader_spec.rb +69 -0
- data/spec/outputs/s3/writable_directory_validator_spec.rb +40 -0
- data/spec/outputs/s3/write_bucket_permission_validator_spec.rb +49 -0
- data/spec/outputs/s3_spec.rb +232 -0
- data/spec/outputs/sns_spec.rb +160 -0
- data/spec/plugin_mixin/aws_config_spec.rb +217 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/helpers.rb +121 -0
- data/spec/unit/outputs/sqs_spec.rb +247 -0
- data/vendor/jar-dependencies/org/logstash/plugins/integration/aws/logstash-integration-aws/7.1.1/logstash-integration-aws-7.1.1.jar +0 -0
- 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
|