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.
@@ -1,246 +0,0 @@
1
- class Fluent::NorikraOutput
2
- class Query
3
- attr_accessor :name, :expression, :tag, :interval
4
-
5
- def initialize(name, expression, tag, interval)
6
- @name = name
7
- @expression = expression
8
- @tag = tag
9
- @interval = interval
10
- end
11
- end
12
-
13
- class QueryGenerator
14
- attr_reader :fetch_interval
15
-
16
- def initialize(name_template, expression_template, tag_template, opts={})
17
- @name_template = name_template || ''
18
- @expression_template = expression_template || ''
19
- @tag_template = tag_template || ''
20
- if @name_template.empty? || @expression_template.empty?
21
- raise Fluent::ConfigError, "query's name/expression must be specified"
22
- end
23
- @fetch_interval = case
24
- when opts['fetch_interval']
25
- Fluent::Config.time_value(opts['fetch_interval'])
26
- when @expression_template =~ /\.win:time_batch\(([^\)]+)\)/
27
- y,mon,w,d,h,m,s,msec = self.class.parse_time_period($1)
28
- (h * 3600 + m * 60 + s) / 5
29
- else
30
- 60
31
- end
32
- end
33
-
34
- def generate(name, escaped)
35
- Fluent::NorikraOutput::Query.new(
36
- self.class.replace_target(name, @name_template),
37
- self.class.replace_target(escaped, @expression_template),
38
- self.class.replace_target(name, @tag_template),
39
- @fetch_interval
40
- )
41
- end
42
-
43
- def self.replace_target(t, str)
44
- str.gsub('${target}', t)
45
- end
46
-
47
- def self.parse_time_period(string)
48
- #### http://esper.codehaus.org/esper-4.9.0/doc/reference/en-US/html/epl_clauses.html#epl-syntax-time-periods
49
- # time-period : [year-part] [month-part] [week-part] [day-part] [hour-part] [minute-part] [seconds-part] [milliseconds-part]
50
- # year-part : (number|variable_name) ("years" | "year")
51
- # month-part : (number|variable_name) ("months" | "month")
52
- # week-part : (number|variable_name) ("weeks" | "week")
53
- # day-part : (number|variable_name) ("days" | "day")
54
- # hour-part : (number|variable_name) ("hours" | "hour")
55
- # minute-part : (number|variable_name) ("minutes" | "minute" | "min")
56
- # seconds-part : (number|variable_name) ("seconds" | "second" | "sec")
57
- # milliseconds-part : (number|variable_name) ("milliseconds" | "millisecond" | "msec")
58
- 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)
59
- years = (m[1] || '').split(' ',2).first.to_i
60
- months = (m[2] || '').split(' ',2).first.to_i
61
- weeks = (m[3] || '').split(' ',2).first.to_i
62
- days = (m[4] || '').split(' ',2).first.to_i
63
- hours = (m[5] || '').split(' ',2).first.to_i
64
- minutes = (m[6] || '').split(' ',2).first.to_i
65
- seconds = (m[7] || '').split(' ',2).first.to_i
66
- msecs = (m[8] || '').split(' ',2).first.to_i
67
- return [years, months, weeks, days, hours, minutes, seconds, msecs]
68
- end
69
- end
70
-
71
- class RecordFilter
72
- attr_reader :default_policy, :include_fields, :include_regexp, :exclude_fields, :exclude_regexp
73
-
74
- def initialize(include='', include_regexp='', exclude='', exclude_regexp='')
75
- include ||= ''
76
- include_regexp ||= ''
77
- exclude ||= ''
78
- exclude_regexp ||= ''
79
-
80
- @default_policy = nil
81
- if include == '*' && exclude == '*'
82
- raise Fluent::ConfigError, "invalid configuration, both of 'include' and 'exclude' are '*'"
83
- end
84
- if include.empty? && include_regexp.empty? && exclude.empty? && exclude_regexp.empty? # assuming "include *"
85
- @default_policy = :include
86
- elsif exclude.empty? && exclude_regexp.empty? || exclude == '*' # assuming "exclude *"
87
- @default_policy = :exclude
88
- elsif include.empty? && include_regexp.empty? || include == '*' # assuming "include *"
89
- @default_policy = :include
90
- else
91
- raise Fluent::ConfigError, "unknown default policy. specify 'include *' or 'exclude *'"
92
- end
93
-
94
- @include_fields = nil
95
- @include_regexp = nil
96
- @exclude_fields = nil
97
- @exclude_regexp = nil
98
-
99
- if @default_policy == :exclude
100
- @include_fields = include.split(',')
101
- @include_regexp = Regexp.new(include_regexp) unless include_regexp.empty?
102
- if @include_fields.empty? && @include_regexp.nil?
103
- raise Fluent::ConfigError, "no one fields specified. specify 'include' or 'include_regexp'"
104
- end
105
- else
106
- @exclude_fields = exclude.split(',')
107
- @exclude_regexp = Regexp.new(exclude_regexp) unless exclude_regexp.empty?
108
- end
109
- end
110
-
111
- def filter(record)
112
- if @default_policy == :include
113
- if @exclude_fields.empty? && @exclude_regexp.nil?
114
- record
115
- else
116
- record = record.dup
117
- record.keys.each do |f|
118
- record.delete(f) if @exclude_fields.include?(f) || @exclude_regexp && @exclude_regexp.match(f)
119
- end
120
- record
121
- end
122
- else # default policy exclude
123
- data = {}
124
- record.keys.each do |f|
125
- data[f] = record[f] if @include_fields.include?(f) || @include_regexp && @include_regexp.match(f)
126
- end
127
- data
128
- end
129
- end
130
- end
131
-
132
- class ConfigSection
133
- attr_accessor :target, :target_matcher, :auto_field, :filter_params, :field_definitions, :query_generators
134
-
135
- def initialize(section)
136
- @target = nil
137
- @target_matcher = nil
138
- if section.name == 'default'
139
- # nil
140
- elsif section.name == 'target'
141
- # unescaped target name (tag style with dots)
142
- @target = section.arg
143
- @target_matcher = Fluent::GlobMatchPattern.new(section.arg)
144
- else
145
- raise ArgumentError, "invalid section for this class, #{section.name}: ConfigSection"
146
- end
147
-
148
- @auto_field = Fluent::Config.bool_value(section['auto_field'])
149
-
150
- @filter_params = {
151
- :include => section['include'],
152
- :include_regexp => section['include_regexp'],
153
- :exclude => section['exclude'],
154
- :exclude_regexp => section['exclude_regexp']
155
- }
156
- @field_definitions = {
157
- :string => (section['field_string'] || '').split(','),
158
- :boolean => (section['field_boolean'] || '').split(','),
159
- :int => (section['field_int'] || '').split(','),
160
- :long => (section['field_long'] || '').split(','),
161
- :float => (section['field_float'] || '').split(','),
162
- :double => (section['field_double'] || '').split(',')
163
- }
164
- @query_generators = []
165
- section.elements.each do |element|
166
- if element.name == 'query'
167
- opt = {}
168
- if element.has_key?('fetch_interval')
169
- opt['fetch_interval'] = element['fetch_interval'].to_i
170
- end
171
- @query_generators.push(QueryGenerator.new(element['name'], element['expression'], element['tag'], opt))
172
- end
173
- end
174
- end
175
-
176
- def +(other)
177
- if other.nil?
178
- other = self.class.new(Fluent::Config::Element.new('target', 'dummy', {}, []))
179
- end
180
- r = self.class.new(Fluent::Config::Element.new('target', (other.target ? other.target : self.target), {}, []))
181
- r.auto_field = (other.auto_field.nil? ? self.auto_field : other.auto_field)
182
-
183
- others_filter = {}
184
- other.filter_params.keys.each do |k|
185
- others_filter[k] = other.filter_params[k] if other.filter_params[k]
186
- end
187
- r.filter_params = self.filter_params.merge(others_filter)
188
- r.field_definitions = {
189
- :string => self.field_definitions[:string] + other.field_definitions[:string],
190
- :boolean => self.field_definitions[:boolean] + other.field_definitions[:boolean],
191
- :int => self.field_definitions[:int] + other.field_definitions[:int],
192
- :long => self.field_definitions[:long] + other.field_definitions[:long],
193
- :float => self.field_definitions[:float] + other.field_definitions[:float],
194
- :double => self.field_definitions[:double] + other.field_definitions[:double]
195
- }
196
- r.query_generators = self.query_generators + other.query_generators
197
- r
198
- end
199
- end
200
-
201
- class Target
202
- attr_accessor :name, :auto_field, :fields, :queries
203
- attr_reader :escaped_name
204
-
205
- def self.escape(src)
206
- if src.nil? || src.empty?
207
- return 'FluentdGenerated'
208
- end
209
-
210
- dst = src.gsub(/[^_a-zA-Z0-9]/, '_')
211
- unless dst =~ /^[a-zA-Z]([_a-zA-Z0-9]*[a-zA-Z0-9])?$/
212
- unless dst =~ /^[a-zA-Z]/
213
- dst = 'Fluentd' + dst
214
- end
215
- unless dst =~ /[a-zA-Z0-9]$/
216
- dst = dst + 'Generated'
217
- end
218
- end
219
- dst
220
- end
221
-
222
- def initialize(target, config)
223
- @name = target
224
- @escaped_name = self.class.escape(@name)
225
- @auto_field = config.auto_field.nil? ? true : config.auto_field
226
-
227
- @filter = RecordFilter.new(*([:include, :include_regexp, :exclude, :exclude_regexp].map{|s| config.filter_params[s]}))
228
- @fields = config.field_definitions
229
- @queries = config.query_generators.map{|g| g.generate(@name, @escaped_name)}
230
- end
231
-
232
- def filter(record)
233
- @filter.filter(record)
234
- end
235
-
236
- def reserve_fields
237
- f = {}
238
- @fields.keys.each do |type_sym|
239
- @fields[type_sym].each do |fieldname|
240
- f[fieldname] = type_sym.to_s
241
- end
242
- end
243
- f
244
- end
245
- end
246
- end
@@ -1,5 +1,11 @@
1
+ require_relative 'norikra/output'
2
+
3
+ require 'norikra-client'
4
+
1
5
  module Fluent
