logstash-input-s3-sns-sqs 1.2.0 → 1.4.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile +9 -0
- data/lib/logstash/inputs/s3snssqs.rb +263 -101
- data/logstash-input-s3-sns-sqs.gemspec +4 -3
- metadata +6 -6
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: fa00cb9383c9782647f404283f10281febb31622
         | 
| 4 | 
            +
              data.tar.gz: a8fe32f0d3668c893ff5ef5cd253e02dee8f4fd5
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: a602a1a99073817666f83f0f0194e953c3ee6a75efd5e4e2faf4a44a1d19323133ea0c96e6fab7d306ec75f2f6384c936f2f1a134b6639e4ffe645c76af727f4
         | 
| 7 | 
            +
              data.tar.gz: 8652e9ecc5c3b9342dabb842c8258adcf52f28d5184dae8f3e1bd27ffed0bac0333a79f2055be4a55bbb2e0849db8ac5426d6936fafe1c96ac25a1701038d58f
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,7 @@ | |
| 1 | 
            +
            ## 1.4.0
         | 
| 2 | 
            +
            - Filehandling rewritten THX to logstash-input-s3 for inspiration
         | 
| 3 | 
            +
            - Improve performance of gzip decoding by 10x by using Java's Zlib
         | 
| 4 | 
            +
            - Added multithreading via config Use: consumer_threads in config
         | 
| 1 5 | 
             
            ## 1.2.0
         | 
| 2 6 | 
             
            - Add codec suggestion by content-type
         | 
| 3 7 | 
             
            - enrich metadata 
         | 
    
        data/Gemfile
    CHANGED
    
    | @@ -1,2 +1,11 @@ | |
| 1 1 | 
             
            source 'https://rubygems.org'
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            gemspec
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            logstash_path = ENV["LOGSTASH_PATH"] || "../../logstash"
         | 
| 6 | 
            +
            use_logstash_source = ENV["LOGSTASH_SOURCE"] && ENV["LOGSTASH_SOURCE"].to_s == "1"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            if Dir.exist?(logstash_path) && use_logstash_source
         | 
| 9 | 
            +
              gem 'logstash-core', :path => "#{logstash_path}/logstash-core"
         | 
| 10 | 
            +
              gem 'logstash-core-plugin-api', :path => "#{logstash_path}/logstash-core-plugin-api"
         | 
| 11 | 
            +
            end
         | 
| @@ -6,6 +6,16 @@ require "logstash/timestamp" | |
| 6 6 | 
             
            require "logstash/plugin_mixins/aws_config"
         | 
| 7 7 | 
             
            require "logstash/errors"
         | 
| 8 8 | 
             
            require 'logstash/inputs/s3sqs/patch'
         | 
| 9 | 
            +
            require "aws-sdk"
         | 
| 10 | 
            +
            require 'cgi'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            require 'java'
         | 
| 13 | 
            +
            java_import java.io.InputStream
         | 
| 14 | 
            +
            java_import java.io.InputStreamReader
         | 
| 15 | 
            +
            java_import java.io.FileInputStream
         | 
| 16 | 
            +
            java_import java.io.BufferedReader
         | 
| 17 | 
            +
            java_import java.util.zip.GZIPInputStream
         | 
| 18 | 
            +
            java_import java.util.zip.ZipException
         | 
| 9 19 |  | 
| 10 20 | 
             
            Aws.eager_autoload!
         | 
| 11 21 |  | 
| @@ -96,6 +106,10 @@ class LogStash::Inputs::S3SNSSQS < LogStash::Inputs::Threadable | |
| 96 106 | 
             
              config :delete_on_success, :validate => :boolean, :default => false
         | 
| 97 107 | 
             
              # Whether the event is processed though an SNS to SQS. (S3>SNS>SQS = true |S3>SQS=false)
         | 
| 98 108 | 
             
              config :from_sns, :validate => :boolean, :default => true
         | 
| 109 | 
            +
              # To run in multiple threads use this
         | 
| 110 | 
            +
              config :consumer_threads, :validate => :number, :default => 1
         | 
