fluent-plugin-filter_where 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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