2
6
  class NorikraOutput < Fluent::BufferedOutput
7
+ include Fluent::NorikraPlugin::OutputMixin
8
+
3
9
  Fluent::Plugin.register_output('norikra', self)
4
10
 
5
11
  config_set_default :flush_interval, 1 # 1sec
@@ -10,11 +16,8 @@ module Fluent
10
16
  config_param :send_timeout, :integer, :default => nil
11
17
  config_param :receive_timeout, :integer, :default => nil
12
18
 
13
- #<server>
14
- attr_reader :execute_server, :execute_server_path
15
-
19
+ #for OutputMixin
16
20
  config_param :remove_tag_prefix, :string, :default => nil
17
-
18
21
  config_param :target_map_tag, :bool, :default => false
19
22
  config_param :target_map_key, :string, :default => nil
20
23
  config_param :target_string, :string, :default => nil
@@ -22,15 +25,6 @@ module Fluent
22
25
  # <default>
23
26
  # <target TARGET>
24
27
 
25
- # <events>
26
- attr_reader :event_method, :event_tag_generator, :event_sweep_interval
27
-
28
- def initialize
29
- super
30
- require_relative 'norikra_target'
31
- require 'norikra/client'
32
- end
33
-
34
28
  def configure(conf)
35
29
  super
36
30
 