| 111 | 
            +
              config :temporary_directory, :validate => :string, :default => File.join(Dir.tmpdir, "logstash")
         | 
| 112 | 
            +
             | 
| 99 113 |  | 
| 100 114 | 
             
              attr_reader :poller
         | 
| 101 115 | 
             
              attr_reader :s3
         | 
| @@ -106,26 +120,33 @@ class LogStash::Inputs::S3SNSSQS < LogStash::Inputs::Threadable | |
| 106 120 | 
             
                require "logstash/codecs/json"
         | 
| 107 121 | 
             
                require "logstash/codecs/json_lines"
         | 
| 108 122 | 
             
                if content_type == "application/json_lines" then
         | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 123 | 
            +
                  @logger.info("Automatically switching from #{@codec.class.config_name} to json_lines codec", :plugin => self.class.config_name)
         | 
| 124 | 
            +
                  @codec = LogStash::Codecs::JSONLines.new("charset" => @codec.charset)
         | 
| 111 125 | 
             
                elsif content_type == "application/json" or key.end_with?(".json") then
         | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 126 | 
            +
                  @logger.info("Automatically switching from #{@codec.class.config_name} to json codec", :plugin => self.class.config_name)
         | 
| 127 | 
            +
                  @codec = LogStash::Codecs::JSON.new("charset" => @codec.charset)
         | 
| 114 128 | 
             
                end
         | 
| 115 129 | 
             
              end
         | 
| 116 130 |  | 
| 131 | 
            +
              public
         | 
| 117 132 | 
             
              def register
         | 
| 118 | 
            -
                require " | 
| 119 | 
            -
                require  | 
| 133 | 
            +
                require "fileutils"
         | 
| 134 | 
            +
                require "digest/md5"
         | 
| 135 | 
            +
                require "aws-sdk-resources"
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                @runner_threads = []
         | 
| 120 138 | 
             
                @logger.info("Registering SQS input", :queue => @queue)
         | 
| 121 139 | 
             
                setup_queue
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                FileUtils.mkdir_p(@temporary_directory) unless Dir.exist?(@temporary_directory)
         | 
| 122 142 | 
             
              end
         | 
| 123 143 |  | 
| 124 144 | 
             
              def setup_queue
         | 
| 125 145 | 
             
                aws_sqs_client = Aws::SQS::Client.new(aws_options_hash)
         | 
| 126 146 | 
             
                queue_url = aws_sqs_client.get_queue_url({ queue_name: @queue, queue_owner_aws_account_id: @queue_owner_aws_account_id})[:queue_url]
         | 
| 127 147 | 
             
                @poller = Aws::SQS::QueuePoller.new(queue_url, :client => aws_sqs_client)
         | 
| 128 | 
            -
                @ | 
| 148 | 
            +
                @s3_client = Aws::S3::Client.new(aws_options_hash)
         | 
| 149 | 
            +
                @s3_resource = get_s3object
         | 
| 129 150 | 
             
              rescue Aws::SQS::Errors::ServiceError => e
         | 
| 130 151 | 
             
                @logger.error("Cannot establish connection to Amazon SQS", :error => e)
         | 
| 131 152 | 
             
                raise LogStash::ConfigurationError, "Verify the SQS queue name and your credentials"
         | 
| @@ -133,17 +154,17 @@ class LogStash::Inputs::S3SNSSQS < LogStash::Inputs::Threadable | |
| 133 154 |  | 
| 134 155 | 
             
              def polling_options
         | 
| 135 156 | 
             
                {
         | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 139 | 
            -
             | 
| 140 | 
            -
             | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 157 | 
            +
                    # we will query 1 message at a time, so we can ensure correct error handling if we can't download a single file correctly
         | 
| 158 | 
            +
                    # (we will throw :skip_delete if download size isn't correct to process the event again later
         | 
| 159 | 
            +
                    # -> set a reasonable "Default Visibility Timeout" for your queue, so that there's enough time to process the log files)
         | 
| 160 | 
            +
                    :max_number_of_messages => 1,
         | 
| 161 | 
            +
                    # we will use the queue's setting, a good value is 10 seconds
         | 
| 162 | 
            +
                    # (to ensure fast logstash shutdown on the one hand and few api calls on the other hand)
         | 
| 163 | 
            +
                    :wait_time_seconds => nil,
         | 
| 143 164 | 
             
                }
         | 
