fluent-plugin-es-mohit 1.9.3

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,371 @@
1
+ # encoding: UTF-8
2
+ require 'date'
3
+ require 'excon'
4
+ require 'elasticsearch'
5
+ require 'json'
6
+ require 'uri'
7
+ begin
8
+ require 'strptime'
9
+ rescue LoadError
10
+ end
11
+
12
+ require 'fluent/output'
13
+ require_relative 'elasticsearch_index_template'
14
+
15
+ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
16
+ class ConnectionFailure < StandardError; end
17
+
18
+ Fluent::Plugin.register_output('elasticsearch', self)
19
+
20
+ config_param :host, :string, :default => 'localhost'
21
+ config_param :port, :integer, :default => 9200
22
+ config_param :user, :string, :default => nil
23
+ config_param :password, :string, :default => nil, :secret => true
24
+ config_param :path, :string, :default => nil
25
+ config_param :scheme, :string, :default => 'http'
26
+ config_param :hosts, :string, :default => nil
27
+ config_param :target_index_key, :string, :default => nil
28
+ config_param :target_type_key, :string, :default => nil
29
+ config_param :time_key_format, :string, :default => nil
30
+ config_param :logstash_format, :bool, :default => false
31
+ config_param :logstash_prefix, :string, :default => "logstash"
32
+ config_param :logstash_dateformat, :string, :default => "%Y.%m.%d"
33
+ config_param :utc_index, :bool, :default => true
34
+ config_param :type_name, :string, :default => "fluentd"
35
+ config_param :index_name, :string, :default => "fluentd"
36
+ config_param :id_key, :string, :default => nil
37
+ config_param :write_operation, :string, :default => "index"
38
+ config_param :parent_key, :string, :default => nil
39
+ config_param :routing_key, :string, :default => nil
40
+ config_param :request_timeout, :time, :default => 5
41
+ config_param :reload_connections, :bool, :default => true
42
+ config_param :reload_on_failure, :bool, :default => false
43
+ config_param :resurrect_after, :time, :default => 60
44
+ config_param :time_key, :string, :default => nil
45
+ config_param :time_key_exclude_timestamp, :bool, :default => false
46
+ config_param :ssl_verify , :bool, :default => true
47
+ config_param :client_key, :string, :default => nil
48
+ config_param :client_cert, :string, :default => nil
49
+ config_param :client_key_pass, :string, :default => nil
50
+ config_param :ca_file, :string, :default => nil
51
+ config_param :remove_keys, :string, :default => nil
52
+ config_param :remove_keys_on_update, :string, :default => ""
53
+ config_param :remove_keys_on_update_key, :string, :default => nil
54
+ config_param :flatten_hashes, :bool, :default => false
55
+ config_param :flatten_hashes_separator, :string, :default => "_"
56
+ config_param :template_name, :string, :default => nil
57
+ config_param :template_file, :string, :default => nil
58
+ config_param :templates, :hash, :default => nil
59
+ config_param :include_tag_key, :bool, :default => false
60
+ config_param :tag_key, :string, :default => 'tag'
61
+ config_param :time_parse_error_tag, :string, :default => 'Fluent::ElasticsearchOutput::TimeParser.error'
62
+ config_param :reconnect_on_error, :bool, :default => false
63
+ config_param :pipeline, :string, :default => nil
64
+
65
+ include Fluent::ElasticsearchIndexTemplate
66
+
67
+ def initialize
68
+ super
69
+ end
70
+
71
+ def configure(conf)
72
+ super
73
+ @time_parser = create_time_parser
74
+
75
+ if @remove_keys
76
+ @remove_keys = @remove_keys.split(/\s*,\s*/)
77
+ end
78
+
79
+ if @target_index_key && @target_index_key.is_a?(String)
80
+ @target_index_key = @target_index_key.split '.'
81
+ end
82
+
83
+ if @target_type_key && @target_type_key.is_a?(String)
84
+ @target_type_key = @target_type_key.split '.'
85
+ end
86
+
87
+ if @remove_keys_on_update && @remove_keys_on_update.is_a?(String)
88
+ @remove_keys_on_update = @remove_keys_on_update.split ','
89
+ end
90
+
91
+ if @template_name && @template_file
92
+ template_install(@template_name, @template_file)
93
+ elsif @templates
94
+ templates_hash_install (@templates)
95
+ end
96
+
97
+ @meta_config_map = create_meta_config_map
98
+
99
+ begin
100
+ require 'oj'
101
+ @dump_proc = Oj.method(:dump)
102
+ rescue LoadError
103
+ @dump_proc = Yajl.method(:dump)
104
+ end
105
+ end
106
+
107
+ def create_meta_config_map
108
+ result = []
109
+ result << [@id_key, '_id'] if @id_key
110
+ result << [@parent_key, '_parent'] if @parent_key
111
+ result << [@routing_key, '_routing'] if @routing_key
112
+ result
113
+ end
114
+
115
+ # once fluent v0.14 is released we might be able to use
116
+ # Fluent::Parser::TimeParser, but it doesn't quite do what we want - if gives
117
+ # [sec,nsec] where as we want something we can call `strftime` on...
118
+ def create_time_parser
119
+ if @time_key_format
120
+ begin
121
+ # Strptime doesn't support all formats, but for those it does it's
122
+ # blazingly fast.
123
+ strptime = Strptime.new(@time_key_format)
124
+ Proc.new { |value| strptime.exec(value).to_datetime }
125
+ rescue
126
+ # Can happen if Strptime doesn't recognize the format; or
127
+ # if strptime couldn't be required (because it's not installed -- it's
128
+ # ruby 2 only)
129
+ Proc.new { |value| DateTime.strptime(value, @time_key_format) }
130
+ end
131
+ else
132
+ Proc.new { |value| DateTime.parse(value) }
133
+ end
134
+ end
135
+
136
+ def parse_time(value, event_time, tag)
137
+ @time_parser.call(value)
138
+ rescue => e
139
+ router.emit_error_event(@time_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @time_key_format, 'value' => value}, e)
140
+ return Time.at(event_time).to_datetime
141
+ end
142
+
143
+ def client
144
+ @_es ||= begin
145
+ excon_options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
146
+ adapter_conf = lambda {|f| f.adapter :excon, excon_options }
147
+ transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(get_connection_options.merge(
148
+ options: {
149
+ reload_connections: @reload_connections,
150
+ reload_on_failure: @reload_on_failure,
151
+ resurrect_after: @resurrect_after,
152
+ retry_on_failure: 5,
153
+ transport_options: {
154
+ request: { timeout: @request_timeout },
155
+ ssl: { verify: @ssl_verify, ca_file: @ca_file }
156
+ }
157
+ }), &adapter_conf)
158
+ es = Elasticsearch::Client.new transport: transport
159
+
160
+ begin
161
+ raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description})!" unless es.ping
162
+ rescue *es.transport.host_unreachable_exceptions => e
163
+ raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description})! #{e.message}"
164
+ end
165
+
166
+ log.info "Connection opened to Elasticsearch cluster => #{connection_options_description}"
167
+ es
168
+ end
169
+ end
170
+
171
+ def get_connection_options
172
+ raise "`password` must be present if `user` is present" if @user && !@password
173
+
174
+ hosts = if @hosts
175
+ @hosts.split(',').map do |host_str|
176
+ # Support legacy hosts format host:port,host:port,host:port...
177
+ if host_str.match(%r{^[^:]+(\:\d+)?$})
178
+ {
179
+ host: host_str.split(':')[0],
180
+ port: (host_str.split(':')[1] || @port).to_i,
181
+ scheme: @scheme
182
+ }
183
+ else
184
+ # New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
185
+ uri = URI(host_str)
186
+ %w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
187
+ hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
188
+ hash
189
+ end
190
+ end
191
+ end.compact
192
+ else
193
+ [{host: @host, port: @port, scheme: @scheme}]
194
+ end.each do |host|
195
+ host.merge!(user: @user, password: @password) if !host[:user] && @user
196
+ host.merge!(path: @path) if !host[:path] && @path
197
+ end
198
+
199
+ {
200
+ hosts: hosts
201
+ }
202
+ end
203
+
204
+ def connection_options_description
205
+ get_connection_options[:hosts].map do |host_info|
206
+ attributes = host_info.dup
207
+ attributes[:password] = 'obfuscated' if attributes.has_key?(:password)
208
+ attributes.inspect
209
+ end.join(', ')
210
+ end
211
+
212
+ BODY_DELIMITER = "\n".freeze
213
+ UPDATE_OP = "update".freeze
214
+ UPSERT_OP = "upsert".freeze
215
+ CREATE_OP = "create".freeze
216
+ INDEX_OP = "index".freeze
217
+ ID_FIELD = "_id".freeze
218
+ TIMESTAMP_FIELD = "@timestamp".freeze
219
+
220
+ def append_record_to_messages(op, meta, header, record, msgs)
221
+ case op
222
+ when UPDATE_OP, UPSERT_OP
223
+ if meta.has_key?(ID_FIELD)
224
+ header[UPDATE_OP] = meta
225
+ msgs << @dump_proc.call(header) << BODY_DELIMITER
226
+ msgs << @dump_proc.call(update_body(record, op)) << BODY_DELIMITER
227
+ end
228
+ when CREATE_OP
229
+ if meta.has_key?(ID_FIELD)
230
+ header[CREATE_OP] = meta
231
+ msgs << @dump_proc.call(header) << BODY_DELIMITER
232
+ msgs << @dump_proc.call(record) << BODY_DELIMITER
233
+ end
234
+ when INDEX_OP
235
+ header[INDEX_OP] = meta
236
+ msgs << @dump_proc.call(header) << BODY_DELIMITER
237
+ msgs << @dump_proc.call(record) << BODY_DELIMITER
238
+ end
239
+ if @pipeline
240
+ msgs << "\"pipeline\" : \"" << @pipeline << "\"" << BODY_DELIMITER
241
+ end
242
+
243
+ def update_body(record, op)
244
+ update = remove_keys(record)
245
+ body = {"doc".freeze => update}
246
+ if op == UPSERT_OP
247
+ if update == record
248
+ body["doc_as_upsert".freeze] = true
249
+ else
250
+ body[UPSERT_OP] = record
251
+ end
252
+ end
253
+ body
254
+ end
255
+
256
+ def remove_keys(record)
257
+ keys = record[@remove_keys_on_update_key] || @remove_keys_on_update || []
258
+ record.delete(@remove_keys_on_update_key)
259
+ return record unless keys.any?
260
+ record = record.dup
261
+ keys.each { |key| record.delete(key) }
262
+ record
263
+ end
264
+
265
+ def flatten_record(record, prefix=[])
266
+ ret = {}
267
+ if record.is_a? Hash
268
+ record.each { |key, value|
269
+ ret.merge! flatten_record(value, prefix + [key.to_s])
270
+ }
271
+ elsif record.is_a? Array
272
+ # Don't mess with arrays, leave them unprocessed
273
+ ret.merge!({prefix.join(@flatten_hashes_separator) => record})
274
+ else
275
+ return {prefix.join(@flatten_hashes_separator) => record}
276
+ end
277
+ ret
278
+ end
279
+
280
+ def write_objects(tag, chunk)
281
+ bulk_message = ''
282
+ header = {}
283
+ meta = {}
284
+
285
+ chunk.msgpack_each do |time, record|
286
+ next unless record.is_a? Hash
287
+
288
+ if @flatten_hashes
289
+ record = flatten_record(record)
290
+ end
291
+
292
+ target_index_parent, target_index_child_key = @target_index_key ? get_parent_of(record, @target_index_key) : nil
293
+ if target_index_parent && target_index_parent[target_index_child_key]
294
+ target_index = target_index_parent.delete(target_index_child_key)
295
+ elsif @logstash_format
296
+ if record.has_key?(TIMESTAMP_FIELD)
297
+ rts = record[TIMESTAMP_FIELD]
298
+ dt = parse_time(rts, time, tag)
299
+ elsif record.has_key?(@time_key)
300
+ rts = record[@time_key]
301
+ dt = parse_time(rts, time, tag)
302
+ record[TIMESTAMP_FIELD] = rts unless @time_key_exclude_timestamp
303
+ else
304
+ dt = Time.at(time).to_datetime
305
+ record[TIMESTAMP_FIELD] = dt.to_s
306
+ end
307
+ dt = dt.new_offset(0) if @utc_index
308
+ target_index = "#{@logstash_prefix}-#{dt.strftime(@logstash_dateformat)}"
309
+ else
310
+ target_index = @index_name
311
+ end
312
+
313
+ # Change target_index to lower-case since Elasticsearch doesn't
314
+ # allow upper-case characters in index names.
315
+ target_index = target_index.downcase
316
+ if @include_tag_key
317
+ record[@tag_key] = tag
318
+ end
319
+
320
+ target_type_parent, target_type_child_key = @target_type_key ? get_parent_of(record, @target_type_key) : nil
321
+ if target_type_parent && target_type_parent[target_type_child_key]
322
+ target_type = target_type_parent.delete(target_type_child_key)
323
+ else
324
+ target_type = @type_name
325
+ end
326
+
327
+ meta.clear
328
+ meta["_index".freeze] = target_index
329
+ meta["_type".freeze] = target_type
330
+
331
+ @meta_config_map.each do |record_key, meta_key|
332
+ meta[meta_key] = record[record_key] if record[record_key]
333
+ end
334
+
335
+ if @remove_keys
336
+ @remove_keys.each { |key| record.delete(key) }
337
+ end
338
+
339
+ append_record_to_messages(@write_operation, meta, header, record, bulk_message)
340
+ end
341
+
342
+ send_bulk(bulk_message) unless bulk_message.empty?
343
+ bulk_message.clear
344
+ end
345
+
346
+ # returns [parent, child_key] of child described by path array in record's tree
347
+ # returns [nil, child_key] if path doesnt exist in record
348
+ def get_parent_of(record, path)
349
+ parent_object = path[0..-2].reduce(record) { |a, e| a.is_a?(Hash) ? a[e] : nil }
350
+ [parent_object, path[-1]]
351
+ end
352
+
353
+ def send_bulk(data)
354
+ retries = 0
355
+ begin
356
+ client.bulk body: data
357
+ rescue *client.transport.host_unreachable_exceptions => e
358
+ if retries < 2
359
+ retries += 1
360
+ @_es = nil
361
+ log.warn "Could not push logs to Elasticsearch, resetting connection and trying again. #{e.message}"
362
+ sleep 2**retries
363
+ retry
364
+ end
365
+ raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
366
+ rescue Exception
367
+ @_es = nil if @reconnect_on_error
368
+ raise
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,269 @@
1
+ # encoding: UTF-8
2
+ require_relative 'out_elasticsearch'
3
+
4
+ class Fluent::ElasticsearchOutputDynamic < Fluent::ElasticsearchOutput
5
+
6
+ Fluent::Plugin.register_output('elasticsearch_dynamic', self)
7
+
8
+ config_param :delimiter, :string, :default => "."
9
+
10
+ # params overloaded as strings
11
+ config_param :port, :string, :default => "9200"
12
+ config_param :logstash_format, :string, :default => "false"
13
+ config_param :utc_index, :string, :default => "true"
14
+ config_param :time_key_exclude_timestamp, :bool, :default => false
15
+ config_param :reload_connections, :string, :default => "true"
16
+ config_param :reload_on_failure, :string, :default => "false"
17
+ config_param :resurrect_after, :string, :default => "60"
18
+ config_param :ssl_verify, :string, :default => "true"
19
+ config_param :reconnect_on_error, :bool, :default => false
20
+ config_param :pipeline, :string, :default => nil
21
+
22
+ def configure(conf)
23
+ super
24
+
25
+ # evaluate all configurations here
26
+ @dynamic_params ||= []
27
+ @dynamic_params += self.instance_variables.select { |var| is_valid_expand_param_type(var) }
28
+ @dynamic_config = Hash.new
29
+ @dynamic_params.each { |var|
30
+ value = expand_param(self.instance_variable_get(var), nil, nil, nil)
31
+ var = var[1..-1]
32
+ @dynamic_config[var] = value
33
+ }
34
+ # end eval all configs
35
+ @current_config = nil
36
+ end
37
+
38
+ def create_meta_config_map
39
+ {'id_key' => '_id', 'parent_key' => '_parent', 'routing_key' => '_routing'}
40
+ end
41
+
42
+ def client(host)
43
+
44
+ # check here to see if we already have a client connection for the given host
45
+ connection_options = get_connection_options(host)
46
+
47
+ @_es = nil unless is_existing_connection(connection_options[:hosts])
48
+
49
+ @_es ||= begin
50
+ @current_config = connection_options[:hosts].clone
51
+ excon_options = { client_key: @dynamic_config['client_key'], client_cert: @dynamic_config['client_cert'], client_key_pass: @dynamic_config['client_key_pass'] }
52
+ adapter_conf = lambda {|f| f.adapter :excon, excon_options }
53
+ transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(connection_options.merge(
54
+ options: {
55
+ reload_connections: Fluent::Config.bool_value(@dynamic_config['reload_connections']),
56
+ reload_on_failure: Fluent::Config.bool_value(@dynamic_config['reload_on_failure']),
57
+ resurrect_after: @dynamic_config['resurrect_after'].to_i,
58
+ retry_on_failure: 5,
59
+ transport_options: {
60
+ request: { timeout: @dynamic_config['request_timeout'] },
61
+ ssl: { verify: @dynamic_config['ssl_verify'], ca_file: @dynamic_config['ca_file'] }
62
+ }
63
+ }), &adapter_conf)
64
+ es = Elasticsearch::Client.new transport: transport
65
+
66
+ begin
67
+ raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description(host)})!" unless es.ping
68
+ rescue *es.transport.host_unreachable_exceptions => e
69
+ raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description(host)})! #{e.message}"
70
+ end
71
+
72
+ log.info "Connection opened to Elasticsearch cluster => #{connection_options_description(host)}"
73
+ es
74
+ end
75
+ end
76
+
77
+ def get_connection_options(con_host)
78
+ raise "`password` must be present if `user` is present" if @dynamic_config['user'] && !@dynamic_config['password']
79
+
80
+ hosts = if con_host || @dynamic_config['hosts']
81
+ (con_host || @dynamic_config['hosts']).split(',').map do |host_str|
82
+ # Support legacy hosts format host:port,host:port,host:port...
83
+ if host_str.match(%r{^[^:]+(\:\d+)?$})
84
+ {
85
+ host: host_str.split(':')[0],
86
+ port: (host_str.split(':')[1] || @dynamic_config['port'] || @port).to_i,
87
+ scheme: @dynamic_config['scheme']
88
+ }
89
+ else
90
+ # New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
91
+ uri = URI(host_str)
92
+ %w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
93
+ hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
94
+ hash
95
+ end
96
+ end
97
+ end.compact
98
+ else
99
+ [{host: @dynamic_config['host'], port: @dynamic_config['port'].to_i, scheme: @dynamic_config['scheme']}]
100
+ end.each do |host|
101
+ host.merge!(user: @dynamic_config['user'], password: @dynamic_config['password']) if !host[:user] && @dynamic_config['user']
102
+ host.merge!(path: @dynamic_config['path']) if !host[:path] && @dynamic_config['path']
103
+ end
104
+
105
+ {
106
+ hosts: hosts
107
+ }
108
+ end
109
+
110
+ def connection_options_description(host)
111
+ get_connection_options(host)[:hosts].map do |host_info|
112
+ attributes = host_info.dup
113
+ attributes[:password] = 'obfuscated' if attributes.has_key?(:password)
114
+ attributes.inspect
115
+ end.join(', ')
116
+ end
117
+
118
+ def write_objects(tag, chunk)
119
+ bulk_message = Hash.new { |h,k| h[k] = '' }
120
+ dynamic_conf = @dynamic_config.clone
121
+
122
+ headers = {
123
+ UPDATE_OP => {},
124
+ UPSERT_OP => {},
125
+ CREATE_OP => {},
126
+ INDEX_OP => {}
127
+ }
128
+
129
+ chunk.msgpack_each do |time, record|
130
+ next unless record.is_a? Hash
131
+
132
+ # evaluate all configurations here
133
+ @dynamic_params.each { |var|
134
+ k = var[1..-1]
135
+ v = self.instance_variable_get(var)
136
+ # check here to determine if we should evaluate
137
+ if dynamic_conf[k] != v
138
+ value = expand_param(v, tag, time, record)
139
+ dynamic_conf[k] = value
140
+ end
141
+ }
142
+ # end eval all configs
143
+
144
+ if eval_or_val(dynamic_conf['logstash_format'])
145
+ if record.has_key?("@timestamp")
146
+ time = Time.parse record["@timestamp"]
147
+ elsif record.has_key?(dynamic_conf['time_key'])
148
+ time = Time.parse record[dynamic_conf['time_key']]
149
+ record['@timestamp'] = record[dynamic_conf['time_key']] unless time_key_exclude_timestamp
150
+ else
151
+ record.merge!({"@timestamp" => Time.at(time).to_datetime.to_s})
152
+ end
153
+
154
+ if eval_or_val(dynamic_conf['utc_index'])
155
+ target_index = "#{dynamic_conf['logstash_prefix']}-#{Time.at(time).getutc.strftime("#{dynamic_conf['logstash_dateformat']}")}"
156
+ else
157
+ target_index = "#{dynamic_conf['logstash_prefix']}-#{Time.at(time).strftime("#{dynamic_conf['logstash_dateformat']}")}"
158
+ end
159
+ else
160
+ target_index = dynamic_conf['index_name']
161
+ end
162
+
163
+ # Change target_index to lower-case since Elasticsearch doesn't
164
+ # allow upper-case characters in index names.
165
+ target_index = target_index.downcase
166
+
167
+ if @include_tag_key
168
+ record.merge!(dynamic_conf['tag_key'] => tag)
169
+ end
170
+
171
+ meta = {"_index" => target_index, "_type" => dynamic_conf['type_name']}
172
+
173
+ @meta_config_map.each_pair do |config_name, meta_key|
174
+ if dynamic_conf[config_name] && record[dynamic_conf[config_name]]
175
+ meta[meta_key] = record[dynamic_conf[config_name]]
176
+ end
177
+ end
178
+
179
+ if dynamic_conf['hosts']
180
+ host = dynamic_conf['hosts']
181
+ else
182
+ host = "#{dynamic_conf['host']}:#{dynamic_conf['port']}"
183
+ end
184
+
185
+ if @remove_keys
186
+ @remove_keys.each { |key| record.delete(key) }
187
+ end
188
+
189
+ write_op = dynamic_conf["write_operation"]
190
+ append_record_to_messages(write_op, meta, headers[write_op], record, bulk_message[host])
191
+ end
192
+
193
+ bulk_message.each do |hKey, msgs|
194
+ send_bulk(msgs, hKey) unless msgs.empty?
195
+ msgs.clear
196
+ end
197
+ end
198
+
199
+ def send_bulk(data, host)
200
+ retries = 0
201
+ begin
202
+ client(host).bulk body: data
203
+ rescue *client(host).transport.host_unreachable_exceptions => e
204
+ if retries < 2
205
+ retries += 1
206
+ @_es = nil
207
+ log.warn "Could not push logs to Elasticsearch, resetting connection and trying again. #{e.message}"
208
+ sleep 2**retries
209
+ retry
210
+ end
211
+ raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
212
+ rescue Exception
213
+ @_es = nil if @reconnect_on_error
214
+ raise
215
+ end
216
+ end
217
+
218
+ def eval_or_val(var)
219
+ return var unless var.is_a?(String)
220
+ eval(var)
221
+ end
222
+
223
+ def expand_param(param, tag, time, record)
224
+ # check for '${ ... }'
225
+ # yes => `eval`
226
+ # no => return param
227
+ return param if (param =~ /\${.+}/).nil?
228
+
229
+ # check for 'tag_parts[]'
230
+ # separated by a delimiter (default '.')
231
+ tag_parts = tag.split(@delimiter) unless (param =~ /tag_parts\[.+\]/).nil? || tag.nil?
232
+
233
+ # pull out section between ${} then eval
234
+ inner = param.clone
235
+ while inner.match(/\${.+}/)
236
+ to_eval = inner.match(/\${(.+?)}/){$1}
237
+
238
+ if !(to_eval =~ /record\[.+\]/).nil? && record.nil?
239
+ return to_eval
240
+ elsif !(to_eval =~/tag_parts\[.+\]/).nil? && tag_parts.nil?
241
+ return to_eval
242
+ elsif !(to_eval =~/time/).nil? && time.nil?
243
+ return to_eval
244
+ else
245
+ inner.sub!(/\${.+?}/, eval( to_eval ))
246
+ end
247
+ end
248
+ inner
249
+ end
250
+
251
+ def is_valid_expand_param_type(param)
252
+ return false if [:@buffer_type].include?(param)
253
+ return self.instance_variable_get(param).is_a?(String)
254
+ end
255
+
256
+ def is_existing_connection(host)
257
+ # check if the host provided match the current connection
258
+ return false if @_es.nil?
259
+ return false if host.length != @current_config.length
260
+
261
+ for i in 0...host.length
262
+ if !host[i][:host].eql? @current_config[i][:host] || host[i][:port] != @current_config[i][:port]
263
+ return false
264
+ end
265
+ end
266
+
267
+ return true
268
+ end
269
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter do |src|
4
+ !(src.filename =~ /^#{SimpleCov.root}\/lib/)
5
+ end
6
+ end
7
+
8
+ require 'coveralls'
9
+ Coveralls.wear!
10
+
11
+ # needs to be after simplecov but before test/unit, because fluentd sets default
12
+ # encoding to ASCII-8BIT, but coverall might load git data which could contain a
13
+ # UTF-8 character
14
+ at_exit do
15
+ Encoding.default_internal = 'UTF-8' if defined?(Encoding) && Encoding.respond_to?(:default_internal)
16
+ Encoding.default_external = 'UTF-8' if defined?(Encoding) && Encoding.respond_to?(:default_external)
17
+ end
18
+
19
+ require 'test/unit'
20
+ require 'fluent/test'
21
+ require 'minitest/pride'
22
+
23
+ require 'webmock/test_unit'
24
+ WebMock.disable_net_connect!