fluq 0.7.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.
- data/.gitignore +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +39 -0
- data/MIT-LICENCE +19 -0
- data/README.md +10 -0
- data/Rakefile +11 -0
- data/benchmark/logging.rb +37 -0
- data/benchmark/socket.rb +52 -0
- data/bin/fluq-rb +8 -0
- data/examples/common.rb +3 -0
- data/examples/simple.rb +5 -0
- data/fluq.gemspec +33 -0
- data/lib/fluq.rb +50 -0
- data/lib/fluq/buffer.rb +6 -0
- data/lib/fluq/buffer/base.rb +51 -0
- data/lib/fluq/buffer/file.rb +68 -0
- data/lib/fluq/cli.rb +142 -0
- data/lib/fluq/dsl.rb +49 -0
- data/lib/fluq/dsl/options.rb +27 -0
- data/lib/fluq/error.rb +2 -0
- data/lib/fluq/event.rb +55 -0
- data/lib/fluq/feed.rb +6 -0
- data/lib/fluq/feed/base.rb +18 -0
- data/lib/fluq/feed/json.rb +28 -0
- data/lib/fluq/feed/msgpack.rb +27 -0
- data/lib/fluq/feed/tsv.rb +30 -0
- data/lib/fluq/handler.rb +6 -0
- data/lib/fluq/handler/base.rb +80 -0
- data/lib/fluq/handler/log.rb +67 -0
- data/lib/fluq/handler/null.rb +4 -0
- data/lib/fluq/input.rb +6 -0
- data/lib/fluq/input/base.rb +59 -0
- data/lib/fluq/input/socket.rb +50 -0
- data/lib/fluq/input/socket/connection.rb +41 -0
- data/lib/fluq/mixins.rb +6 -0
- data/lib/fluq/mixins/loggable.rb +7 -0
- data/lib/fluq/mixins/logger.rb +26 -0
- data/lib/fluq/reactor.rb +76 -0
- data/lib/fluq/testing.rb +26 -0
- data/lib/fluq/url.rb +16 -0
- data/lib/fluq/version.rb +3 -0
- data/spec/fluq/buffer/base_spec.rb +21 -0
- data/spec/fluq/buffer/file_spec.rb +47 -0
- data/spec/fluq/dsl/options_spec.rb +24 -0
- data/spec/fluq/dsl_spec.rb +43 -0
- data/spec/fluq/event_spec.rb +25 -0
- data/spec/fluq/feed/base_spec.rb +15 -0
- data/spec/fluq/feed/json_spec.rb +27 -0
- data/spec/fluq/feed/msgpack_spec.rb +27 -0
- data/spec/fluq/feed/tsv_spec.rb +27 -0
- data/spec/fluq/handler/base_spec.rb +70 -0
- data/spec/fluq/handler/log_spec.rb +68 -0
- data/spec/fluq/handler/null_spec.rb +11 -0
- data/spec/fluq/input/base_spec.rb +29 -0
- data/spec/fluq/input/socket/connection_spec.rb +35 -0
- data/spec/fluq/input/socket_spec.rb +45 -0
- data/spec/fluq/mixins/loggable_spec.rb +10 -0
- data/spec/fluq/mixins/logger_spec.rb +25 -0
- data/spec/fluq/reactor_spec.rb +58 -0
- data/spec/fluq/url_spec.rb +16 -0
- data/spec/fluq_spec.rb +11 -0
- data/spec/scenario/config/nested/common.rb +3 -0
- data/spec/scenario/config/test.rb +3 -0
- data/spec/scenario/lib/fluq/handler/custom/test_handler.rb +4 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/configuration.rb +25 -0
- metadata +242 -0
    
        data/lib/fluq/dsl.rb
    ADDED
    
    | @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            class FluQ::DSL
         | 
| 2 | 
            +
              attr_reader :path, :reactor, :inputs, :handlers
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              # @param [FluQ::Reactor] reactor
         | 
| 5 | 
            +
              # @param [String] DSL script file path
         | 
| 6 | 
            +
              def initialize(reactor, path)
         | 
| 7 | 
            +
                @reactor  = reactor
         | 
| 8 | 
            +
                @path     = Pathname.new(path)
         | 
| 9 | 
            +
                @inputs   = []
         | 
| 10 | 
            +
                @handlers = []
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              # @param [Array<Symbol>] input type path, e.g. :socket
         | 