| 144 165 | 
             
              end
         | 
| 145 166 |  | 
| 146 | 
            -
              def handle_message(message, queue)
         | 
| 167 | 
            +
              def handle_message(message, queue, instance_codec)
         | 
| 147 168 | 
             
                hash = JSON.parse message.body
         | 
| 148 169 | 
             
                @logger.debug("handle_message", :hash => hash, :message => message)
         | 
| 149 170 | 
             
                #If send via sns there is an additional JSON layer
         | 
| @@ -152,97 +173,239 @@ class LogStash::Inputs::S3SNSSQS < LogStash::Inputs::Threadable | |
| 152 173 | 
             
                end
         | 
| 153 174 | 
             
                # there may be test events sent from the s3 bucket which won't contain a Records array,
         | 
| 154 175 | 
             
                # we will skip those events and remove them from queue
         | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 195 | 
            -
             | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 220 | 
            -
             | 
| 221 | 
            -
             | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
             | 
| 225 | 
            -
             | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 176 | 
            +
                if hash['Records'] then
         | 
| 177 | 
            +
                  # typically there will be only 1 record per event, but since it is an array we will
         | 
| 178 | 
            +
                  # treat it as if there could be more records
         | 
| 179 | 
            +
                  hash['Records'].each do |record|
         | 
| 180 | 
            +
                    @logger.debug("We found a record", :record => record)
         | 
| 181 | 
            +
                    # in case there are any events with Records that aren't s3 object-created events and can't therefore be
         | 
| 182 | 
            +
                    # processed by this plugin, we will skip them and remove them from queue
         | 
| 183 | 
            +
                    if record['eventSource'] == EVENT_SOURCE and record['eventName'].start_with?(EVENT_TYPE) then
         | 
| 184 | 
            +
                      @logger.debug("It is a valid record")
         | 
| 185 | 
            +
                      bucket = CGI.unescape(record['s3']['bucket']['name'])
         | 
| 186 | 
            +
                      key    = CGI.unescape(record['s3']['object']['key'])
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                      # try download and :skip_delete if it fails
         | 
| 189 | 
            +
                      #if record['s3']['object']['size'] < 10000000 then
         | 
| 190 | 
            +
                      process_log(bucket, key, instance_codec, queue)
         | 
| 191 | 
            +
                      #else
         | 
| 192 | 
            +
                      #  @logger.info("Your file is too big")
         | 
| 193 | 
            +
                      #end
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
                  end
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
              end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
              private
         | 
| 200 | 
            +
              def process_log(bucket , key, instance_codec, queue)
         | 
| 201 | 
            +
                s3bucket = @s3_resource.bucket(bucket)
         | 
| 202 | 
            +
                @logger.debug("Lets go reading file", :bucket => bucket, :key => key)
         | 
| 203 | 
            +
                object = s3bucket.object(key)
         | 
| 204 | 
            +
                filename = File.join(temporary_directory, File.basename(key))
         | 
| 205 | 
            +
                if download_remote_file(object, filename)
         | 
| 206 | 
            +
                  if process_local_log( filename, key, instance_codec, queue)
         | 
| 207 | 
            +
                    delete_file_from_bucket(object)
         | 
| 208 | 
            +
                    FileUtils.remove_entry_secure(filename, true)
         | 
| 209 | 
            +
                  end
         | 
| 210 | 
            +
                else
         | 
| 211 | 
            +
                  FileUtils.remove_entry_secure(filename, true)
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
              end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
              private
         | 
| 216 | 
            +
              # Stream the remove file to the local disk
         | 
| 217 | 
            +
              #
         | 
| 218 | 
            +
              # @param [S3Object] Reference to the remove S3 objec to download
         | 
| 219 | 
            +
              # @param [String] The Temporary filename to stream to.
         | 
| 220 | 
            +
              # @return [Boolean] True if the file was completely downloaded
         | 
