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.
@@ -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