| 14 | 
            +
              def input(*type, &block)
         | 
| 15 | 
            +
                klass = constantize(:input, *type)
         | 
| 16 | 
            +
                inputs.push [klass, FluQ::DSL::Options.new(&block).to_hash]
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              # @param [Array<Symbol>] handler type path, e.g. :log, :counter
         | 
| 20 | 
            +
              def handler(*type, &block)
         | 
| 21 | 
            +
                klass = constantize(:handler, *type)
         | 
| 22 | 
            +
                handlers.push [klass, FluQ::DSL::Options.new(&block).to_hash]
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              # @param [String] relative relative path
         | 
| 26 | 
            +
              def import(relative)
         | 
| 27 | 
            +
                instance_eval(path.dirname.join(relative).read)
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              # Starts the components. Handlers first, then inputs.
         | 
| 31 | 
            +
              def run
         | 
| 32 | 
            +
                instance_eval(path.read)
         | 
| 33 | 
            +
                handlers.each {|klass, options| reactor.register(klass, options) }
         | 
| 34 | 
            +
                inputs.each   {|klass, options| reactor.listen(klass, options) }
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              protected
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def constantize(*path)
         | 
| 40 | 
            +
                  require([:fluq, *path].join('/'))
         | 
| 41 | 
            +
                  names = path.map {|p| p.to_s.split('_').map(&:capitalize).join }
         | 
| 42 | 
            +
                  names.inject(FluQ) {|klass, name| klass.const_get(name) }
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            %w'options'.each do |name|
         | 
| 48 | 
            +
              require "fluq/dsl/#{name}"
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            class FluQ::DSL::Options
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              # Constructor
         | 
| 4 | 
            +
              # @yield options assigment
         | 
| 5 | 
            +
              def initialize(&block)
         | 
| 6 | 
            +
                @opts = {}
         | 
| 7 | 
            +
                instance_eval(&block) if block
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              # @return [Hash] options hash
         | 
| 11 | 
            +
              def to_hash
         | 
| 12 | 
            +
                @opts
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              protected
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def method_missing(name, *args, &block)
         | 
| 18 | 
            +
                  value = args[0]
         | 
| 19 | 
            +
                  if value && block
         | 
| 20 | 
            +
                    @opts[name.to_sym] = value
         | 
| 21 | 
            +
                    @opts[:"#{name}_options"] = self.class.new(&block).to_hash
         | 
| 22 | 
            +
                  else
         | 
| 23 | 
            +
                    @opts[name.to_sym] = value || block || true
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            end
         | 
    
        data/lib/fluq/error.rb
    ADDED
    
    
    
        data/lib/fluq/event.rb
    ADDED
    
    | @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            class FluQ::Event < Hash
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              attr_reader :tag, :timestamp
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              # @param [String] tag the event tag
         | 
| 6 | 
            +
              # @param [Integer] timestamp the UNIX timestamp
         | 
| 7 | 
            +
              # @param [Hash] record the attribute pairs
         | 
| 8 | 
            +
              def initialize(tag = "", timestamp = 0, record = {})
         | 
| 9 | 
            +
                @tag, @timestamp = tag.to_s, timestamp.to_i
         | 
| 10 | 
            +
                super()
         | 
| 11 | 
            +
                update(record) if Hash === record
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              # @return [Time] UTC time
         | 
| 15 | 
            +
              def time
         | 
| 16 | 
            +
                @time ||= Time.at(timestamp).utc
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              # @return [Array] tuple
         | 
| 20 | 
            +
              def to_a
         | 
| 21 | 
            +
                [tag, timestamp, self]
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              # @return [Boolean] true if comparable
         | 
| 25 | 
            +
              def ==(other)
         | 
| 26 | 
            +
                case other
         | 
| 27 | 
            +
                when Array
         | 
| 28 | 
            +
                  to_a == other
         | 
| 29 | 
            +
                else
         | 
| 30 | 
            +
                  super
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
              alias :eql? :==
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              # @return [String] tab-separated string
         | 
| 36 | 
            +
              def to_tsv
         | 
| 37 | 
            +
                [tag, timestamp, Oj.dump(self)].join("\t")
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              # @return [String] JSON encoded
         | 
| 41 | 
            +
              def to_json
         | 