| 221 | 
            +
              def download_remote_file(remote_object, local_filename)
         | 
| 222 | 
            +
                completed = false
         | 
| 223 | 
            +
                @logger.debug("S3 input: Download remote file", :remote_key => remote_object.key, :local_filename => local_filename)
         | 
| 224 | 
            +
                File.open(local_filename, 'wb') do |s3file|
         | 
| 225 | 
            +
                  return completed if stop?
         | 
| 226 | 
            +
                  remote_object.get(:response_target => s3file)
         | 
| 227 | 
            +
                end
         | 
| 228 | 
            +
                completed = true
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                return completed
         | 
| 231 | 
            +
              end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
              private
         | 
| 234 | 
            +
             | 
| 235 | 
            +
              # Read the content of the local file
         | 
| 236 | 
            +
              #
         | 
| 237 | 
            +
              # @param [Queue] Where to push the event
         | 
| 238 | 
            +
              # @param [String] Which file to read from
         | 
| 239 | 
            +
              # @return [Boolean] True if the file was completely read, false otherwise.
         | 
| 240 | 
            +
              def process_local_log(filename, key, instance_codec, queue)
         | 
| 241 | 
            +
                @logger.debug('Processing file', :filename => filename)
         | 
| 242 | 
            +
                metadata = {}
         | 
| 243 | 
            +
                i=1
         | 
| 244 | 
            +
                # Currently codecs operates on bytes instead of stream.
         | 
| 245 | 
            +
                # So all IO stuff: decompression, reading need to be done in the actual
         | 
| 246 | 
            +
                # input and send as bytes to the codecs.
         | 
| 247 | 
            +
                read_file(filename) do |line|
         | 
| 248 | 
            +
                  if stop?
         | 
| 249 | 
            +
                    @logger.warn("Logstash S3 input, stop reading in the middle of the file, we will read it again when logstash is started")
         | 
| 250 | 
            +
                    return false
         | 
| 251 | 
            +
                  end
         | 
| 252 | 
            +
                  #@logger.info("read line #{i}", :line => line)
         | 
| 253 | 
            +
                  #line = line.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: "\u2370")
         | 
| 254 | 
            +
                  instance_codec.decode(line) do |event|
         | 
| 255 | 
            +
                    #@logger.info("decorate event")
         | 
| 256 | 
            +
                    # We are making an assumption concerning cloudfront
         | 
| 257 | 
            +
                    # log format, the user will use the plain or the line codec
         | 
| 258 | 
            +
                    # and the message key will represent the actual line content.
         | 
| 259 | 
            +
                    # If the event is only metadata the event will be drop.
         | 
| 260 | 
            +
                    # This was the behavior of the pre 1.5 plugin.
         | 
| 261 | 
            +
                    #
         | 
| 262 | 
            +
                    # The line need to go through the codecs to replace
         | 
| 263 | 
            +
                    # unknown bytes in the log stream before doing a regexp match or
         | 
| 264 | 
            +
                    # you will get a `Error: invalid byte sequence in UTF-8'
         | 
| 265 | 
            +
                    #event = LogStash::Event.new("message" => @message)
         | 
| 266 | 
            +
                    if event_is_metadata?(event)
         | 
| 267 | 
            +
                      @logger.debug('Event is metadata, updating the current cloudfront metadata', :event => event)
         | 
| 268 | 
            +
                      update_metadata(metadata, event)
         | 
| 269 | 
            +
                    else
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                      decorate(event)
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                      event.set("cloudfront_version", metadata[:cloudfront_version]) unless metadata[:cloudfront_version].nil?
         | 
| 274 | 
            +
                      event.set("cloudfront_fields", metadata[:cloudfront_fields]) unless metadata[:cloudfront_fields].nil?
         | 
| 275 | 
            +
             | 
| 276 | 
            +
                      event.set("[@metadata][s3]", { "key" => key })
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                      if match=/#{s3_key_prefix}\/?(?<type_folder>.*?)\/.*/.match(key)
         | 
| 279 | 
            +
                        event.set('[@metadata][s3_object_folder]', match['type_folder'])
         | 
