fluent-plugin-norikra 0.0.9 → 0.1.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/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
|