logstash-output-cloudwatchlogs-latest 2.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1c17daac13139a11c64056b900ea32a651b316d2
4
+ data.tar.gz: f966ae472a647dd6478e64e361fb5c53bdab9fe3
5
+ SHA512:
6
+ metadata.gz: e47e50cc8e56739b6084302f77aff434ed4f962d5b18536fb7439ba89564cd93fec91505931027d47dc9e168172bbbd5926928f974ff475d6ce16fbc2ec49ced
7
+ data.tar.gz: 5f838104ff14c72a02a927ae918195bf1c2f8d149c450518c3a13808a2825d885ea4014847dcdbab046a97fbf939741b910916d79cf9988f710d2c6531a9b354
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Amazon Software License
2
+ 1. Definitions
3
+ "Licensor" means any person or entity that distributes its Work.
4
+
5
+ "Software" means the original work of authorship made available under this License.
6
+
7
+ "Work" means the Software and any additions to or derivative works of the Software that are made available under this License.
8
+
9
+ The terms "reproduce," "reproduction," "derivative works," and "distribution" have the meaning as provided under U.S. copyright law; provided, however, that for the purposes of this License, derivative works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work.
10
+
11
+ Works, including the Software, are "made available" under this License by including in or with the Work either (a) a copyright notice referencing the applicability of this License to the Work, or (b) a copy of this License.
12
+ 2. License Grants
13
+ 2.1 Copyright Grant. Subject to the terms and conditions of this License, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free, copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense and distribute its Work and any resulting derivative works in any form.
14
+ 2.2 Patent Grant. Subject to the terms and conditions of this License, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free patent license to make, have made, use, sell, offer for sale, import, and otherwise transfer its Work, in whole or in part. The foregoing license applies only to the patent claims licensable by Licensor that would be infringed by Licensor’s Work (or portion thereof) individually and excluding any combinations with any other materials or technology.
15
+ 3. Limitations
16
+ 3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do so under this License, (b) you include a complete copy of this License with your distribution, and (c) you retain without modification any copyright, patent, trademark, or attribution notices that are present in the Work.
17
+ 3.2 Derivative Works. You may specify that additional or different terms apply to the use, reproduction, and distribution of your derivative works of the Work ("Your Terms") only if (a) Your Terms provide that the use limitation in Section 3.3 applies to your derivative works, and (b) you identify the specific derivative works that are subject to Your Terms. Notwithstanding Your Terms, this License (including the redistribution requirements in Section 3.1) will continue to apply to the Work itself.
18
+ 3.3 Use Limitation. The Work and any derivative works thereof only may be used or intended for use with the web services, computing platforms or applications provided by Amazon.com, Inc. or its affiliates, including Amazon Web Services, Inc.
19
+ 3.4 Patent Claims. If you bring or threaten to bring a patent claim against any Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to enforce any patents that you allege are infringed by any Work, then your rights under this License from such Licensor (including the grants in Sections 2.1 and 2.2) will terminate immediately.
20
+ 3.5 Trademarks. This License does not grant any rights to use any Licensor’s or its affiliates’ names, logos, or trademarks, except as necessary to reproduce the notices described in this License.
21
+ 3.6 Termination. If you violate any term of this License, then your rights under this License (including the grants in Sections 2.1 and 2.2) will terminate immediately.
22
+ 4. Disclaimer of Warranty.
23
+ THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF M ERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE. SOME STATES’ CONSUMER LAWS DO NOT ALLOW EXCLUSION OF AN IMPLIED WARRANTY, SO THIS DISCLAIMER MAY NOT APPLY TO YOU.
24
+ 5. Limitation of Liability.
25
+ EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER COMM ERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
26
+ Effective Date – April 18, 2008 © 2008 Amazon.com, Inc. or its affiliates. All rights reserved.
@@ -0,0 +1,2 @@
1
+ logstash-output-cloudwatchlogs
2
+ Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
@@ -0,0 +1,150 @@
1
+ # logstash-output-cloudwatchlogs
2
+ A logstash plugin that allows to send logs to AWS CloudWatch Logs service.
3
+
4
+ ## Developing
5
+
6
+ ### 1. Plugin Developement and Testing
7
+
8
+ #### Code
9
+ - To get started, you'll need JRuby with the Bundler gem installed.
10
+
11
+ - Clone the repository.
12
+
13
+ - Install dependencies
14
+ ```sh
15
+ bundle install
16
+ ```
17
+
18
+ #### Test
19
+
20
+ - Update your dependencies
21
+
22
+ ```sh
23
+ bundle install
24
+ ```
25
+
26
+ - Run tests
27
+
28
+ ```sh
29
+ bundle exec rspec
30
+ ```
31
+
32
+ ### 2. Running your unpublished Plugin in Logstash
33
+
34
+ #### 2.1 Run in a local Logstash clone
35
+
36
+ - Edit Logstash `Gemfile` and add the local plugin path, for example:
37
+ ```ruby
38
+ gem "logstash-output-cloudwatchlogs", :path => "/your/local/logstash-output-cloudwatchlogs"
39
+ ```
40
+ - Install plugin
41
+ ```sh
42
+ bin/plugin install --no-verify
43
+ ```
44
+ - Run Logstash with your plugin
45
+
46
+ At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
47
+
48
+ #### 2.2 Run in an installed Logstash
49
+
50
+ You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
51
+
52
+ - Build your plugin gem
53
+ ```sh
54
+ gem build logstash-output-cloudwatchlogs.gemspec
55
+ ```
56
+ - Install the plugin from the Logstash home
57
+ ```sh
58
+ bin/plugin install /your/local/plugin/logstash-output-cloudwatchlogs.gem
59
+ ```
60
+ - Start Logstash and proceed to test the plugin
61
+
62
+ ## Usage
63
+
64
+ Below sample configuration reads 2 log4j logs and sends them to 2 log streams respectively.
65
+
66
+ ```
67
+ input {
68
+ file {
69
+ path => "/path/to/app1.log"
70
+ start_position => beginning
71
+ tags => ["app1"]
72
+ }
73
+ file {
74
+ path => "/path/to/app2.log"
75
+ start_position => beginning
76
+ tags => ["app2"]
77
+ }
78
+ }
79
+
80
+ filter {
81
+ multiline {
82
+ pattern => "^%{MONTHDAY} %{MONTH} %{YEAR} %{TIME}"
83
+ negate => true
84
+ what => "previous"
85
+ }
86
+ grok {
87
+ match => { "message" => "(?<timestamp>%{MONTHDAY} %{MONTH} %{YEAR} %{TIME})" }
88
+ }
89
+ date {
90
+ match => [ "timestamp", "dd MMM yyyy HH:mm:ss,SSS" ]
91
+ target => "@timestamp"
92
+ }
93
+ }
94
+
95
+ output {
96
+ if "app1" in [tags] {
97
+ cloudwatchlogs {
98
+ "log_group_name" => "app1"
99
+ "log_stream_name" => "host1"
100
+ }
101
+ }
102
+ if "app2" in [tags] {
103
+ cloudwatchlogs {
104
+ "log_group_name" => "app2"
105
+ "log_stream_name" => "host1"
106
+ }
107
+ }
108
+ }
109
+
110
+ ```
111
+
112
+ Here are all the supported options:
113
+
114
+ * region: string, the AWS region, defaults to us-east-1.
115
+ * access_key_id: string, specifies the access key.
116
+ * secret_access_key: string, specifies the secret access key.
117
+ * aws_credentials_file: string, points to a file which specifies access_key_id and secret_access_key.
118
+ * log_group_name: string, required, specifies the destination log group. A log group will be created automatically if it doesn't already exist.
119
+ * log_stream_name: string, required, specifies the destination log stream. A log stream will be created automatically if it doesn't already exist.
120
+ * batch_count: number, the max number of log events in a batch, up to 10000.
121
+ * batch_size: number, the max size of log events in a batch, in bytes, up to 1048576.
122
+ * buffer_duration: number, the amount of time to batch log events, in milliseconds, from 5000 milliseconds.
123
+ * queue_size: number, the max number of batches to buffer, defaults to 5.
124
+ * dry_run: boolean, prints out the log events to stdout instead of sending them to CloudWatch Logs service.
125
+
126
+
127
+ In addition to configuring the AWS credential in the configuration file, credentials can also be loaded automatically from the following locations:
128
+
129
+ * ENV['AWS_ACCESS_KEY_ID'] and ENV['AWS_SECRET_ACCESS_KEY']
130
+ * The shared credentials ini file at ~/.aws/credentials (more information)
131
+ * From an instance profile when running on EC2
132
+
133
+ ```
134
+ cloudwatchlogs {
135
+ "log_group_name" => "lg2"
136
+ "log_stream_name" => "ls1"
137
+ "batch_count" => 1000
138
+ "batch_size" => 1048576
139
+ "buffer_duration" => 5000
140
+ "queue_size" => 10
141
+ "dry_run" => false
142
+ }
143
+ ```
144
+ ## Contributing
145
+
146
+ 1. Fork it
147
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
148
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
149
+ 4. Push to the branch (`git push origin my-new-feature`)
150
+ 5. Create new Pull Request
@@ -0,0 +1,7 @@
1
+ @files=[]
2
+
3
+ task :default do
4
+ system("rake -T")
5
+ end
6
+
7
+ require "logstash/devutils/rake"
@@ -0,0 +1,413 @@
1
+ # encoding: utf-8
2
+
3
+ #
4
+ # Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
+ #
6
+ # Licensed under the Amazon Software License (the "License").
7
+ # You may not use this file except in compliance with the License.
8
+ # A copy of the License is located at
9
+ #
10
+ # http://aws.amazon.com/asl/
11
+ #
12
+ # or in the "license" file accompanying this file. This file is distributed
13
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14
+ # express or implied. See the License for the specific language governing
15
+ # permissions and limitations under the License.
16
+
17
+ require "logstash/outputs/base"
18
+ require "logstash/namespace"
19
+ require "logstash/plugin_mixins/aws_config"
20
+
21
+ require "time"
22
+
23
+ # This output lets you send log data to AWS CloudWatch Logs service
24
+ #
25
+ class LogStash::Outputs::CloudWatchLogs < LogStash::Outputs::Base
26
+
27
+ include LogStash::PluginMixins::AwsConfig::V2
28
+
29
+ config_name "cloudwatchlogs"
30
+
31
+ # Constants
32
+ LOG_GROUP_NAME = "log_group_name"
33
+ LOG_STREAM_NAME = "log_stream_name"
34
+ SEQUENCE_TOKEN = "sequence_token"
35
+ TIMESTAMP = "@timestamp"
36
+ MESSAGE = "message"
37
+
38
+ PER_EVENT_OVERHEAD = 26
39
+ MAX_BATCH_SIZE = 1024 * 1024
40
+ MAX_BATCH_COUNT = 10000
41
+ MAX_DISTANCE_BETWEEN_EVENTS = 86400 * 1000
42
+ MIN_DELAY = 0.2
43
+ MIN_BUFFER_DURATION = 5000
44
+ # Backoff up to 64 seconds upon failure
45
+ MAX_BACKOFF_IN_SECOND = 64
46
+
47
+ # The destination log group
48
+ config :log_group_name, :validate => :string, :required => true
49
+
50
+ # The destination log stream
51
+ config :log_stream_name, :validate => :string, :required => true
52
+
53
+ # The max number of log events in a batch.
54
+ config :batch_count, :validate => :number, :default => MAX_BATCH_COUNT
55
+
56
+ # The max size of log events in a batch.
57
+ config :batch_size, :validate => :number, :default => MAX_BATCH_SIZE
58
+
59
+ # The amount of time to batch log events, in milliseconds.
60
+ config :buffer_duration, :validate => :number, :default => 5000
61
+
62
+ # Set to true to encode the events using a codec.
63
+ # The default behaviour is to just send the "message" field of the event
64
+ config :use_codec, :validate => :boolean, :default => false
65
+
66
+ # The max number of batches to buffer.
67
+ # Log event is added to batch first. When batch is full or exceeds specified
68
+ # buffer_duration milliseconds, batch is added to queue which is consumed by
69
+ # a different thread. When both batch and queue are full, the add operation is
70
+ # blocked.
71
+ config :queue_size, :validate => :number, :default => 5
72
+
73
+ # Print out the log events to stdout
74
+ config :dry_run, :validate => :boolean, :default => false
75
+
76
+ attr_accessor :sequence_token, :last_flush, :cwl
77
+
78
+ # Only accessed by tests
79
+ attr_reader :buffer
80
+
81
+ public
82
+ def register
83
+ require "aws-sdk"
84
+ @cwl = Aws::CloudWatchLogs::Client.new(aws_options_hash)
85
+
86
+ if @batch_count > MAX_BATCH_COUNT
87
+ @logger.warn(":batch_count exceeds the max number of log events. Use #{MAX_BATCH_COUNT} instead.")
88
+ @batch_count = MAX_BATCH_COUNT
89
+ end
90
+ if @batch_size > MAX_BATCH_SIZE
91
+ @logger.warn(":batch_size exceeds the max size of log events. Use #{MAX_BATCH_SIZE} instead.")
92
+ @batch_size = MAX_BATCH_SIZE
93
+ end
94
+ if @buffer_duration < MIN_BUFFER_DURATION
95
+ @logger.warn(":buffer_duration is smaller than the min value. Use #{MIN_BUFFER_DURATION} instead.")
96
+ @buffer_duration = MIN_BUFFER_DURATION
97
+ end
98
+ @sequence_token = nil
99
+ @last_flush = Time.now.to_f
100
+ @buffer = Buffer.new(
101
+ max_batch_count: batch_count, max_batch_size: batch_size,
102
+ buffer_duration: @buffer_duration, out_queue_size: @queue_size, logger: @logger,
103
+ size_of_item_proc: Proc.new {|event| event[:message].bytesize + PER_EVENT_OVERHEAD})
104
+ @publisher = Thread.new do
105
+ @buffer.deq do |batch|
106
+ flush(batch)
107
+ end
108
+ end
109
+
110
+ if @log_stream_name.include? "%instance_id%"
111
+ require "net/http"
112
+ @log_stream_name.gsub!("%instance_id%", Net::HTTP.get(URI.parse("http://169.254.169.254/latest/meta-data/instance-id")))
113
+ end
114
+
115
+ if @log_stream_name.include? "%hostname%"
116
+ require "socket"
117
+ @log_stream_name.gsub!("%hostname%", Socket.gethostname)
118
+ end
119
+
120
+ if @log_stream_name.include? "%ipv4%"
121
+ require "socket"
122
+ @log_stream_name.gsub!("%ipv4%", Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address)
123
+ end
124
+
125
+ if @use_codec
126
+ @codec.on_event() {|event, payload| @buffer.enq({:timestamp => event.timestamp.time.to_f*1000,
127
+ :message => payload})}
128
+ end
129
+ end # def register
130
+
131
+ public
132
+ def receive(event)
133
+ return unless output?(event)
134
+
135
+ if event == LogStash::SHUTDOWN
136
+ @buffer.close
137
+ @publisher.join
138
+ @logger.info("CloudWatch Logs output plugin shutdown.")
139
+ finished
140
+ return
141
+ end
142
+ return if invalid?(event)
143
+
144
+ if @use_codec
145
+ @codec.encode(event)
146
+ else
147
+ @buffer.enq({:timestamp => event.timestamp.time.to_f*1000,
148
+ :message => event[MESSAGE] })
149
+ end
150
+ end # def receive
151
+
152
+ public
153
+ def close
154
+ @logger.info("Going to clean up resources")
155
+ @buffer.close
156
+ @publisher.join
157
+ @cwl = nil
158
+ end # def close
159
+
160
+ public
161
+ def flush(events)
162
+ return if events.nil? or events.empty?
163
+ log_event_batches = prepare_log_events(events)
164
+ log_event_batches.each do |log_events|
165
+ put_log_events(log_events)
166
+ end
167
+ end
168
+
169
+ private
170
+ def put_log_events(log_events)
171
+ return if log_events.nil? or log_events.empty?
172
+ # Shouldn't send two requests within MIN_DELAY
173
+ delay = MIN_DELAY - (Time.now.to_f - @last_flush)
174
+ sleep(delay) if delay > 0
175
+ backoff = 1
176
+ begin
177
+ @logger.info("Sending #{log_events.size} events to #{@log_group_name}/#{@log_stream_name}")
178
+ @last_flush = Time.now.to_f
179
+ if @dry_run
180
+ log_events.each do |event|
181
+ puts event[:message]
182
+ end
183
+ return
184
+ end
185
+ response = @cwl.put_log_events(
186
+ :log_group_name => @log_group_name,
187
+ :log_stream_name => @log_stream_name,
188
+ :log_events => log_events,
189
+ :sequence_token => @sequence_token
190
+ )
191
+ @sequence_token = response.next_sequence_token
192
+ rescue Aws::CloudWatchLogs::Errors::InvalidSequenceTokenException => e
193
+ @logger.warn(e)
194
+ if /sequenceToken(?:\sis)?: ([^\s]+)/ =~ e.to_s
195
+ if $1 == 'null'
196
+ @sequence_token = nil
197
+ else
198
+ @sequence_token = $1
199
+ end
200
+ @logger.info("Will retry with new sequence token #{@sequence_token}")
201
+ retry
202
+ else
203
+ @logger.error("Cannot find sequence token from response")
204
+ end
205
+ rescue Aws::CloudWatchLogs::Errors::DataAlreadyAcceptedException => e
206
+ @logger.warn(e)
207
+ if /sequenceToken(?:\sis)?: ([^\s]+)/ =~ e.to_s
208
+ if $1 == 'null'
209
+ @sequence_token = nil
210
+ else
211
+ @sequence_token = $1
212
+ end
213
+ @logger.info("Data already accepted and no need to resend")
214
+ else
215
+ @logger.error("Cannot find sequence token from response")
216
+ end
217
+ rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException => e
218
+ @logger.info("Will create log group/stream and retry")
219
+ begin
220
+ @cwl.create_log_group(:log_group_name => @log_group_name)
221
+ rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException => e
222
+ @logger.info("Log group #{@log_group_name} already exists")
223
+ rescue Exception => e
224
+ @logger.error(e)
225
+ end
226
+ begin
227
+ @cwl.create_log_stream(:log_group_name => @log_group_name, :log_stream_name => @log_stream_name)
228
+ rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException => e
229
+ @logger.info("Log stream #{@log_stream_name} already exists")
230
+ rescue Exception => e
231
+ @logger.error(e)
232
+ end
233
+ retry
234
+ rescue Aws::CloudWatchLogs::Errors::InvalidParameterException => e
235
+ # swallow exception
236
+ @logger.error("Skip batch due to #{e}")
237
+ rescue Exception => e
238
+ if backoff * 2 <= MAX_BACKOFF_IN_SECOND
239
+ backoff = backoff * 2
240
+ end
241
+ @logger.error("Will retry for #{e} after #{backoff} seconds")
242
+ sleep backoff
243
+ retry
244
+ end
245
+ end# def flush
246
+
247
+ private
248
+ def invalid?(event)
249
+ status = event[TIMESTAMP].nil? || (!@use_codec && event[MESSAGE].nil?)
250
+ if status
251
+ @logger.warn("Skipping invalid event #{event.to_hash}")
252
+ end
253
+ return status
254
+ end
255
+
256
+ private
257
+ def prepare_log_events(events)
258
+ log_events = events.sort {|e1,e2| e1[:timestamp] <=> e2[:timestamp]}
259
+ batches = []
260
+ if log_events[-1][:timestamp] - log_events[0][:timestamp] > MAX_DISTANCE_BETWEEN_EVENTS
261
+ temp_batch = []
262
+ log_events.each do |log_event|
263
+ if temp_batch.empty? || log_event[:timestamp] - temp_batch[0][:timestamp] <= MAX_DISTANCE_BETWEEN_EVENTS
264
+ temp_batch << log_event
265
+ else
266
+ batches << temp_batch
267
+ temp_batch = []
268
+ temp_batch << log_event
269
+ end
270
+ end
271
+ if not temp_batch.empty?
272
+ batches << temp_batch
273
+ end
274
+ else
275
+ batches << log_events
276
+ end
277
+ batches
278
+ end
279
+
280
+ ##
281
+ # This class buffers series of single item to batches and puts batches to
282
+ # a SizedQueue for consumption.
283
+ # A buffer includes an ongoing batch and an out queue. An item is added
284
+ # to the ongoing batch first, when the ongoing batch becomes to ready for
285
+ # consumption and then is added to out queue/emptified.
286
+ # An ongoing batch becomes to comsumption ready if the number of items is going to
287
+ # exceed *max_batch_count*, or the size of items is going to exceed
288
+ # *max_batch_size*, with the addition of one more item,
289
+ # or the batch has opend more than *buffer_duration* milliseconds and has at least one item.
290
+
291
+ class Buffer
292
+
293
+ CLOSE_BATCH = :close
294
+
295
+ attr_reader :in_batch, :in_count, :in_size, :out_queue
296
+
297
+ # Creates a new buffer
298
+ def initialize(options = {})
299
+ @max_batch_count = options.fetch(:max_batch_count)
300
+ @max_batch_size = options.fetch(:max_batch_size)
301
+ @buffer_duration = options.fetch(:buffer_duration)
302
+ @out_queue_size = options.fetch(:out_queue_size, 10)
303
+ @logger = options.fetch(:logger, nil)
304
+ @size_of_item_proc = options.fetch(:size_of_item_proc)
305
+ @in_batch = Array.new
306
+ @in_count = 0
307
+ @in_size = 0
308
+ @out_queue = SizedQueue.new(@out_queue_size)
309
+ @batch_update_mutex = Mutex.new
310
+ @last_batch_time = Time.now
311
+ if @buffer_duration > 0
312
+ @scheduled_batcher = Thread.new do
313
+ loop do
314
+ sleep(@buffer_duration / 1000.0)
315
+ enq(:scheduled)
316
+ end
317
+ end
318
+ end
319
+ end
320
+
321
+ # Enques an item to buffer
322
+ #
323
+ # * If ongoing batch is not full with this addition, adds item to batch.
324
+ # * If ongoing batch is full with this addition, adds item to batch and add batch to out queue.
325
+ # * If ongoing batch is going to overflow with this addition, adds batch to out queue,
326
+ # and then adds the item to the new batch
327
+ def enq(item)
328
+ @batch_update_mutex.synchronize do
329
+ if item == :scheduled || item == :close
330
+ add_current_batch_to_out_queue(item)
331
+ return
332
+ end
333
+ status = try_add_item(item)
334
+ if status != 0
335
+ add_current_batch_to_out_queue(:add)
336
+ if status == -1
337
+ try_add_item(item)
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ # Closes the buffer
344
+ #
345
+ # Adds current batch to the queue and adds CLOSE_BATCH to queue.
346
+ # Waits until consumer completes.
347
+ def close
348
+ while @in_size != 0 do
349
+ enq(:close)
350
+ sleep(1)
351
+ end
352
+ @out_queue.enq(CLOSE_BATCH)
353
+ end
354
+
355
+ # Deques ready for consumption batches
356
+ #
357
+ # The caller blocks on this call until the buffer is closed.
358
+ def deq(&proc)
359
+ loop do
360
+ batch = @out_queue.deq
361
+ if batch == CLOSE_BATCH
362
+ break
363
+ end
364
+ proc.call(batch)
365
+ end
366
+ end
367
+
368
+ private
369
+ # Tries to add an item to buffer
370
+ def try_add_item(item)
371
+ item_size = @size_of_item_proc.call(item)
372
+ if @in_count + 1 == @max_batch_count ||
373
+ @in_size + item_size == @max_batch_size
374
+ # accept item, but can't accept more items
375
+ add_item(item)
376
+ return 1
377
+ elsif @in_size + item_size > @max_batch_size
378
+ # cannot accept item
379
+ return -1
380
+ else
381
+ add_item(item)
382
+ # accept item, and may accept next item
383
+ return 0
384
+ end
385
+ end
386
+
387
+ # Adds item to batch
388
+ def add_item(item)
389
+ @in_batch << item
390
+ @in_count += 1
391
+ @in_size += @size_of_item_proc.call(item)
392
+ end
393
+
394
+ # Adds batch to out queue
395
+ def add_current_batch_to_out_queue(from)
396
+ if from == :scheduled && (Time.now - @last_batch_time) * 1000 < @buffer_duration
397
+ return
398
+ end
399
+ if @in_batch.size == 0
400
+ @last_batch_time = Time.now
401
+ return
402
+ end
403
+ @logger.debug("Added batch with #{in_count} items in #{in_size} by #{from}") if @logger
404
+ @out_queue.enq(@in_batch)
405
+ @in_batch = Array.new
406
+ @in_count = 0
407
+ @in_size = 0
408
+ @last_batch_time = Time.now
409
+ end
410
+
411
+ end
412
+
413
+ end # class LogStash::Outputs::CloudWatchLogs