| 228 280 | 
             
                      end
         | 
| 281 | 
            +
                      #@logger.info("queue event #{i}")
         | 
| 282 | 
            +
                      #i += 1
         | 
| 283 | 
            +
                      queue << event
         | 
| 229 284 | 
             
                    end
         | 
| 230 285 | 
             
                  end
         | 
| 286 | 
            +
                end
         | 
| 287 | 
            +
                #@logger.info("event pre flush", :event => event)
         | 
| 288 | 
            +
                # #ensure any stateful codecs (such as multi-line ) are flushed to the queue
         | 
| 289 | 
            +
                instance_codec.flush do |event|
         | 
| 290 | 
            +
                  queue << event
         | 
| 291 | 
            +
                end
         | 
| 292 | 
            +
             | 
| 293 | 
            +
                return true
         | 
| 294 | 
            +
              end # def process_local_log
         | 
| 295 | 
            +
             | 
| 296 | 
            +
              private
         | 
| 297 | 
            +
              def read_file(filename, &block)
         | 
| 298 | 
            +
                if gzip?(filename)
         | 
| 299 | 
            +
                  read_gzip_file(filename, block)
         | 
| 300 | 
            +
                else
         | 
| 301 | 
            +
                  read_plain_file(filename, block)
         | 
| 302 | 
            +
                end
         | 
| 303 | 
            +
              end
         | 
| 304 | 
            +
             | 
| 305 | 
            +
              def read_plain_file(filename, block)
         | 
| 306 | 
            +
                File.open(filename, 'rb') do |file|
         | 
| 307 | 
            +
                  file.each(&block)
         | 
| 308 | 
            +
                end
         | 
| 309 | 
            +
              end
         | 
| 310 | 
            +
             | 
| 311 | 
            +
              private
         | 
| 312 | 
            +
              def read_gzip_file(filename, block)
         | 
| 313 | 
            +
                file_stream = FileInputStream.new(filename)
         | 
| 314 | 
            +
                gzip_stream = GZIPInputStream.new(file_stream)
         | 
| 315 | 
            +
                decoder = InputStreamReader.new(gzip_stream, "UTF-8")
         | 
| 316 | 
            +
                buffered = BufferedReader.new(decoder)
         | 
| 317 | 
            +
             | 
| 318 | 
            +
                while (line = buffered.readLine())
         | 
| 319 | 
            +
                  block.call(line)
         | 
| 320 | 
            +
                end
         | 
| 321 | 
            +
              rescue ZipException => e
         | 
| 322 | 
            +
                @logger.error("Gzip codec: We cannot uncompress the gzip file", :filename => filename)
         | 
| 323 | 
            +
                raise e
         | 
| 324 | 
            +
              ensure
         | 
| 325 | 
            +
                buffered.close unless buffered.nil?
         | 
| 326 | 
            +
                decoder.close unless decoder.nil?
         | 
| 327 | 
            +
                gzip_stream.close unless gzip_stream.nil?
         | 
| 328 | 
            +
                file_stream.close unless file_stream.nil?
         | 
| 329 | 
            +
              end
         | 
| 330 | 
            +
             | 
| 331 | 
            +
              private
         | 
| 332 | 
            +
              def gzip?(filename)
         | 
| 333 | 
            +
                filename.end_with?('.gz','.gzip')
         | 
| 231 334 | 
             
              end
         | 
| 232 335 |  | 
| 336 | 
            +
             | 
| 337 | 
            +
              private
         | 
| 338 | 
            +
              def delete_file_from_bucket(object)
         | 
| 339 | 
            +
                if @delete_on_success
         | 
| 340 | 
            +
                  object.delete()
         | 
| 341 | 
            +
                end
         | 
| 342 | 
            +
              end
         | 
| 343 | 
            +
             | 
| 344 | 
            +
              private
         | 
| 345 | 
            +
              def get_s3object
         | 
| 346 | 
            +
                s3 = Aws::S3::Resource.new(client: @s3_client)
         | 
| 347 | 
            +
              end
         | 
| 348 | 
            +
             | 
| 349 | 
            +
              private
         | 