| 42 | 
            +
                Oj.dump merge("=" => tag, "@" => timestamp)
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              # @return [String] mgspack encoded bytes
         | 
| 46 | 
            +
              def to_msgpack
         | 
| 47 | 
            +
                MessagePack.pack merge("=" => tag, "@" => timestamp)
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              # @return [String] inspection
         | 
| 51 | 
            +
              def inspect
         | 
| 52 | 
            +
                [tag, timestamp, Hash.new.update(self)].inspect
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            end
         | 
    
        data/lib/fluq/feed.rb
    ADDED
    
    
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            class FluQ::Feed::Base
         | 
| 2 | 
            +
              include Enumerable
         | 
| 3 | 
            +
              include FluQ::Mixins::Loggable
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              # @attr_reader [FluQ::Buffer::Base] buffer
         | 
| 6 | 
            +
              attr_reader :buffer
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              # @param [FluQ::Buffer::Base] buffer
         | 
| 9 | 
            +
              def initialize(buffer)
         | 
| 10 | 
            +
                @buffer = buffer
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              # @abstract enumerator
         | 
| 14 | 
            +
              # @yield ober a feed of events
         | 
| 15 | 
            +
              # @yieldparam [FluQ::Event] event
         | 
| 16 | 
            +
              def each
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            class FluQ::Feed::Json < FluQ::Feed::Base
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              # @see [FluQ::Feed::Base] each
         | 
| 4 | 
            +
              def each
         | 
| 5 | 
            +
                buffer.drain do |io|
         | 
| 6 | 
            +
                  while line = io.gets
         | 
| 7 | 
            +
                    event = to_event(line)
         | 
| 8 | 
            +
                    yield event if event
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              private
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def to_event(line)
         | 
| 16 | 
            +
                  case hash = Oj.load(line)
         | 
| 17 | 
            +
                  when Hash
         | 
| 18 | 
            +
                    FluQ::Event.new hash.delete("="), hash.delete("@"), hash
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    logger.warn "buffer contained invalid event #{hash.inspect}"
         | 
| 21 | 
            +
                    nil
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                rescue Oj::ParseError
         | 
| 24 | 
            +
                  logger.warn "buffer contained invalid line #{line.inspect}"
         | 
| 25 | 
            +
                  nil
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            class FluQ::Feed::Msgpack < FluQ::Feed::Base
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              # @see [FluQ::Feed::Base] each
         | 
| 4 | 
            +
              def each
         | 
| 5 | 
            +
                buffer.drain do |io|
         | 
| 6 | 
            +
                  pac = MessagePack::Unpacker.new(io)
         | 
| 7 | 
            +
                  pac.each do |hash|
         | 
| 8 | 
            +
                    event = to_event(hash)
         | 
| 9 | 
            +
                    yield event if event
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              rescue EOFError
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              private
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def to_event(hash)
         | 
| 18 | 
            +
                  case hash
         | 
| 19 | 
            +
                  when Hash
         | 
| 20 | 
            +
                    FluQ::Event.new hash.delete("="), hash.delete("@"), hash
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    logger.warn "buffer contained invalid event #{hash.inspect}"
         | 
| 23 | 
            +
                    nil
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            class FluQ::Feed::Tsv < FluQ::Feed::Base
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              # @see [FluQ::Feed::Base] each
         | 
| 4 | 
            +
              def each
         | 
| 5 | 
            +
                buffer.drain do |io|
         | 
| 6 | 
            +
                  while line = io.gets
         | 
| 7 | 
            +
                    event = to_event(line)
         | 
| 8 | 
            +
                    yield event if event
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              private
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def to_event(line)
         | 
| 16 | 
            +
                  tag, timestamp, json = line.split("\t")
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  case hash = Oj.load(json)
         | 
| 19 | 
            +
                  when Hash
         | 
| 20 | 
            +
                    FluQ::Event.new tag, timestamp, hash
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    logger.warn "buffer contained invalid event #{[tag, timestamp, hash].inspect}"
         | 
| 23 | 
            +
                    nil
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                rescue Oj::ParseError, ArgumentError
         | 
| 26 | 
            +
                  logger.warn "buffer contained invalid line #{line.inspect}"
         | 
| 27 | 
            +
                  nil
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            end
         | 
    
        data/lib/fluq/handler.rb
    ADDED
    
    
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            require 'digest/md5'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class FluQ::Handler::Base
         | 
