fluq 0.7.5 → 0.8.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/.gitignore +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +12 -1
- data/Gemfile.lock +44 -8
- data/README.md +24 -6
- data/Rakefile +8 -1
- data/benchmark/socket.rb +13 -25
- data/examples/config/multi.rb +52 -0
- data/examples/config/simple.rb +15 -0
- data/fluq.gemspec +3 -3
- data/lib/fluq.rb +22 -16
- data/lib/fluq/cli.rb +3 -12
- data/lib/fluq/dsl.rb +2 -45
- data/lib/fluq/dsl/base.rb +11 -0
- data/lib/fluq/dsl/feed.rb +24 -0
- data/lib/fluq/dsl/root.rb +35 -0
- data/lib/fluq/event.rb +9 -28
- data/lib/fluq/feed.rb +40 -5
- data/lib/fluq/format.rb +6 -0
- data/lib/fluq/format/base.rb +42 -0
- data/lib/fluq/format/json.rb +17 -0
- data/lib/fluq/format/lines.rb +27 -0
- data/lib/fluq/format/msgpack.rb +28 -0
- data/lib/fluq/format/tsv.rb +19 -0
- data/lib/fluq/handler.rb +1 -1
- data/lib/fluq/handler/base.rb +11 -38
- data/lib/fluq/handler/log.rb +12 -14
- data/lib/fluq/handler/noop.rb +2 -0
- data/lib/fluq/input/base.rb +33 -29
- data/lib/fluq/input/socket.rb +46 -16
- data/lib/fluq/mixins.rb +2 -2
- data/lib/fluq/runner.rb +41 -0
- data/lib/fluq/testing.rb +5 -11
- data/lib/fluq/version.rb +1 -1
- data/lib/fluq/worker.rb +73 -0
- data/spec/fluq/dsl/feed_spec.rb +33 -0
- data/spec/fluq/dsl/root_spec.rb +20 -0
- data/spec/fluq/event_spec.rb +17 -12
- data/spec/fluq/feed_spec.rb +24 -0
- data/spec/fluq/format/base_spec.rb +9 -0
- data/spec/fluq/format/json_spec.rb +22 -0
- data/spec/fluq/format/lines_spec.rb +20 -0
- data/spec/fluq/format/msgpack_spec.rb +22 -0
- data/spec/fluq/format/tsv_spec.rb +21 -0
- data/spec/fluq/handler/base_spec.rb +7 -52
- data/spec/fluq/handler/log_spec.rb +11 -14
- data/spec/fluq/handler/{null_spec.rb → noop_spec.rb} +1 -3
- data/spec/fluq/input/base_spec.rb +48 -15
- data/spec/fluq/input/socket_spec.rb +34 -26
- data/spec/fluq/mixins/loggable_spec.rb +2 -2
- data/spec/fluq/runner_spec.rb +18 -0
- data/spec/fluq/worker_spec.rb +87 -0
- data/spec/fluq_spec.rb +1 -2
- data/spec/scenario/config/nested/feed1.rb +6 -0
- data/spec/scenario/config/test.rb +8 -2
- data/spec/spec_helper.rb +7 -26
- metadata +62 -62
- data/benchmark/logging.rb +0 -37
- data/examples/common.rb +0 -3
- data/examples/simple.rb +0 -5
- data/lib/fluq/buffer.rb +0 -6
- data/lib/fluq/buffer/base.rb +0 -51
- data/lib/fluq/buffer/file.rb +0 -68
- data/lib/fluq/feed/base.rb +0 -37
- data/lib/fluq/feed/json.rb +0 -28
- data/lib/fluq/feed/msgpack.rb +0 -27
- data/lib/fluq/feed/tsv.rb +0 -30
- data/lib/fluq/handler/null.rb +0 -4
- data/lib/fluq/input/socket/connection.rb +0 -41
- data/lib/fluq/mixins/logger.rb +0 -26
- data/lib/fluq/reactor.rb +0 -79
- data/spec/fluq/buffer/base_spec.rb +0 -21
- data/spec/fluq/buffer/file_spec.rb +0 -47
- data/spec/fluq/dsl_spec.rb +0 -43
- data/spec/fluq/feed/base_spec.rb +0 -15
- data/spec/fluq/feed/json_spec.rb +0 -27
- data/spec/fluq/feed/msgpack_spec.rb +0 -27
- data/spec/fluq/feed/tsv_spec.rb +0 -27
- data/spec/fluq/input/socket/connection_spec.rb +0 -35
- data/spec/fluq/mixins/logger_spec.rb +0 -25
- data/spec/fluq/reactor_spec.rb +0 -69
- data/spec/scenario/config/nested/common.rb +0 -3
@@ -0,0 +1,24 @@
|
|
1
|
+
# Feed-level DSL configuration
|
2
|
+
class FluQ::DSL::Feed < FluQ::DSL::Base
|
3
|
+
attr_reader :name, :inputs, :handlers
|
4
|
+
|
5
|
+
def initialize(name, &block)
|
6
|
+
@name = name
|
7
|
+
@inputs = []
|
8
|
+
@handlers = []
|
9
|
+
instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [Array<Symbol>] input type path, e.g. :socket
|
13
|
+
def input(*type, &block)
|
14
|
+
klass = constantize(:input, *type)
|
15
|
+
inputs.push [klass, FluQ::DSL::Options.new(&block).to_hash]
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [Array<Symbol>] handler type path, e.g. :log, :counter
|
19
|
+
def handler(*type, &block)
|
20
|
+
klass = constantize(:handler, *type)
|
21
|
+
handlers.push [klass, FluQ::DSL::Options.new(&block).to_hash]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Root-level DSL configuration
|
2
|
+
class FluQ::DSL::Root < FluQ::DSL::Base
|
3
|
+
attr_reader :path, :feeds
|
4
|
+
|
5
|
+
# @param [String] DSL script file path
|
6
|
+
def initialize(path)
|
7
|
+
@path = Pathname.new(path)
|
8
|
+
@feeds = []
|
9
|
+
|
10
|
+
instance_eval @path.read
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [String] feed name, e.g. "my_events"
|
14
|
+
def feed(name, &block)
|
15
|
+
feeds.push FluQ::DSL::Feed.new(name, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [String] relative relative path
|
19
|
+
def import(relative)
|
20
|
+
instance_eval path.dirname.join(relative).read
|
21
|
+
end
|
22
|
+
|
23
|
+
# Applies the configuration.
|
24
|
+
# Registers components of each feed. Handlers first, then inputs.
|
25
|
+
# @param [FluQ::Runner] runner
|
26
|
+
def apply(runner)
|
27
|
+
feeds.each do |conf|
|
28
|
+
runner.feed conf.name do |feed|
|
29
|
+
conf.handlers.each {|k, *a| feed.register(k, *a) }
|
30
|
+
conf.inputs.each {|k, *a| feed.listen(k, *a) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/lib/fluq/event.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
class FluQ::Event < Hash
|
2
2
|
|
3
|
-
|
3
|
+
attr_accessor :timestamp
|
4
|
+
attr_reader :meta
|
4
5
|
|
5
|
-
# @param [String] tag the event tag
|
6
|
-
# @param [Integer] timestamp the UNIX timestamp
|
7
6
|
# @param [Hash] record the attribute pairs
|
8
|
-
|
9
|
-
|
7
|
+
# @param [Integer] timestamp the UNIX timestamp
|
8
|
+
def initialize(record = {}, timestamp = Time.now)
|
9
|
+
@timestamp = timestamp.to_i
|
10
|
+
@meta = {}
|
10
11
|
super()
|
11
12
|
update(record) if Hash === record
|
12
13
|
end
|
@@ -16,40 +17,20 @@ class FluQ::Event < Hash
|
|
16
17
|
@time ||= Time.at(timestamp).utc
|
17
18
|
end
|
18
19
|
|
19
|
-
# @return [Array] tuple
|
20
|
-
def to_a
|
21
|
-
[tag, timestamp, self]
|
22
|
-
end
|
23
|
-
|
24
20
|
# @return [Boolean] true if comparable
|
25
21
|
def ==(other)
|
26
22
|
case other
|
27
|
-
when
|
28
|
-
|
23
|
+
when FluQ::Event
|
24
|
+
super && other.timestamp == timestamp
|
29
25
|
else
|
30
26
|
super
|
31
27
|
end
|
32
28
|
end
|
33
29
|
alias :eql? :==
|
34
30
|
|
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
31
|
# @return [String] inspection
|
51
32
|
def inspect
|
52
|
-
|
33
|
+
"#<FluQ::Event(#{timestamp}) data:#{super} meta:#{meta.inspect}>"
|
53
34
|
end
|
54
35
|
|
55
36
|
end
|
data/lib/fluq/feed.rb
CHANGED
@@ -1,6 +1,41 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
class FluQ::Feed < Celluloid::SupervisionGroup
|
2
|
+
|
3
|
+
# @attr_reader [String] name
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
# @attr_reader [Array] handlers
|
7
|
+
attr_reader :handlers
|
8
|
+
|
9
|
+
# Constructor
|
10
|
+
# @param [String] name feed name
|
11
|
+
def initialize(name, &block)
|
12
|
+
@name = name.to_s
|
13
|
+
@handlers = []
|
14
|
+
super(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Array<FluQ::Input::Base>] inputs
|
18
|
+
def inputs
|
19
|
+
actors
|
20
|
+
end
|
3
21
|
|
4
|
-
|
5
|
-
|
6
|
-
|
22
|
+
# Listens to an input
|
23
|
+
# @param [Class<FluQ::Input::Base>] klass input class
|
24
|
+
# @param [multiple] args initialization arguments
|
25
|
+
def listen(klass, *args)
|
26
|
+
supervise klass, name, handlers, *args
|
27
|
+
end
|
28
|
+
|
29
|
+
# Registers a handler
|
30
|
+
# @param [Class<FluQ::Handler::Base>] klass handler class
|
31
|
+
# @param [multiple] args initialization arguments
|
32
|
+
def register(klass, *args)
|
33
|
+
handlers.push [klass, *args]
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String] introspection
|
37
|
+
def inspect
|
38
|
+
"#<#{self.class.name}(#{name}) inputs: #{inputs.size}, handlers: #{handlers.size}>"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/lib/fluq/format.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
class FluQ::Format::Base
|
2
|
+
include FluQ::Mixins::Loggable
|
3
|
+
extend FluQ::Mixins::Loggable
|
4
|
+
|
5
|
+
# @abstract converter
|
6
|
+
# @param [String] raw event string
|
7
|
+
# @return [FluQ::Event] event
|
8
|
+
def self.to_event(raw)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @abstract initializer
|
12
|
+
# @param [Hash] options format-specific options
|
13
|
+
def initialize(options = {})
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
# @abstract parse data, return events
|
18
|
+
# @param [String] data
|
19
|
+
# @return [Array<FluQ::Event>] events
|
20
|
+
def parse(data)
|
21
|
+
events = []
|
22
|
+
parse_each(data) do |raw|
|
23
|
+
if event = self.class.to_event(raw)
|
24
|
+
events.push(event)
|
25
|
+
true
|
26
|
+
else
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
events
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# @abstract enumerator
|
36
|
+
# @param [String] data
|
37
|
+
# @yield over raw events
|
38
|
+
# @yieldparam [Hash] raw event data
|
39
|
+
def parse_each(data)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class FluQ::Format::Json < FluQ::Format::Lines
|
2
|
+
|
3
|
+
# @see FluQ::Format::Base.to_event
|
4
|
+
def self.to_event(raw)
|
5
|
+
case hash = MultiJson.load(raw)
|
6
|
+
when Hash
|
7
|
+
FluQ::Event.new(hash)
|
8
|
+
else
|
9
|
+
logger.warn "buffer contained invalid event #{hash.inspect}"
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
rescue MultiJson::LoadError
|
13
|
+
logger.warn "buffer contained invalid line #{raw.inspect}"
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class FluQ::Format::Lines < FluQ::Format::Base
|
2
|
+
include MonitorMixin
|
3
|
+
|
4
|
+
# @see FluQ::Format::Base#initialize
|
5
|
+
def initialize(*)
|
6
|
+
super
|
7
|
+
@buffer = ""
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
# @see FluQ::Format::Base#parse_each
|
13
|
+
def parse_each(chunk)
|
14
|
+
last_chunk = nil
|
15
|
+
synchronize do
|
16
|
+
@buffer << chunk
|
17
|
+
@buffer.each_line do |line|
|
18
|
+
line.chomp!
|
19
|
+
next if line.empty?
|
20
|
+
|
21
|
+
last_chunk = yield(line) ? nil : line
|
22
|
+
end
|
23
|
+
last_chunk ? @buffer = last_chunk : @buffer.clear
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class FluQ::Format::Msgpack < FluQ::Format::Base
|
2
|
+
|
3
|
+
# @see FluQ::Format::Base.to_event
|
4
|
+
def self.to_event(raw)
|
5
|
+
case raw
|
6
|
+
when Hash
|
7
|
+
FluQ::Event.new(raw)
|
8
|
+
else
|
9
|
+
logger.warn "buffer contained invalid event #{raw.inspect}"
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Msgpack initializer
|
15
|
+
# @see FluQ::Format::Base#initialize
|
16
|
+
def initialize(*)
|
17
|
+
super
|
18
|
+
@buffer = MessagePack::Unpacker.new
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
# @see FluQ::Format::Base#parse_each
|
24
|
+
def parse_each(chunk, &block)
|
25
|
+
@buffer.feed_each(chunk, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
end if defined?(MessagePack)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class FluQ::Format::Tsv < FluQ::Format::Lines
|
2
|
+
|
3
|
+
# @see FluQ::Format::Base.to_event
|
4
|
+
def self.to_event(raw)
|
5
|
+
timestamp, json = raw.split("\t")
|
6
|
+
|
7
|
+
case hash = MultiJson.load(json)
|
8
|
+
when Hash
|
9
|
+
FluQ::Event.new hash, timestamp
|
10
|
+
else
|
11
|
+
logger.warn "buffer contained invalid event #{hash.inspect}"
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
rescue MultiJson::LoadError, ArgumentError
|
15
|
+
logger.warn "buffer contained invalid line #{raw.inspect}"
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/fluq/handler.rb
CHANGED
data/lib/fluq/handler/base.rb
CHANGED
@@ -8,43 +8,34 @@ class FluQ::Handler::Base
|
|
8
8
|
@type ||= name.split("::")[-1].downcase
|
9
9
|
end
|
10
10
|
|
11
|
-
# @attr_reader [FluQ::Reactor] reactor
|
12
|
-
attr_reader :reactor
|
13
|
-
|
14
11
|
# @attr_reader [String] name unique name
|
15
12
|
attr_reader :name
|
16
13
|
|
17
14
|
# @attr_reader [Hash] config
|
18
15
|
attr_reader :config
|
19
16
|
|
20
|
-
# @attr_reader [Regexp] pattern
|
21
|
-
attr_reader :pattern
|
22
|
-
|
23
17
|
# @param [Hash] options
|
24
18
|
# @option options [String] :name a (unique) handler identifier
|
25
|
-
# @option options [String] :pattern tag pattern to match
|
26
19
|
# @example
|
27
20
|
#
|
28
21
|
# class MyHandler < FluQ::Handler::Base
|
29
22
|
# end
|
30
|
-
# MyHandler.new
|
23
|
+
# MyHandler.new
|
31
24
|
#
|
32
|
-
def initialize(
|
33
|
-
@reactor = reactor
|
25
|
+
def initialize(options = {})
|
34
26
|
@config = defaults.merge(options)
|
35
|
-
@name = config[:name] ||
|
36
|
-
@pattern = generate_pattern
|
27
|
+
@name = config[:name] || self.class.type
|
37
28
|
end
|
38
29
|
|
39
|
-
# @
|
40
|
-
|
41
|
-
|
30
|
+
# @param [Array<FluQ::Event>] events
|
31
|
+
# @return [Array<FluQ::Event>] filtered events
|
32
|
+
def filter(events)
|
33
|
+
events
|
42
34
|
end
|
43
35
|
|
44
|
-
# @
|
45
|
-
|
46
|
-
|
47
|
-
events.select {|e| match?(e) }
|
36
|
+
# @return [Timers] timers
|
37
|
+
def timers
|
38
|
+
@timers ||= Timers.new
|
48
39
|
end
|
49
40
|
|
50
41
|
# @abstract callback, called on each event
|
@@ -56,25 +47,7 @@ class FluQ::Handler::Base
|
|
56
47
|
|
57
48
|
# Configuration defaults
|
58
49
|
def defaults
|
59
|
-
{
|
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}$"
|
50
|
+
{ timeout: 60 }
|
78
51
|
end
|
79
52
|
|
80
53
|
end
|
data/lib/fluq/handler/log.rb
CHANGED
@@ -20,10 +20,10 @@ class FluQ::Handler::Log < FluQ::Handler::Base
|
|
20
20
|
# @see FluQ::Handler::Base#initialize
|
21
21
|
def initialize(*)
|
22
22
|
super
|
23
|
-
@
|
24
|
-
@rewrite
|
25
|
-
@convert
|
26
|
-
@pool
|
23
|
+
@path = config[:path]
|
24
|
+
@rewrite = config[:rewrite]
|
25
|
+
@convert = config[:convert]
|
26
|
+
@pool = FilePool.new max_size: config[:cache_max], ttl: config[:cache_ttl]
|
27
27
|
end
|
28
28
|
|
29
29
|
# @see FluQ::Handler::Base#on_events
|
@@ -36,9 +36,8 @@ class FluQ::Handler::Log < FluQ::Handler::Base
|
|
36
36
|
# Configuration defaults
|
37
37
|
def defaults
|
38
38
|
super.merge \
|
39
|
-
path:
|
40
|
-
|
41
|
-
convert: lambda {|event| event.to_tsv },
|
39
|
+
path: "log/raw/%Y%m%d.log",
|
40
|
+
convert: ->evt { [evt.timestamp, MultiJson.dump(evt)].join("\t") },
|
42
41
|
cache_max: 100,
|
43
42
|
cache_ttl: 300
|
44
43
|
end
|
@@ -46,7 +45,7 @@ class FluQ::Handler::Log < FluQ::Handler::Base
|
|
46
45
|
def write(path, slice, attepts = 0)
|
47
46
|
io = @pool.open(path)
|
48
47
|
slice.each do |event|
|
49
|
-
io.write
|
48
|
+
io.write @convert.call(event) << "\n"
|
50
49
|
end
|
51
50
|
rescue IOError
|
52
51
|
@pool.delete path.to_s
|
@@ -54,14 +53,13 @@ class FluQ::Handler::Log < FluQ::Handler::Base
|
|
54
53
|
end
|
55
54
|
|
56
55
|
def partition(events)
|
57
|
-
paths = {}
|
56
|
+
paths = Hash.new {|h,k| h[k] = [] }
|
58
57
|
events.each do |event|
|
59
|
-
tag = @rewrite.call(event.
|
60
|
-
path = event.time.strftime(@
|
61
|
-
paths[path]
|
62
|
-
paths[path] << event
|
58
|
+
tag = @rewrite ? @rewrite.call(event).to_s : ""
|
59
|
+
path = event.time.strftime(FluQ.root.join(@path).to_s.gsub("%t", tag))
|
60
|
+
paths[path] << event
|
63
61
|
end
|
64
62
|
paths
|
65
63
|
end
|
66
64
|
|
67
|
-
end
|
65
|
+
end
|