| 350 | 
            +
              def event_is_metadata?(event)
         | 
| 351 | 
            +
                return false unless event.get("message").class == String
         | 
| 352 | 
            +
                line = event.get("message")
         | 
| 353 | 
            +
                version_metadata?(line) || fields_metadata?(line)
         | 
| 354 | 
            +
              end
         | 
| 355 | 
            +
             | 
| 356 | 
            +
              private
         | 
| 357 | 
            +
              def version_metadata?(line)
         | 
| 358 | 
            +
                line.start_with?('#Version: ')
         | 
| 359 | 
            +
              end
         | 
| 360 | 
            +
             | 
| 361 | 
            +
              private
         | 
| 362 | 
            +
              def fields_metadata?(line)
         | 
| 363 | 
            +
                line.start_with?('#Fields: ')
         | 
| 364 | 
            +
              end
         | 
| 365 | 
            +
             | 
| 366 | 
            +
              private
         | 
| 367 | 
            +
              def update_metadata(metadata, event)
         | 
| 368 | 
            +
                line = event.get('message').strip
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                if version_metadata?(line)
         | 
| 371 | 
            +
                  metadata[:cloudfront_version] = line.split(/#Version: (.+)/).last
         | 
| 372 | 
            +
                end
         | 
| 373 | 
            +
             | 
| 374 | 
            +
                if fields_metadata?(line)
         | 
| 375 | 
            +
                  metadata[:cloudfront_fields] = line.split(/#Fields: (.+)/).last
         | 
| 376 | 
            +
                end
         | 
| 377 | 
            +
              end
         | 
| 378 | 
            +
             | 
| 379 | 
            +
              public
         | 
| 233 380 | 
             
              def run(queue)
         | 
| 234 381 | 
             
                # ensure we can stop logstash correctly
         | 
| 235 | 
            -
                 | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 238 | 
            -
             | 
| 239 | 
            -
             | 
| 240 | 
            -
             | 
| 241 | 
            -
                 | 
| 242 | 
            -
             | 
| 243 | 
            -
             | 
| 244 | 
            -
             | 
| 245 | 
            -
             | 
| 382 | 
            +
                @runner_threads = consumer_threads.times.map { |consumer| thread_runner(queue) }
         | 
| 383 | 
            +
                @runner_threads.each { |t| t.join }
         | 
| 384 | 
            +
              end
         | 
| 385 | 
            +
             | 
| 386 | 
            +
              public
         | 
| 387 | 
            +
              def stop
         | 
| 388 | 
            +
                @runner_threads.each { |c| c.wakeup }
         | 
| 389 | 
            +
              end
         | 
| 390 | 
            +
             | 
| 391 | 
            +
              private
         | 
| 392 | 
            +
              def thread_runner(queue)
         | 
| 393 | 
            +
                Thread.new do
         | 
| 394 | 
            +
                  @logger.info("Starting new thread")
         | 
| 395 | 
            +
                  begin
         | 
| 396 | 
            +
                    poller.before_request do |stats|
         | 
| 397 | 
            +
                      if stop? then
         | 
| 398 | 
            +
                        @logger.warn("issuing :stop_polling on stop?", :queue => @queue)
         | 
| 399 | 
            +
                        # this can take up to "Receive Message Wait Time" (of the sqs queue) seconds to be recognized
         | 
| 400 | 
            +
                        throw :stop_polling
         | 
| 401 | 
            +
                      end
         | 
| 402 | 
            +
                    end
         | 
| 403 | 
            +
                    # poll a message and process it
         | 
| 404 | 
            +
                    run_with_backoff do
         | 
| 405 | 
            +
                      poller.poll(polling_options) do |message|
         | 
| 406 | 
            +
                        handle_message(message, queue, @codec.clone)
         | 
| 407 | 
            +
                      end
         | 
| 408 | 
            +
                    end
         | 
| 246 409 | 
             
                  end
         | 
| 247 410 | 
             
                end
         | 
| 248 411 | 
             
              end
         | 
| @@ -266,5 +429,4 @@ class LogStash::Inputs::S3SNSSQS < LogStash::Inputs::Threadable | |
| 266 429 | 
             
                  retry
         | 
| 267 430 | 
             
                end
         | 
| 268 431 | 
             
              end
         | 
| 269 | 
            -
             | 
| 270 | 
            -
            end # class
         | 
| 432 | 
            +
            end # class
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            Gem::Specification.new do |s|
         | 
| 2 2 | 
             
              s.name            = 'logstash-input-s3-sns-sqs'
         | 
| 3 | 
            -
              s.version         = '1. | 
| 3 | 
            +
              s.version         = '1.4.0'
         | 
| 4 4 | 
             
              s.licenses        = ['Apache License (2.0)']
         | 
| 5 5 | 
             
              s.summary         = "Get logs from AWS s3 buckets as issued by an object-created event via sns -> sqs."
         | 
| 6 6 | 
             
              s.description     = "This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program"
         | 
| @@ -19,11 +19,12 @@ Gem::Specification.new do |s| | |
| 19 19 | 
             
              s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
         | 
| 20 20 |  | 
| 21 21 | 
             
              # Gem dependencies
         | 
| 22 | 
            -
              s.add_runtime_dependency "logstash-core-plugin-api", ">= 1. | 
| 22 | 
            +
              s.add_runtime_dependency "logstash-core-plugin-api", ">= 2.1.12", "<= 2.99"
         | 
| 23 23 |  | 
| 24 24 | 
             
              s.add_runtime_dependency 'logstash-codec-json'
         | 
| 25 | 
            -
              s.add_runtime_dependency "logstash-mixin-aws" | 
| 25 | 
            +
              s.add_runtime_dependency "logstash-mixin-aws"
         | 
| 26 26 |  | 
| 27 27 | 
             
              s.add_development_dependency 'logstash-devutils'
         | 
| 28 | 
            +
             | 
| 28 29 | 
             
            end
         | 
| 29 30 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,21 +1,21 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: logstash-input-s3-sns-sqs
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.4.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Christian Herweg
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2018- | 
| 11 | 
            +
            date: 2018-03-16 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 15 15 | 
             
                requirements:
         | 
| 16 16 | 
             
                - - '>='
         | 
| 17 17 | 
             
                  - !ruby/object:Gem::Version
         | 
| 18 | 
            -
                    version:  | 
| 18 | 
            +
                    version: 2.1.12
         | 
| 19 19 | 
             
                - - <=
         | 
| 20 20 | 
             
                  - !ruby/object:Gem::Version
         | 
| 21 21 | 
             
                    version: '2.99'
         | 
| @@ -26,7 +26,7 @@ dependencies: | |
| 26 26 | 
             
                requirements:
         | 
| 27 27 | 
             
                - - '>='
         | 
| 28 28 | 
             
                  - !ruby/object:Gem::Version
         | 
| 29 | 
            -
                    version:  | 
| 29 | 
            +
                    version: 2.1.12
         | 
| 30 30 | 
             
                - - <=
         | 
| 31 31 | 
             
                  - !ruby/object:Gem::Version
         | 
| 32 32 | 
             
                    version: '2.99'
         | 
| @@ -49,7 +49,7 @@ dependencies: | |
| 49 49 | 
             
                requirements:
         | 
| 50 50 | 
             
                - - '>='
         | 
| 51 51 | 
             
                  - !ruby/object:Gem::Version
         | 
| 52 | 
            -
                    version:  | 
| 52 | 
            +
                    version: '0'
         | 
| 53 53 | 
             
              name: logstash-mixin-aws
         | 
| 54 54 | 
             
              prerelease: false
         | 
| 55 55 | 
             
              type: :runtime
         | 
| @@ -57,7 +57,7 @@ dependencies: | |
| 57 57 | 
             
                requirements:
         | 
| 58 58 | 
             
                - - '>='
         | 
| 59 59 | 
             
                  - !ruby/object:Gem::Version
         | 
| 60 | 
            -
                    version:  | 
| 60 | 
            +
                    version: '0'
         | 
| 61 61 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 62 62 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 63 63 | 
             
                requirements:
         |