| 4 | 
            +
              include FluQ::Mixins::Loggable
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              # @return [String] handler type
         | 
| 7 | 
            +
              def self.type
         | 
| 8 | 
            +
                @type ||= name.split("::")[-1].downcase
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              # @attr_reader [FluQ::Reactor] reactor
         | 
| 12 | 
            +
              attr_reader :reactor
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              # @attr_reader [String] name unique name
         | 
| 15 | 
            +
              attr_reader :name
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              # @attr_reader [Hash] config
         | 
| 18 | 
            +
              attr_reader :config
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              # @attr_reader [Regexp] pattern
         | 
| 21 | 
            +
              attr_reader :pattern
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              # @param [Hash] options
         | 
| 24 | 
            +
              # @option options [String] :name a (unique) handler identifier
         | 
| 25 | 
            +
              # @option options [String] :pattern tag pattern to match
         | 
| 26 | 
            +
              # @example
         | 
| 27 | 
            +
              #
         | 
| 28 | 
            +
              #   class MyHandler < FluQ::Handler::Base
         | 
| 29 | 
            +
              #   end
         | 
| 30 | 
            +
              #   MyHandler.new(reactor, pattern: "visits.*")
         | 
| 31 | 
            +
              #
         | 
| 32 | 
            +
              def initialize(reactor, options = {})
         | 
| 33 | 
            +
                @reactor = reactor
         | 
| 34 | 
            +
                @config  = defaults.merge(options)
         | 
| 35 | 
            +
                @name    = config[:name] || generate_name
         | 
| 36 | 
            +
                @pattern = generate_pattern
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              # @return [Boolean] true if event matches
         | 
| 40 | 
            +
              def match?(event)
         | 
| 41 | 
            +
                !!(pattern =~ event.tag)
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              # @param [Array<FluQ::Event>] events
         | 
| 45 | 
            +
              # @return [Array<FluQ::Event>] matching events
         | 
| 46 | 
            +
              def select(events)
         | 
| 47 | 
            +
                events.select &method(:match?)
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              # @abstract callback, called on each event
         | 
| 51 | 
            +
              # @param [Array<FluQ::Event>] the event stream
         | 
| 52 | 
            +
              def on_events(events)
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              protected
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                # Configuration defaults
         | 
| 58 | 
            +
                def defaults
         | 
| 59 | 
            +
                  { pattern: /./ }
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                # @return [String] generated name
         | 
| 63 | 
            +
                def generate_name
         | 
| 64 | 
            +
                  suffix = [Digest::MD5.digest(config[:pattern].to_s)].pack("m0").tr('+/=lIO0', 'pqrsxyz')[0,6]
         | 
| 65 | 
            +
                  [self.class.type, suffix].join("-")
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def generate_pattern
         | 
| 69 | 
            +
                  return config[:pattern] if Regexp === config[:pattern]
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  string = Regexp.quote(config[:pattern])
         | 
| 72 | 
            +
                  string.gsub!("\\*", ".*")
         | 
| 73 | 
            +
                  string.gsub!("\\?", ".")
         | 
| 74 | 
            +
                  string.gsub!(/\\\{(.+?)\\\}/) do |match|
         | 
| 75 | 
            +
                    "(?:#{$1.split(",").join("|")})"
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                  Regexp.new "^#{string}$"
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            end
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            class FluQ::Handler::Log < FluQ::Handler::Base
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class FilePool < TimedLRU
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def open(path)
         | 
| 6 | 
            +
                  path = path.to_s
         | 
| 7 | 
            +
                  self[path.to_s] ||= begin
         | 
| 8 | 
            +
                    FileUtils.mkdir_p File.dirname(path)
         | 
| 9 | 
            +
                    file = File.open(path, "a+")
         | 
| 10 | 
            +
                    file.autoclose = true
         | 
| 11 | 
            +
                    file
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              # @attr_reader [FluQ::Handler::Log::FilePool] file pool
         | 
| 18 | 
            +
              attr_reader :pool
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              # @see FluQ::Handler::Base#initialize
         | 
| 21 | 
            +
              def initialize(*)
         | 
| 22 | 
            +
                super
         | 
| 23 | 
            +
                @full_path = FluQ.root.join(config[:path]).to_s.freeze
         | 
| 24 | 
            +
                @rewrite   = config[:rewrite]
         | 