@@ -40,78 +34,17 @@ module Fluent
40
34
  if !@target_map_tag && @target_map_key.nil? && @target_string.nil?
41
35
  raise Fluent::ConfigError, 'target naming not specified (target_map_tag/target_map_key/target_string)'
42
36
  end
43
- @target_generator = case
44
- when @target_string
45
- lambda {|tag,record| @target_string}
46
- when @target_map_key
47
- lambda {|tag,record| record[@target_map_key]}
48
- when @target_map_tag
49
- lambda {|tag,record| tag.gsub(/^#{@remove_tag_prefix}(\.)?/, '')}
50
- else
51
- raise Fluent::ConfigError, "no one way specified to decide target"
52
- end
53
-
54
- # target map already prepared (opened, and related queries registered)
55
- @target_map = {} # 'target' => instance of Fluent::NorikraOutput::Target
56
-
57
- # for conversion from query_name to tag
58
- @query_map = {} # 'query_name' => instance of Fluent::NorikraOutput::Query
59
-
60
- @default_target = ConfigSection.new(Fluent::Config::Element.new('default', nil, {}, []))
61
- @config_targets = {}
62
37
 
63
- @execute_server = false
64
-
65
- event_section = nil
66
38
  conf.elements.each do |element|
67
39
  case element.name
68
- when 'default'
69
- @default_target = ConfigSection.new(element)
70
- when 'target'
71
- c = ConfigSection.new(element)
72
- @config_targets[c.target] = c
73
- when 'server'
74
- @execute_server = true
75
- @execute_jruby_path = element['jruby']
76
- @execute_server_path = element['path']
77
- @execute_server_opts = element['opts']
78
- when 'event', 'events'
79
- event_section = element
40
+ when 'default', 'target'
41
+ # ignore: processed in OutputMixin
80
42
  else
81
43
  raise Fluent::ConfigError, "unknown configuration section name for this plugin: #{element.name}"
82
44
  end
83
45
  end
84
46
 
85
- @event_method = @event_tag_generator = @event_sweep_interval = nil
86
- if event_section
87
- @event_method = case event_section['method']
88
- when 'sweep' then :sweep
89
- when 'listen'
90
- raise Fluent::ConfigError, "not implemeneted now"
91
- else
92
- raise Fluent::ConfigError, "unknown method #{event_section['method']}"
93
- end
94
- unless event_section['tag']
95
- raise Fluent::ConfigError, "<event> section needs 'tag' configuration"
96
- end
97
- tag_prefix = if event_section.has_key?('tag_prefix')
98
- event_section['tag_prefix'] + (event_section['tag_prefix'] =~ /\.$/ ? '' : '.')
99
- else
100
- ''
101
- end
102
- tag_by, tag_arg = event_section['tag'].split(/ +/, 2)
103
- @event_tag_generator = case tag_by
104
- when 'query_name' then lambda{|query_name,record| tag_prefix + query_name}
105
- when 'field' then lambda{|query_name,record| tag_prefix + record[tag_arg]}
106
- when 'string' then lambda{|query_name,record| tag_prefix + tag_arg}
107
- else
108
- raise Fluent::ConfigError, "unknown tag configuration specified:#{event_section['tag']}"
109
- end
110
- @event_sweep_interval = Fluent::Config.time_value(event_section['sweep_interval'] || '10s')
111
- end
112
-
113
- @mutex = Mutex.new
114
- @target_mutex = Mutex.new
47
+ setup_output(conf, false) # <query> disabled in <default> and <target TARGET>
115
48
  end
116
49
 
117
50
  def client(opts={})
@@ -124,298 +57,16 @@ module Fluent
124
57
 
125
58
  def start
126
59
  super
127
-
128
- @norikra_started = false
129
-
130
- if @execute_server
131
- @norikra_pid = nil
132
- @norikra_thread = Thread.new(&method(:server_starter))
133
- # @norikra_started will be set in server_starter
134
- else
135
- @norikra_pid = nil
136
- @norikra_thread = nil
137
- @norikra_started = true
138
- end
139
-
140
- # register worker thread
141
- @register_queue = []
142
- @registered_targets = {}
143
- @register_thread = Thread.new(&method(:register_worker))
144
-
145
- # fetch worker thread
146
- @fetch_queue = []
147
- @fetch_thread = Thread.new(&method(:fetch_worker))
148
-
149
- # for sweep
150
- if @event_method
151
- @fetch_queue.push(FetchRequest.new(nil, @event_sweep_interval))
152
- end
60
+ start_output
153
61
  end
154
62
 
155
63
  def shutdown
156
- @register_thread.kill
157
- @fetch_thread.kill
158
- Process.kill(:TERM, @norikra_pid) if @execute_server
159
-
160
- @register_thread.join
161
- @fetch_thread.join
162
-
163
- if @execute_server
164
- begin
165
- counter = 0
166
- while !Process.waitpid(@norikra_pid, Process::WNOHANG)
167
- sleep 1
168
- break if counter > 3
169
- end
170
- rescue Errno::ECHILD
171
- # norikra server process exited.
172
- end
173
- end
174
- end
175
-
176
- def server_starter
177
- $log.info "starting Norikra server process #{@host}:#{@port}"
178
- base_options = [@execute_server_path, 'start', '-H', @host, '-P', @port.to_s]
179
- cmd,options = if @execute_jruby_path
180
- [@execute_jruby_path, [@execute_server_path, 'start', '-H', @host, '-P', @port.to_s]]
181
- else
182
- [@execute_server_path, ['start', '-H', @host, '-P', @port.to_s]]
183
- end
184
- if @execute_server_opts
185
- options += @execute_server_opts.split(/ +/)
186
- end
187
- @norikra_pid = fork do
188
- ENV.keys.select{|k| k =~ /^(RUBY|GEM|BUNDLE|RBENV|RVM|rvm)/}.each {|k| ENV.delete(k)}
189
- exec([cmd, 'norikra(fluentd)'], *options)
190
- end
191
- connecting = true
192
- $log.info "trying to confirm norikra server status..."
193
- while connecting
194
- begin
195
- $log.debug "start to connect norikra server #{@host}:#{@port}"
196
- client(:connect_timeout => 1, :send_timeout => 1, :receive_timeout => 1).targets
197
- # discard result: no exceptions is success
198
- connecting = false
199
- next
200
- rescue HTTPClient::TimeoutError
201
- $log.debug "Norikra server test connection timeout. retrying..."
202
- rescue Errno::ECONNREFUSED
203
- $log.debug "Norikra server test connection refused. retrying..."
204
- rescue => e
205
- $log.error "unknown error in confirming norikra server, #{e.class}:#{e.message}"
206
- end
207
- sleep 3
208
- end
209
- $log.info "confirmed that norikra server #{@host}:#{@port} started."
210
- @norikra_started = true
211
- end
212
-
213
- def register_worker
214
- while sleep(0.25)
215
- next unless @norikra_started
216
-
217
- c = client()
218
-
219
- targets = @register_queue.shift(10)
220
- targets.each do |t|
221
- next if @target_map[t.name]
222
-
223
- $log.debug "Preparing norikra target #{t.name} on #{@host}:#{@port}"
224
- if prepare_target(c, t)
225
- $log.debug "success to prepare target #{t.name} on #{@host}:#{@port}"
226
- # success
227
- t.queries.each do |query|
228
- @query_map[query.name] = query
229
- insert_fetch_queue(FetchRequest.new(query)) unless query.tag.empty? || @event_method
230
- end
231
- @target_map[t.name] = t
232
- @registered_targets.delete(t.name)
233
- else
234
- $log.error "Failed to prepare norikra data for target:#{t.name}"
235
- @norikra_started.push(t)
236
- end
237
- end
238
- end
239
- end
240
-
241
- def fetch_worker
242
- while sleep(1)
243
- next unless @norikra_started
244
- next if @fetch_queue.first.nil? || @fetch_queue.first.time > Time.now
245
-
246
- now = Time.now
247
- while @fetch_queue.first.time <= now
248
- req = @fetch_queue.shift
249
- if req.query.nil?
250
- sweep()
251
- else
252
- fetch(req.query)
253
- end
254
- insert_fetch_queue(req)
255
- end
256
- end
64
+ stop_output
65
+ shutdown_output
257
66
  end
258
67
 
259
- def format_stream(tag, es)
260
- tobe_registered_target_names = []
261
-
262
- out = ''
263
-
264
- es.each do |time,record|
265
- target = @target_generator.call(tag, record)
266
-
267
- tgt = @target_mutex.synchronize do
268
- t = @target_map[target]
269
- unless t
270
- unless tobe_registered_target_names.include?(target)
271
- conf = @config_targets[target]
272
- unless conf
273
- @config_targets.values.each do |c|
274
- if c.target_matcher.match(target)
275
- conf = c
276
- break
277
- end
278
- end
279
- end
280
- t = Target.new(target, @default_target + conf)
281
- @registered_targets[target] = t
282
- @register_queue.push(t)
283
- tobe_registered_target_names.push(target)
284
- end
285
- t = @registered_targets[target]
286
- end
287
- t
288
- end
289
-
290
- event = tgt.filter(record)
291
-
292
- out << [tgt.escaped_name,event].to_msgpack
293
- end
294
-
295
- out
296
- end
297
-
298
- def prepared?(target_names)
299
- @norikra_started && target_names.reduce(true){|r,t| r && @target_map.values.any?{|target| target.escaped_name == t}}
300
- end
301
-
302
- def write(chunk)
303
- events_map = {} # target => [event]
304
- chunk.msgpack_each do |target, event|
305
- events_map[target] ||= []
306
- events_map[target].push(event)
307
- end
308
-
309
- unless prepared?(events_map.keys)
310
- raise RuntimeError, "norikra server is not ready for this targets: #{events_map.keys.join(',')}"
311
- end
312
-
313
- c = client()
314
-
315
- events_map.each do |target, events|
316
- c.send(target, events)
317
- end
318
- end
319
-
320
- def prepare_target(client, target)
321
- # target open and reserve fields
322
- $log.debug "Going to prepare about target"
323
- begin
324
- unless client.targets.include?(target.escaped_name)
325
- $log.debug "opening target #{target.escaped_name}"
326
- client.open(target.escaped_name, target.reserve_fields, target.auto_field)
327
- $log.debug "opening target #{target.escaped_name}, done."
328
- end
329
-
330
- reserving = target.reserve_fields
331
- reserved = []
332
- client.fields(target.escaped_name).each do |field|
333
- if reserving[field['name']]
334
- reserved.push(field['name'])
335
- if reserving[field['name']] != field['type']
336
- $log.warn "field type mismatch, reserving:#{reserving[field['name']]} but reserved:#{field['type']}"
337
- end
338
- end
339
- end
340
-
341
- reserving.each do |fieldname,type|
342
- client.reserve(target.escaped_name, fieldname, type) unless reserved.include?(fieldname)
343
- end
344
- rescue => e
345
- $log.error "failed to prepare target:#{target.escaped_name}", :norikra => "#{@host}:#{@port}", :error => e.class, :message => e.message
346
- return false
347
- end
348
-
349
- # query registration
350
- begin
351
- registered = Hash[client.queries.map{|q| [q['name'], q['expression']]}]
352
- target.queries.each do |query|
353
- if registered.has_key?(query.name) # query already registered
354
- if registered[query.name] != query.expression
355
- $log.warn "query name and expression mismatch, check norikra server status. target query name:#{query.name}"
356
- end
357
- next
358
- end
359
- client.register(query.name, query.expression)
360
- end
361
- rescue => e
362
- $log.warn "failed to register query", :norikra => "#{@host}:#{@port}", :error => e.class, :message => e.message
363
- end
364
- end
365
-
366
- class FetchRequest
367
- attr_accessor :time, :query
368
- def initialize(query, interval=nil)
369
- @query = query
370
- @interval = interval || query.interval
371
- @time = Time.now + @interval
372
- end
373
- def <=>(other)
374
- self.time <=> other.time
375
- end
376
- def next!
377
- @time = Time.now + @interval
378
- end
379
- end
380
-
381
- def insert_fetch_queue(request)
382
- @mutex.synchronize do
383
- request.next!
384
- # if @fetch_queue.size > 0
385
- # next_pos = @fetch_queue.bsearch{|req| req.time > request.time}
386
- # @fetch_queue.insert(next_pos, request)
387
- # else
388
- # @fetch_queue.push(request)
389
- # end
390
- @fetch_queue.push(request)
391
- @fetch_queue.sort!
392
- end
393
- rescue => e
394
- $log.error "unknown log encountered", :error_class => e.class, :message => e.message
395
- end
396
-
397
- def sweep
398
- begin
399
- client().sweep.each do |query_name, event_array|
400
- query = @query_map[query_name]
401
- event_array.each do |time,event|
402
- tag = (query && !query.tag.empty?) ? query.tag : @event_tag_generator.call(query_name, event)
403
- Fluent::Engine.emit(tag, time, event)
404
- end
405
- end
406
- rescue => e
407
- $log.error "failed to sweep", :norikra => "#{@host}:#{@port}", :error => e.class, :message => e.message
408
- end
409
- end
410
-
411
- def fetch(query)
412
- begin
413
- client().event(query.name).each do |time,event| # [[time(int from epoch), event], ...]
414
- Fluent::Engine.emit(query.tag, time, event)
415
- end
416
- rescue => e
417
- $log.error "failed to fetch for query:#{query.name}", :norikra => "#{@host}:#{@port}", :error => e.class, :message => e.message
418
- end
68
+ def fetchable?
69
+ true
419
70
  end
420
71
  end
421
72
  end