fluent-plugin-filter_where 1.0.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 +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/Gemfile.fluentd.0.12 +4 -0
- data/LICENSE +22 -0
- data/README.md +141 -0
- data/Rakefile +24 -0
- data/example.conf +14 -0
- data/fluent-plugin-filter_where.gemspec +30 -0
- data/lib/fluent/plugin/filter_where.rb +7 -0
- data/lib/fluent/plugin/filter_where/core.rb +32 -0
- data/lib/fluent/plugin/filter_where/parser.racc +86 -0
- data/lib/fluent/plugin/filter_where/parser.rex +92 -0
- data/lib/fluent/plugin/filter_where/parser.rex.rb +206 -0
- data/lib/fluent/plugin/filter_where/parser.tab.rb +419 -0
- data/lib/fluent/plugin/filter_where/parser/exp.rb +178 -0
- data/lib/fluent/plugin/filter_where/parser/literal.rb +56 -0
- data/lib/fluent/plugin/filter_where/v12.rb +26 -0
- data/lib/fluent/plugin/filter_where/v14.rb +21 -0
- data/test/bench_filter_where.rb +23 -0
- data/test/helper.rb +98 -0
- data/test/test_filter_where.rb +37 -0
- data/test/test_where_parser.rb +109 -0
- metadata +197 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
# Operation Node of AST (Abstract Syntax Tree)
|
2
|
+
require_relative 'literal'
|
3
|
+
|
4
|
+
module Fluent
|
5
|
+
module FilterWhere
|
6
|
+
class Parser
|
7
|
+
class Exp
|
8
|
+
def eval(record)
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class BinaryOpExp < Exp
|
14
|
+
attr_reader :left, :right, :operator
|
15
|
+
|
16
|
+
# @param [ParserLiteral] left
|
17
|
+
# @param [ParserLiteral] right
|
18
|
+
# @param [Symbol] operator
|
19
|
+
def initialize(left, right, operator)
|
20
|
+
@left = left
|
21
|
+
@right = right
|
22
|
+
@operator = operator
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class BooleanOpExp < BinaryOpExp
|
27
|
+
# @return [Boolean]
|
28
|
+
def eval(record)
|
29
|
+
l = left.get(record)
|
30
|
+
r = right.get(record)
|
31
|
+
case operator
|
32
|
+
when :EQ
|
33
|
+
l == r
|
34
|
+
when :NEQ
|
35
|
+
l != r
|
36
|
+
else
|
37
|
+
assert(false)
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class NumberOpExp < BinaryOpExp
|
44
|
+
# @return [Boolean]
|
45
|
+
def eval(record)
|
46
|
+
l = left.get(record)
|
47
|
+
r = right.get(record)
|
48
|
+
case operator
|
49
|
+
when :EQ
|
50
|
+
l == r
|
51
|
+
when :NEQ
|
52
|
+
l != r
|
53
|
+
when :GT
|
54
|
+
l > r
|
55
|
+
when :GE
|
56
|
+
l >= r
|
57
|
+
when :LT
|
58
|
+
l < r
|
59
|
+
when :LE
|
60
|
+
l <= r
|
61
|
+
else
|
62
|
+
assert(false)
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class StringOpExp < BinaryOpExp
|
69
|
+
def eval(record)
|
70
|
+
l = left.get(record)
|
71
|
+
r = right.get(record)
|
72
|
+
case operator
|
73
|
+
when :EQ
|
74
|
+
l == r
|
75
|
+
when :NEQ
|
76
|
+
l != r
|
77
|
+
# when :GT
|
78
|
+
# l > r
|
79
|
+
# when :GE
|
80
|
+
# l >= r
|
81
|
+
# when :LT
|
82
|
+
# l < r
|
83
|
+
# when :LE
|
84
|
+
# l <= r
|
85
|
+
when :START_WITH
|
86
|
+
l.start_with?(r)
|
87
|
+
when :END_WITH
|
88
|
+
l.end_with?(r)
|
89
|
+
when :INCLUDE
|
90
|
+
l.include?(r)
|
91
|
+
else
|
92
|
+
assert(false)
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class RegexpOpExp < BinaryOpExp
|
99
|
+
attr_reader :pattern
|
100
|
+
|
101
|
+
def initialize(left, right, operator)
|
102
|
+
super(left, right, operator)
|
103
|
+
@pattern = Regexp.compile(right.val)
|
104
|
+
end
|
105
|
+
|
106
|
+
def eval(record)
|
107
|
+
l = left.get(record)
|
108
|
+
!!@pattern.match(l)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class NullOpExp < Exp
|
113
|
+
attr_reader :literal, :operator
|
114
|
+
|
115
|
+
# @param [ParserLiteral] literal
|
116
|
+
# @param [Symbol] operator
|
117
|
+
def initialize(literal, operator)
|
118
|
+
@literal = literal
|
119
|
+
@operator = operator
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Boolean]
|
123
|
+
def eval(record)
|
124
|
+
is_null = literal.get(record).nil?
|
125
|
+
case operator
|
126
|
+
when :EQ
|
127
|
+
is_null
|
128
|
+
when :NEQ
|
129
|
+
! is_null
|
130
|
+
else
|
131
|
+
assert(false)
|
132
|
+
false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class LogicalOpExp < Exp
|
138
|
+
attr_reader :left, :right, :operator
|
139
|
+
|
140
|
+
# @param [ParserLiteral] left
|
141
|
+
# @param [ParserLiteral] right
|
142
|
+
# @param [Symbol] operator
|
143
|
+
def initialize(left, right, operator)
|
144
|
+
@left = left
|
145
|
+
@right = right
|
146
|
+
@operator = operator
|
147
|
+
end
|
148
|
+
|
149
|
+
def eval(record)
|
150
|
+
l = left.eval(record)
|
151
|
+
r = right.eval(record)
|
152
|
+
case operator
|
153
|
+
when :OR
|
154
|
+
return l || r
|
155
|
+
when :AND
|
156
|
+
l && r
|
157
|
+
else
|
158
|
+
assert(false)
|
159
|
+
false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class NegateOpExp < Exp
|
165
|
+
attr_reader :exp
|
166
|
+
|
167
|
+
# @param [Exp] exp
|
168
|
+
def initialize(exp)
|
169
|
+
@exp = exp
|
170
|
+
end
|
171
|
+
|
172
|
+
def eval(record)
|
173
|
+
! exp.eval(record)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Literal Node of AST (Abstract Syntax Tree)
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Fluent
|
5
|
+
module FilterWhere
|
6
|
+
class Parser
|
7
|
+
class Literal
|
8
|
+
attr_reader :text
|
9
|
+
attr_reader :val
|
10
|
+
|
11
|
+
def get(record)
|
12
|
+
@val
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class BooleanLiteral < Literal
|
17
|
+
def initialize(text)
|
18
|
+
@text = text
|
19
|
+
if text.downcase == 'true'.freeze
|
20
|
+
@val = true
|
21
|
+
elsif text.downcase == 'false'.freeze
|
22
|
+
@val = false
|
23
|
+
else
|
24
|
+
raise ConfigError.new("\"%s\" is not a Boolean literal" % text)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class NumberLiteral < Literal
|
30
|
+
def initialize(text)
|
31
|
+
@text = text
|
32
|
+
@val = Float(text)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class StringLiteral < Literal
|
37
|
+
def initialize(text)
|
38
|
+
@text = text
|
39
|
+
@val = text
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class IdentifierLiteral < Literal
|
44
|
+
attr_reader :name
|
45
|
+
|
46
|
+
def initialize(name)
|
47
|
+
@name = name
|
48
|
+
end
|
49
|
+
|
50
|
+
def get(record)
|
51
|
+
record[@name]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'core'
|
2
|
+
|
3
|
+
module Fluent
|
4
|
+
class FilterWhere < Filter
|
5
|
+
Fluent::Plugin.register_filter('where', self)
|
6
|
+
|
7
|
+
include ::Fluent::FilterWhere::Core
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
# To support log_level option implemented by Fluentd v0.10.43
|
14
|
+
unless method_defined?(:log)
|
15
|
+
define_method("log") { $log }
|
16
|
+
end
|
17
|
+
|
18
|
+
def configure(conf)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def filter(tag, time, record)
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'core'
|
2
|
+
|
3
|
+
module Fluent
|
4
|
+
class Plugin::FilterWhere < Plugin::Filter
|
5
|
+
Fluent::Plugin.register_filter('where', self)
|
6
|
+
|
7
|
+
include ::Fluent::FilterWhere::Core
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure(conf)
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def filter(tag, time, record)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative 'helper'
|
3
|
+
require 'fluent/plugin/filter_where'
|
4
|
+
require 'benchmark'
|
5
|
+
Fluent::Test.setup
|
6
|
+
|
7
|
+
# setup
|
8
|
+
message = {'message' => "2013/01/13T07:02:11.124202 INFO GET /ping"}
|
9
|
+
time = event_time
|
10
|
+
|
11
|
+
string_eq = create_driver(%[where string = 'string'])
|
12
|
+
string_regexp = create_driver(%[where string REGEXP '.*string.*'])
|
13
|
+
|
14
|
+
# bench
|
15
|
+
n = 10000
|
16
|
+
Benchmark.bm(7) do |x|
|
17
|
+
x.report("string_eq") { string_eq.run { n.times { string_eq.feed(time, message) } } }
|
18
|
+
x.report("string_regexp") { string_regexp.run { n.times { string_regexp.feed(time, message) } } }
|
19
|
+
end
|
20
|
+
|
21
|
+
# user system total real
|
22
|
+
# string_eq 0.030000 0.000000 0.030000 ( 0.036840)
|
23
|
+
# string_regexp 0.040000 0.000000 0.040000 ( 0.043812)
|
data/test/helper.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/rr'
|
3
|
+
require 'timecop'
|
4
|
+
require 'fluent/log'
|
5
|
+
require 'fluent/test'
|
6
|
+
|
7
|
+
ROOT = File.dirname(__dir__)
|
8
|
+
|
9
|
+
unless defined?(Test::Unit::AssertionFailedError)
|
10
|
+
class Test::Unit::AssertionFailedError < StandardError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Reduce sleep period at
|
15
|
+
# https://github.com/fluent/fluentd/blob/a271b3ec76ab7cf89ebe4012aa5b3912333dbdb7/lib/fluent/test/base.rb#L81
|
16
|
+
module Fluent
|
17
|
+
module Test
|
18
|
+
class TestDriver
|
19
|
+
def run(num_waits = 10, &block)
|
20
|
+
@instance.start
|
21
|
+
begin
|
22
|
+
# wait until thread starts
|
23
|
+
# num_waits.times { sleep 0.05 }
|
24
|
+
sleep 0.05
|
25
|
+
return yield
|
26
|
+
ensure
|
27
|
+
@instance.shutdown
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
## v0.14
|
35
|
+
# d = create_driver(conf, syntax: :v1)
|
36
|
+
# d.run(default_tag: default_tag) do
|
37
|
+
# d.feed(time, record)
|
38
|
+
# end
|
39
|
+
# d.filtered_records
|
40
|
+
#
|
41
|
+
## v0.12
|
42
|
+
# d = create_driver(conf, use_v1, default_tag: tag)
|
43
|
+
# d.run do
|
44
|
+
# d.emit(record, time)
|
45
|
+
# end
|
46
|
+
# d.filtered # es
|
47
|
+
#
|
48
|
+
## Ours
|
49
|
+
# d = create_driver(conf, syntax: :v1, default_tag: tag)
|
50
|
+
# d.run do
|
51
|
+
# d.feed(time, record)
|
52
|
+
# end
|
53
|
+
# d.filtered_records
|
54
|
+
|
55
|
+
require 'fluent/version'
|
56
|
+
major, minor, patch = Fluent::VERSION.split('.').map(&:to_i)
|
57
|
+
if major > 0 || (major == 0 && minor >= 14)
|
58
|
+
require 'fluent/test/driver/filter'
|
59
|
+
require 'fluent/test/helpers'
|
60
|
+
include Fluent::Test::Helpers
|
61
|
+
|
62
|
+
class FilterWhereTestDriver < Fluent::Test::Driver::Filter
|
63
|
+
def initialize(klass, tag)
|
64
|
+
super(klass)
|
65
|
+
@tag = tag
|
66
|
+
end
|
67
|
+
|
68
|
+
def run(&block)
|
69
|
+
super(default_tag: @tag, &block)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_driver(conf, syntax: :v1, default_tag: 'tag')
|
74
|
+
FilterWhereTestDriver.new(Fluent::Plugin::FilterWhere, default_tag).configure(conf, syntax: syntax)
|
75
|
+
end
|
76
|
+
else # <= v0.12
|
77
|
+
def event_time(str)
|
78
|
+
Time.parse(str)
|
79
|
+
end
|
80
|
+
|
81
|
+
class FilterWhereTestDriver < Fluent::Test::FilterTestDriver
|
82
|
+
def configure(conf, syntax: :v1)
|
83
|
+
syntax == :v1 ? super(conf, true) : super(conf, false)
|
84
|
+
end
|
85
|
+
|
86
|
+
def feed(time, record)
|
87
|
+
emit(record, time)
|
88
|
+
end
|
89
|
+
|
90
|
+
def filtered_records
|
91
|
+
@filtered.map {|_time, record| record }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_driver(conf, syntax: :v1, default_tag: 'tag')
|
96
|
+
FilterWhereTestDriver.new(Fluent::Plugin::FilterWhere, default_tag).configure(conf, syntax: syntax)
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require 'time'
|
3
|
+
require 'fluent/plugin/filter_where'
|
4
|
+
|
5
|
+
Fluent::Test.setup
|
6
|
+
|
7
|
+
class FilterWhereTest < Test::Unit::TestCase
|
8
|
+
setup do
|
9
|
+
@time = event_time("2010-05-04 03:02:01")
|
10
|
+
Timecop.freeze(@time)
|
11
|
+
end
|
12
|
+
|
13
|
+
teardown do
|
14
|
+
Timecop.return
|
15
|
+
end
|
16
|
+
|
17
|
+
sub_test_case '#configure' do
|
18
|
+
test 'check default' do
|
19
|
+
assert_raise(Fluent::ConfigError) { create_driver(%[]) }
|
20
|
+
end
|
21
|
+
|
22
|
+
test "where" do
|
23
|
+
assert_nothing_raised { create_driver(%[where foo = 'foo']) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
sub_test_case '#filter' do
|
28
|
+
test "where" do
|
29
|
+
d = create_driver(%[where foo = 'foo'])
|
30
|
+
d.run do
|
31
|
+
d.feed(@time, {'foo' => 'foo'})
|
32
|
+
d.feed(@time, {'foo' => 'bar'})
|
33
|
+
end
|
34
|
+
assert { d.filtered_records == [{'foo' => 'foo'}] }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|