fluent-plugin-norikra 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +174 -125
- data/example/blank.conf +2 -3
- data/example/blank.rb +15 -0
- data/example/blank2.conf +1 -1
- data/example/example1.conf +6 -7
- data/example/test1.conf +4 -5
- data/example/test1.rb +40 -0
- data/example/test2.conf +5 -6
- data/example/test_in_out.rb +48 -0
- data/fluent-plugin-norikra.gemspec +1 -1
- data/lib/fluent/plugin/in_norikra.rb +75 -0
- data/lib/fluent/plugin/norikra/config_section.rb +67 -0
- data/lib/fluent/plugin/norikra/fetch_request.rb +72 -0
- data/lib/fluent/plugin/norikra/input.rb +109 -0
- data/lib/fluent/plugin/norikra/output.rb +213 -0
- data/lib/fluent/plugin/norikra/query.rb +13 -0
- data/lib/fluent/plugin/norikra/query_generator.rb +61 -0
- data/lib/fluent/plugin/norikra/record_filter.rb +62 -0
- data/lib/fluent/plugin/norikra/target.rb +47 -0
- data/lib/fluent/plugin/norikra_target.rb +0 -246
- data/lib/fluent/plugin/out_norikra.rb +15 -364
- data/lib/fluent/plugin/out_norikra_filter.rb +172 -0
- data/test/helper.rb +2 -0
- data/test/plugin/test_in_norikra.rb +14 -0
- data/test/plugin/test_out_norikra.rb +5 -1
- data/test/plugin/test_out_norikra_filter.rb +15 -0
- data/test/test_config_section.rb +80 -25
- data/test/test_query.rb +6 -2
- data/test/test_query_generator.rb +9 -4
- data/test/test_record_filter.rb +2 -2
- data/test/test_target.rb +11 -13
- metadata +19 -2
@@ -0,0 +1,67 @@
|
|
1
|
+
module Fluent::NorikraPlugin
|
2
|
+
class ConfigSection
|
3
|
+
attr_accessor :target, :target_matcher, :auto_field, :filter_params, :field_definitions, :query_generators
|
4
|
+
|
5
|
+
def initialize(section, enable_auto_query=true)
|
6
|
+
@target = nil
|
7
|
+
@target_matcher = nil
|
8
|
+
if section.name == 'default'
|
9
|
+
# nil
|
10
|
+
elsif section.name == 'target'
|
11
|
+
# unescaped target name (tag style with dots)
|
12
|
+
@target = section.arg
|
13
|
+
@target_matcher = Fluent::GlobMatchPattern.new(section.arg)
|
14
|
+
else
|
15
|
+
raise ArgumentError, "invalid section for this class, #{section.name}: ConfigSection"
|
16
|
+
end
|
17
|
+
|
18
|
+
@auto_field = Fluent::Config.bool_value(section['auto_field'])
|
19
|
+
|
20
|
+
@filter_params = {
|
21
|
+
:include => section['include'],
|
22
|
+
:include_regexp => section['include_regexp'],
|
23
|
+
:exclude => section['exclude'],
|
24
|
+
:exclude_regexp => section['exclude_regexp']
|
25
|
+
}
|
26
|
+
@field_definitions = {
|
27
|
+
:string => (section['field_string'] || '').split(','),
|
28
|
+
:boolean => (section['field_boolean'] || '').split(','),
|
29
|
+
:integer => (section['field_integer'] || '').split(','),
|
30
|
+
:float => (section['field_float'] || '').split(','),
|
31
|
+
}
|
32
|
+
|
33
|
+
@query_generators = []
|
34
|
+
section.elements.each do |element|
|
35
|
+
if element.name == 'query' && enable_auto_query
|
36
|
+
opt = {}
|
37
|
+
if element.has_key?('fetch_interval')
|
38
|
+
opt['fetch_interval'] = Fluent::Config.time_value(element['fetch_interval'])
|
39
|
+
end
|
40
|
+
@query_generators.push(QueryGenerator.new(element['name'], element['group'], element['expression'], element['tag'], opt))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def +(other)
|
46
|
+
if other.nil?
|
47
|
+
other = self.class.new(Fluent::Config::Element.new('target', 'dummy', {}, []))
|
48
|
+
end
|
49
|
+
r = self.class.new(Fluent::Config::Element.new('target', (other.target ? other.target : self.target), {}, []))
|
50
|
+
r.auto_field = (other.auto_field.nil? ? self.auto_field : other.auto_field)
|
51
|
+
|
52
|
+
others_filter = {}
|
53
|
+
other.filter_params.keys.each do |k|
|
54
|
+
others_filter[k] = other.filter_params[k] if other.filter_params[k]
|
55
|
+
end
|
56
|
+
r.filter_params = self.filter_params.merge(others_filter)
|
57
|
+
r.field_definitions = {
|
58
|
+
:string => self.field_definitions[:string] + other.field_definitions[:string],
|
59
|
+
:boolean => self.field_definitions[:boolean] + other.field_definitions[:boolean],
|
60
|
+
:integer => self.field_definitions[:integer] + other.field_definitions[:integer],
|
61
|
+
:float => self.field_definitions[:float] + other.field_definitions[:float],
|
62
|
+
}
|
63
|
+
r.query_generators = self.query_generators + other.query_generators
|
64
|
+
r
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Fluent::NorikraPlugin
|
2
|
+
class FetchRequest
|
3
|
+
METHODS = [:event, :sweep]
|
4
|
+
TAG_TYPES = ['query_name', 'field', 'string']
|
5
|
+
|
6
|
+
attr_accessor :method, :target, :interval, :tag_generator, :tag_prefix
|
7
|
+
attr_accessor :time
|
8
|
+
|
9
|
+
def initialize(method, target, interval, tag_type, tag_arg, tag_prefix)
|
10
|
+
raise ArgumentError, "unknown method '#{method}'" unless METHODS.include?(method.to_sym)
|
11
|
+
|
12
|
+
@method = method.to_sym
|
13
|
+
@target = target
|
14
|
+
@interval = interval.to_i
|
15
|
+
|
16
|
+
raise ArgumentError, "unknown tag type specifier '#{tag_type}'" unless TAG_TYPES.include?(tag_type.to_s)
|
17
|
+
raw_tag_prefix = tag_prefix.to_s
|
18
|
+
if (! raw_tag_prefix.empty?) && (! raw_tag_prefix.end_with?('.')) # tag_prefix specified, and ends without dot
|
19
|
+
raw_tag_prefix += '.'
|
20
|
+
end
|
21
|
+
|
22
|
+
@tag_generator = case tag_type.to_s
|
23
|
+
when 'query_name' then lambda{|query_name,record| raw_tag_prefix + query_name}
|
24
|
+
when 'field' then lambda{|query_name,record| raw_tag_prefix + record[tag_arg]}
|
25
|
+
when 'string' then lambda{|query_name,record| raw_tag_prefix + tag_arg}
|
26
|
+
else
|
27
|
+
raise "bug"
|
28
|
+
end
|
29
|
+
@time = Time.now + 1 # should be fetched soon ( 1sec later )
|
30
|
+
end
|
31
|
+
|
32
|
+
def <=>(other)
|
33
|
+
self.time <=> other.time
|
34
|
+
end
|
35
|
+
|
36
|
+
def next!
|
37
|
+
@time = Time.now + @interval
|
38
|
+
end
|
39
|
+
|
40
|
+
# returns hash: { tag => [[time, record], ...], ... }
|
41
|
+
def fetch(client)
|
42
|
+
# events { query_name => [[time, record], ...], ... }
|
43
|
+
events = case @method
|
44
|
+
when :event then event(client)
|
45
|
+
when :sweep then sweep(client)
|
46
|
+
else
|
47
|
+
raise "BUG: unknown method: #{@method}"
|
48
|
+
end
|
49
|
+
|
50
|
+
output = {}
|
51
|
+
|
52
|
+
events.keys.each do |query_name|
|
53
|
+
events[query_name].each do |time, record|
|
54
|
+
tag = @tag_generator.call(query_name, record)
|
55
|
+
output[tag] ||= []
|
56
|
+
output[tag] << [time, record]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
output
|
61
|
+
end
|
62
|
+
|
63
|
+
def event(client)
|
64
|
+
events = client.event(@target) # [[time(int from epoch), event], ...]
|
65
|
+
{@target => events}
|
66
|
+
end
|
67
|
+
|
68
|
+
def sweep(client)
|
69
|
+
client.sweep(@target) # {query_name => event_array, ...}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require_relative 'fetch_request'
|
2
|
+
|
3
|
+
module Fluent::NorikraPlugin
|
4
|
+
module InputMixin
|
5
|
+
# <fetch>
|
6
|
+
# method event
|
7
|
+
# target QUERY_NAME
|
8
|
+
# interval 5s
|
9
|
+
# tag query_name
|
10
|
+
# # tag field FIELDNAME
|
11
|
+
# # tag string FIXED_STRING
|
12
|
+
# tag_prefix norikra.event # actual tag: norikra.event.QUERYNAME
|
13
|
+
# </fetch>
|
14
|
+
# <fetch>
|
15
|
+
# method sweep
|
16
|
+
# target QUERY_GROUP # or unspecified => default
|
17
|
+
# interval 60s
|
18
|
+
# tag field group_by_key
|
19
|
+
# tag_prefix norikra.query
|
20
|
+
# </fetch>
|
21
|
+
|
22
|
+
def setup_input(conf)
|
23
|
+
@fetch_queue = []
|
24
|
+
|
25
|
+
conf.elements.each do |e|
|
26
|
+
next unless e.name == 'fetch'
|
27
|
+
method = e['method']
|
28
|
+
target = e['target']
|
29
|
+
interval_str = e['interval']
|
30
|
+
tag = e['tag']
|
31
|
+
unless method && interval_str && tag
|
32
|
+
raise Fluent::ConfigError, "<fetch> must be specified with method/interval/tag"
|
33
|
+
end
|
34
|
+
if method == 'event' and target.nil?
|
35
|
+
raise Fluent::ConfigError, "<fetch> method 'event' requires 'target' for fetch target query name"
|
36
|
+
end
|
37
|
+
|
38
|
+
interval = Fluent::Config.time_value(interval_str)
|
39
|
+
tag_type, tag_arg = tag.split(/ /, 2)
|
40
|
+
req = FetchRequest.new(method, target, interval, tag_type, tag_arg, e['tag_prefix'])
|
41
|
+
|
42
|
+
@fetch_queue << req
|
43
|
+
end
|
44
|
+
|
45
|
+
@fetch_queue_mutex = Mutex.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def start_input
|
49
|
+
@fetch_worker_running = true
|
50
|
+
@fetch_thread = Thread.new(&method(:fetch_worker))
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop_input
|
54
|
+
@fetch_worker_running = false
|
55
|
+
end
|
56
|
+
|
57
|
+
def shutdown_input
|
58
|
+
# @fetch_thread.kill
|
59
|
+
@fetch_thread.join
|
60
|
+
end
|
61
|
+
|
62
|
+
def insert_fetch_queue(request)
|
63
|
+
@fetch_queue_mutex.synchronize do
|
64
|
+
request.next! if request.time < Time.now
|
65
|
+
# if @fetch_queue.size > 0
|
66
|
+
# next_pos = @fetch_queue.bsearch{|req| req.time > request.time}
|
67
|
+
# @fetch_queue.insert(next_pos, request)
|
68
|
+
# else
|
69
|
+
# @fetch_queue.push(request)
|
70
|
+
# end
|
71
|
+
@fetch_queue.push(request)
|
72
|
+
@fetch_queue.sort!
|
73
|
+
end
|
74
|
+
rescue => e
|
75
|
+
$log.error "unknown log encountered", :error_class => e.class, :message => e.message
|
76
|
+
end
|
77
|
+
|
78
|
+
def fetch_worker
|
79
|
+
while sleep(1)
|
80
|
+
break unless @fetch_worker_running
|
81
|
+
next unless fetchable?
|
82
|
+
next if @fetch_queue.first.nil? || @fetch_queue.first.time > Time.now
|
83
|
+
|
84
|
+
now = Time.now
|
85
|
+
while @fetch_queue.first.time <= now
|
86
|
+
req = @fetch_queue.shift
|
87
|
+
|
88
|
+
begin
|
89
|
+
data = req.fetch(client())
|
90
|
+
rescue => e
|
91
|
+
$log.error "failed to fetch", :norikra => "#{@host}:#{@port}", :method => req.method, :target => req.target, :error => e.class, :message => e.message
|
92
|
+
end
|
93
|
+
|
94
|
+
data.each do |tag, event_array|
|
95
|
+
event_array.each do |time,event|
|
96
|
+
begin
|
97
|
+
Fluent::Engine.emit(tag, time, event)
|
98
|
+
rescue => e
|
99
|
+
$log.error "failed to emit event from norikra query", :norikra => "#{@host}:#{@port}", :error => e.class, :message => e.message, :tag => tag, :record => event
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
insert_fetch_queue(req)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require_relative 'config_section'
|
2
|
+
require_relative 'query'
|
3
|
+
require_relative 'query_generator'
|
4
|
+
require_relative 'record_filter'
|
5
|
+
require_relative 'target'
|
6
|
+
|
7
|
+
require_relative 'fetch_request'
|
8
|
+
|
9
|
+
module Fluent::NorikraPlugin
|
10
|
+
module OutputMixin
|
11
|
+
def setup_output(conf, enable_auto_query)
|
12
|
+
@enable_auto_query = enable_auto_query
|
13
|
+
|
14
|
+
@target_generator = case
|
15
|
+
when @target_string
|
16
|
+
lambda {|tag,record| @target_string}
|
17
|
+
when @target_map_key
|
18
|
+
lambda {|tag,record| record[@target_map_key]}
|
19
|
+
when @target_map_tag
|
20
|
+
lambda {|tag,record| tag.gsub(/^#{@remove_tag_prefix}(\.)?/, '')}
|
21
|
+
else
|
22
|
+
raise Fluent::ConfigError, "no one way specified to decide target"
|
23
|
+
end
|
24
|
+
|
25
|
+
# target map already prepared (opened, and related queries registered)
|
26
|
+
@target_map = {} # 'target' => instance of Fluent::NorikraPlugin::Target
|
27
|
+
|
28
|
+
# for conversion from query_name to tag
|
29
|
+
@query_map = {} # 'query_name' => instance of Fluent::NorikraPlugin::Query
|
30
|
+
|
31
|
+
@default_target = ConfigSection.new(Fluent::Config::Element.new('default', nil, {}, []), @enable_auto_query)
|
32
|
+
@config_targets = {}
|
33
|
+
|
34
|
+
conf.elements.each do |element|
|
35
|
+
case element.name
|
36
|
+
when 'default'
|
37
|
+
@default_target = ConfigSection.new(element, @enable_auto_query)
|
38
|
+
when 'target'
|
39
|
+
c = ConfigSection.new(element, @enable_auto_query)
|
40
|
+
@config_targets[c.target] = c
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@target_mutex = Mutex.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def start_output
|
48
|
+
@register_worker_running = true
|
49
|
+
@register_queue = []
|
50
|
+
@registered_targets = {}
|
51
|
+
@register_thread = Thread.new(&method(:register_worker))
|
52
|
+
end
|
53
|
+
|
54
|
+
def stop_output
|
55
|
+
@register_worker_running = false
|
56
|
+
end
|
57
|
+
|
58
|
+
def shutdown_output
|
59
|
+
# @register_thread.kill
|
60
|
+
@register_thread.join
|
61
|
+
end
|
62
|
+
|
63
|
+
def prepared?(target_names)
|
64
|
+
fetchable? && target_names.reduce(true){|r,t| r && @target_map.values.any?{|target| target.escaped_name == t}}
|
65
|
+
end
|
66
|
+
|
67
|
+
def fetch_event_registration(query)
|
68
|
+
return if query.tag.nil? || query.tag.empty?
|
69
|
+
req = FetchRequest.new(:event, query.name, query.interval, 'string', query.tag, nil)
|
70
|
+
insert_fetch_queue(req)
|
71
|
+
end
|
72
|
+
|
73
|
+
def register_worker
|
74
|
+
while sleep(0.25)
|
75
|
+
break unless @register_worker_running
|
76
|
+
next unless fetchable?
|
77
|
+
|
78
|
+
c = client()
|
79
|
+
|
80
|
+
targets = @register_queue.shift(10)
|
81
|
+
targets.each do |t|
|
82
|
+
next if @target_map[t.name]
|
83
|
+
|
84
|
+
$log.debug "Preparing norikra target #{t.name} on #{@host}:#{@port}"
|
85
|
+
if prepare_target(c, t)
|
86
|
+
$log.debug "success to prepare target #{t.name} on #{@host}:#{@port}"
|
87
|
+
|
88
|
+
if @enable_auto_query
|
89
|
+
raise "bug" unless self.respond_to?(:insert_fetch_queue)
|
90
|
+
|
91
|
+
t.queries.each do |query|
|
92
|
+
@query_map[query.name] = query
|
93
|
+
fetch_event_registration(query)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
@target_map[t.name] = t
|
97
|
+
@registered_targets.delete(t.name)
|
98
|
+
else
|
99
|
+
$log.error "Failed to prepare norikra data for target:#{t.name}"
|
100
|
+
@norikra_started.push(t)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def prepare_target(client, target)
|
107
|
+
# target open and reserve fields
|
108
|
+
$log.debug "Going to prepare about target"
|
109
|
+
begin
|
110
|
+
unless client.targets.include?(target.escaped_name)
|
111
|
+
$log.debug "opening target #{target.escaped_name}"
|
112
|
+
client.open(target.escaped_name, target.reserve_fields, target.auto_field)
|
113
|
+
$log.debug "opening target #{target.escaped_name}, done."
|
114
|
+
end
|
115
|
+
|
116
|
+
reserving = target.reserve_fields
|
117
|
+
reserved = []
|
118
|
+
client.fields(target.escaped_name).each do |field|
|
119
|
+
if reserving[field['name']]
|
120
|
+
reserved.push(field['name'])
|
121
|
+
if reserving[field['name']] != field['type']
|
122
|
+
$log.warn "field type mismatch, reserving:#{reserving[field['name']]} but reserved:#{field['type']}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
reserving.each do |fieldname,type|
|
128
|
+
client.reserve(target.escaped_name, fieldname, type) unless reserved.include?(fieldname)
|
129
|
+
end
|
130
|
+
rescue => e
|
131
|
+
$log.error "failed to prepare target:#{target.escaped_name}", :norikra => "#{@host}:#{@port}", :error => e.class, :message => e.message
|
132
|
+
return false
|
133
|
+
end
|
134
|
+
|
135
|
+
# query registration
|
136
|
+
begin
|
137
|
+
registered = Hash[client.queries.map{|q| [q['name'], q['expression']]}]
|
138
|
+
target.queries.each do |query|
|
139
|
+
if registered.has_key?(query.name) # query already registered
|
140
|
+
if registered[query.name] != query.expression
|
141
|
+
$log.warn "query name and expression mismatch, check norikra server status. target query name:#{query.name}"
|
142
|
+
end
|
143
|
+
next
|
144
|
+
end
|
145
|
+
client.register(query.name, query.group, query.expression)
|
146
|
+
|
147
|
+
@query_map[query.name] = query
|
148
|
+
fetch_event_registration(query)
|
149
|
+
end
|
150
|
+
rescue => e
|
151
|
+
$log.warn "failed to register query", :norikra => "#{@host}:#{@port}", :error => e.class, :message => e.message
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def format_stream(tag, es)
|
156
|
+
tobe_registered_target_names = []
|
157
|
+
|
158
|
+
out = ''
|
159
|
+
|
160
|
+
es.each do |time,record|
|
161
|
+
target = @target_generator.call(tag, record)
|
162
|
+
|
163
|
+
tgt = @target_mutex.synchronize do
|
164
|
+
t = @target_map[target]
|
165
|
+
unless t
|
166
|
+
unless tobe_registered_target_names.include?(target)
|
167
|
+
conf = @config_targets[target]
|
168
|
+
unless conf
|
169
|
+
@config_targets.values.each do |c|
|
170
|
+
if c.target_matcher.match(target)
|
171
|
+
conf = c
|
172
|
+
break
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
t = Target.new(target, @default_target + conf)
|
177
|
+
@registered_targets[target] = t
|
178
|
+
@register_queue.push(t)
|
179
|
+
tobe_registered_target_names.push(target)
|
180
|
+
end
|
181
|
+
t = @registered_targets[target]
|
182
|
+
end
|
183
|
+
t
|
184
|
+
end
|
185
|
+
|
186
|
+
event = tgt.filter(record)
|
187
|
+
|
188
|
+
out << [tgt.escaped_name,event].to_msgpack
|
189
|
+
end
|
190
|
+
|
191
|
+
out
|
192
|
+
end
|
193
|
+
|
194
|
+
def write(chunk)
|
195
|
+
events_map = {} # target => [event]
|
196
|
+
chunk.msgpack_each do |target, event|
|
197
|
+
events_map[target] ||= []
|
198
|
+
events_map[target].push(event)
|
199
|
+
end
|
200
|
+
|
201
|
+
unless prepared?(events_map.keys)
|
202
|
+
raise RuntimeError, "norikra server is not ready for this targets: #{events_map.keys.join(',')}"
|
203
|
+
end
|
204
|
+
|
205
|
+
c = client()
|
206
|
+
|
207
|
+
events_map.each do |target, events|
|
208
|
+
c.send(target, events)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Fluent::NorikraPlugin
|
2
|
+
class Query
|
3
|
+
attr_accessor :name, :group, :expression, :tag, :interval
|
4
|
+
|
5
|
+
def initialize(name, group, expression, tag, interval)
|
6
|
+
@name = name
|
7
|
+
@group = group
|
8
|
+
@expression = expression
|
9
|
+
@tag = tag
|
10
|
+
@interval = interval
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Fluent::NorikraPlugin
|
2
|
+
class QueryGenerator
|
3
|
+
attr_reader :fetch_interval
|
4
|
+
|
5
|
+
def initialize(name_template, group, expression_template, tag_template, opts={})
|
6
|
+
@name_template = name_template || ''
|
7
|
+
@group = group
|
8
|
+
@expression_template = expression_template || ''
|
9
|
+
@tag_template = tag_template || ''
|
10
|
+
if @name_template.empty? || @expression_template.empty?
|
11
|
+
raise Fluent::ConfigError, "query's name/expression must be specified"
|
12
|
+
end
|
13
|
+
@fetch_interval = case
|
14
|
+
when opts['fetch_interval']
|
15
|
+
Fluent::Config.time_value(opts['fetch_interval'])
|
16
|
+
when @expression_template =~ /\.win:time_batch\(([^\)]+)\)/
|
17
|
+
y,mon,w,d,h,m,s,msec = self.class.parse_time_period($1)
|
18
|
+
(h * 3600 + m * 60 + s) / 5
|
19
|
+
else
|
20
|
+
60
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate(name, escaped)
|
25
|
+
Fluent::NorikraPlugin::Query.new(
|
26
|
+
self.class.replace_target(name, @name_template),
|
27
|
+
@group,
|
28
|
+
self.class.replace_target(escaped, @expression_template),
|
29
|
+
self.class.replace_target(name, @tag_template),
|
30
|
+
@fetch_interval
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.replace_target(t, str)
|
35
|
+
str.gsub('${target}', t)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.parse_time_period(string)
|
39
|
+
#### http://esper.codehaus.org/esper-4.9.0/doc/reference/en-US/html/epl_clauses.html#epl-syntax-time-periods
|
40
|
+
# time-period : [year-part] [month-part] [week-part] [day-part] [hour-part] [minute-part] [seconds-part] [milliseconds-part]
|
41
|
+
# year-part : (number|variable_name) ("years" | "year")
|
42
|
+
# month-part : (number|variable_name) ("months" | "month")
|
43
|
+
# week-part : (number|variable_name) ("weeks" | "week")
|
44
|
+
# day-part : (number|variable_name) ("days" | "day")
|
45
|
+
# hour-part : (number|variable_name) ("hours" | "hour")
|
46
|
+
# minute-part : (number|variable_name) ("minutes" | "minute" | "min")
|
47
|
+
# seconds-part : (number|variable_name) ("seconds" | "second" | "sec")
|
48
|
+
# milliseconds-part : (number|variable_name) ("milliseconds" | "millisecond" | "msec")
|
49
|
+
m = /^\s*(\d+ years?)? ?(\d+ months?)? ?(\d+ weeks?)? ?(\d+ days?)? ?(\d+ hours?)? ?(\d+ (?:min|minute|minutes))? ?(\d+ (?:sec|second|seconds))? ?(\d+ (?:msec|millisecond|milliseconds))?/.match(string)
|
50
|
+
years = (m[1] || '').split(' ',2).first.to_i
|
51
|
+
months = (m[2] || '').split(' ',2).first.to_i
|
52
|
+
weeks = (m[3] || '').split(' ',2).first.to_i
|
53
|
+
days = (m[4] || '').split(' ',2).first.to_i
|
54
|
+
hours = (m[5] || '').split(' ',2).first.to_i
|
55
|
+
minutes = (m[6] || '').split(' ',2).first.to_i
|
56
|
+
seconds = (m[7] || '').split(' ',2).first.to_i
|
57
|
+
msecs = (m[8] || '').split(' ',2).first.to_i
|
58
|
+
return [years, months, weeks, days, hours, minutes, seconds, msecs]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Fluent::NorikraPlugin
|
2
|
+
class RecordFilter
|
3
|
+
attr_reader :default_policy, :include_fields, :include_regexp, :exclude_fields, :exclude_regexp
|
4
|
+
|
5
|
+
def initialize(include='', include_regexp='', exclude='', exclude_regexp='')
|
6
|
+
include ||= ''
|
7
|
+
include_regexp ||= ''
|
8
|
+
exclude ||= ''
|
9
|
+
exclude_regexp ||= ''
|
10
|
+
|
11
|
+
@default_policy = nil
|
12
|
+
if include == '*' && exclude == '*'
|
13
|
+
raise Fluent::ConfigError, "invalid configuration, both of 'include' and 'exclude' are '*'"
|
14
|
+
end
|
15
|
+
if include.empty? && include_regexp.empty? && exclude.empty? && exclude_regexp.empty? # assuming "include *"
|
16
|
+
@default_policy = :include
|
17
|
+
elsif exclude.empty? && exclude_regexp.empty? || exclude == '*' # assuming "exclude *"
|
18
|
+
@default_policy = :exclude
|
19
|
+
elsif include.empty? && include_regexp.empty? || include == '*' # assuming "include *"
|
20
|
+
@default_policy = :include
|
21
|
+
else
|
22
|
+
raise Fluent::ConfigError, "unknown default policy. specify 'include *' or 'exclude *'"
|
23
|
+
end
|
24
|
+
|
25
|
+
@include_fields = nil
|
26
|
+
@include_regexp = nil
|
27
|
+
@exclude_fields = nil
|
28
|
+
@exclude_regexp = nil
|
29
|
+
|
30
|
+
if @default_policy == :exclude
|
31
|
+
@include_fields = include.split(',')
|
32
|
+
@include_regexp = Regexp.new(include_regexp) unless include_regexp.empty?
|
33
|
+
if @include_fields.empty? && @include_regexp.nil?
|
34
|
+
raise Fluent::ConfigError, "no one fields specified. specify 'include' or 'include_regexp'"
|
35
|
+
end
|
36
|
+
else
|
37
|
+
@exclude_fields = exclude.split(',')
|
38
|
+
@exclude_regexp = Regexp.new(exclude_regexp) unless exclude_regexp.empty?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def filter(record)
|
43
|
+
if @default_policy == :include
|
44
|
+
if @exclude_fields.empty? && @exclude_regexp.nil?
|
45
|
+
record
|
46
|
+
else
|
47
|
+
record = record.dup
|
48
|
+
record.keys.each do |f|
|
49
|
+
record.delete(f) if @exclude_fields.include?(f) || @exclude_regexp && @exclude_regexp.match(f)
|
50
|
+
end
|
51
|
+
record
|
52
|
+
end
|
53
|
+
else # default policy exclude
|
54
|
+
data = {}
|
55
|
+
record.keys.each do |f|
|
56
|
+
data[f] = record[f] if @include_fields.include?(f) || @include_regexp && @include_regexp.match(f)
|
57
|
+
end
|
58
|
+
data
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Fluent::NorikraPlugin
|
2
|
+
class Target
|
3
|
+
attr_accessor :name, :auto_field, :fields, :queries
|
4
|
+
attr_reader :escaped_name
|
5
|
+
|
6
|
+
def self.escape(src)
|
7
|
+
if src.nil? || src.empty?
|
8
|
+
return 'FluentdGenerated'
|
9
|
+
end
|
10
|
+
|
11
|
+
dst = src.gsub(/[^_a-zA-Z0-9]/, '_')
|
12
|
+
unless dst =~ /^[a-zA-Z]([_a-zA-Z0-9]*[a-zA-Z0-9])?$/
|
13
|
+
unless dst =~ /^[a-zA-Z]/
|
14
|
+
dst = 'Fluentd' + dst
|
15
|
+
end
|
16
|
+
unless dst =~ /[a-zA-Z0-9]$/
|
17
|
+
dst = dst + 'Generated'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
dst
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(target, config)
|
24
|
+
@name = target
|
25
|
+
@escaped_name = self.class.escape(@name)
|
26
|
+
@auto_field = config.auto_field.nil? ? true : config.auto_field
|
27
|
+
|
28
|
+
@filter = RecordFilter.new(*([:include, :include_regexp, :exclude, :exclude_regexp].map{|s| config.filter_params[s]}))
|
29
|
+
@fields = config.field_definitions
|
30
|
+
@queries = config.query_generators.map{|g| g.generate(@name, @escaped_name)}
|
31
|
+
end
|
32
|
+
|
33
|
+
def filter(record)
|
34
|
+
@filter.filter(record)
|
35
|
+
end
|
36
|
+
|
37
|
+
def reserve_fields
|
38
|
+
f = {}
|
39
|
+
@fields.keys.each do |type_sym|
|
40
|
+
@fields[type_sym].each do |fieldname|
|
41
|
+
f[fieldname] = type_sym.to_s
|
42
|
+
end
|
43
|
+
end
|
44
|
+
f
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|