| 25 | 
            +
                @convert   = config[:convert]
         | 
| 26 | 
            +
                @pool      = FilePool.new max_size: config[:cache_max], ttl: config[:cache_ttl]
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              # @see FluQ::Handler::Base#on_events
         | 
| 30 | 
            +
              def on_events(events)
         | 
| 31 | 
            +
                partition(events).each {|path, slice| write(path, slice) }
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              protected
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                # Configuration defaults
         | 
| 37 | 
            +
                def defaults
         | 
| 38 | 
            +
                  super.merge \
         | 
| 39 | 
            +
                    path: "log/raw/%t/%Y%m%d/%H.log",
         | 
| 40 | 
            +
                    rewrite:  lambda {|tag| tag.gsub(".", "/") },
         | 
| 41 | 
            +
                    convert:  lambda {|event| event.to_tsv },
         | 
| 42 | 
            +
                    cache_max: 100,
         | 
| 43 | 
            +
                    cache_ttl: 300
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def write(path, slice, attepts = 0)
         | 
| 47 | 
            +
                  io = @pool.open(path)
         | 
| 48 | 
            +
                  slice.each do |event|
         | 
| 49 | 
            +
                    io.write "#{@convert.call(event)}\n"
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                rescue IOError
         | 
| 52 | 
            +
                  @pool.delete path.to_s
         | 
| 53 | 
            +
                  (attepts+=1) < 3 ? retry : raise
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def partition(events)
         | 
| 57 | 
            +
                  paths = {}
         | 
| 58 | 
            +
                  events.each do |event|
         | 
| 59 | 
            +
                    tag  = @rewrite.call(event.tag)
         | 
| 60 | 
            +
                    path = event.time.strftime(@full_path.gsub("%t", tag))
         | 
| 61 | 
            +
                    paths[path] ||= []
         | 
| 62 | 
            +
                    paths[path]  << event
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                  paths
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            end
         | 
    
        data/lib/fluq/input.rb
    ADDED
    
    
| @@ -0,0 +1,59 @@ | |
| 1 | 
            +
            class FluQ::Input::Base
         | 
| 2 | 
            +
              include FluQ::Mixins::Loggable
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              # @attr_reader [FluQ::Reactor] reactor reference
         | 
| 5 | 
            +
              attr_reader :reactor
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              # @attr_reader [Hash] config
         | 
| 8 | 
            +
              attr_reader :config
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              # @param [FluQ::Reactor] reactor
         | 
| 11 | 
            +
              # @param [Hash] options various configuration options
         | 
| 12 | 
            +
              def initialize(reactor, options = {})
         | 
| 13 | 
            +
                super()
         | 
| 14 | 
            +
                @reactor = reactor
         | 
| 15 | 
            +
                @config  = defaults.merge(options)
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              # @return [String] descriptive name
         | 
| 19 | 
            +
              def name
         | 
| 20 | 
            +
                @name ||= self.class.name.split("::")[-1].downcase
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              # Start the input
         | 
| 24 | 
            +
              def run
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              # Creates a new buffer object
         | 
| 28 | 
            +
              # @return [FluQ::Buffer::Base] a new buffer
         | 
| 29 | 
            +
              def new_buffer
         | 
| 30 | 
            +
                buffer_klass.new config[:buffer_options]
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              # Flushes and closes a buffer
         | 
| 34 | 
            +
              # @param [FluQ::Buffer::Base] buffer
         | 
| 35 | 
            +
              def flush!(buffer)
         | 
| 36 | 
            +
                feed_klass.new(buffer).each_slice(10_000) do |events|
         | 
| 37 | 
            +
                  reactor.process(events)
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              rescue => ex
         | 
| 40 | 
            +
                logger.crash "#{self.class.name} failure: #{ex.message} (#{ex.class.name})", ex
         | 
| 41 | 
            +
              ensure
         | 
| 42 | 
            +
                buffer.close if buffer
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              protected
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def buffer_klass
         | 
| 48 | 
            +
                  @buffer_klass ||= FluQ::Buffer.const_get(config[:buffer].to_s.capitalize)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def feed_klass
         | 
| 52 | 
            +
                  @feed_klass ||= FluQ::Feed.const_get(config[:feed].to_s.capitalize)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def defaults
         | 
| 56 | 
            +
                  { buffer: "file", feed: "msgpack", buffer_options: {} }
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            end
         |