fluent-plugin-elasticsearch 1.18.2 → 2.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,136 +1,129 @@
1
1
  # encoding: UTF-8
2
2
  require_relative 'out_elasticsearch'
3
3
 
4
- class Fluent::ElasticsearchOutputDynamic < Fluent::ElasticsearchOutput
4
+ module Fluent::Plugin
5
+ class ElasticsearchOutputDynamic < ElasticsearchOutput
5
6
 
6
- Fluent::Plugin.register_output('elasticsearch_dynamic', self)
7
+ Fluent::Plugin.register_output('elasticsearch_dynamic', self)
7
8
 
8
- config_param :delimiter, :string, :default => "."
9
+ config_param :delimiter, :string, :default => "."
9
10
 
10
- DYNAMIC_PARAM_NAMES = %W[hosts host port include_timestamp logstash_format logstash_prefix logstash_dateformat time_key utc_index index_name tag_key type_name id_key parent_key routing_key write_operation]
11
- DYNAMIC_PARAM_SYMBOLS = DYNAMIC_PARAM_NAMES.map { |n| "@#{n}".to_sym }
11
+ DYNAMIC_PARAM_NAMES = %W[hosts host port logstash_format logstash_prefix logstash_dateformat time_key utc_index index_name tag_key type_name id_key parent_key routing_key write_operation]
12
+ DYNAMIC_PARAM_SYMBOLS = DYNAMIC_PARAM_NAMES.map { |n| "@#{n}".to_sym }
12
13
 
13
- attr_reader :dynamic_config
14
+ attr_reader :dynamic_config
14
15
 
15
- def configure(conf)
16
- super
16
+ def configure(conf)
17
+ super
17
18
 
18
- # evaluate all configurations here
19
- @dynamic_config = {}
20
- DYNAMIC_PARAM_SYMBOLS.each_with_index { |var, i|
21
- value = expand_param(self.instance_variable_get(var), nil, nil, nil)
22
- key = DYNAMIC_PARAM_NAMES[i]
23
- @dynamic_config[key] = value.to_s
24
- }
25
- # end eval all configs
26
- @current_config = nil
27
- end
19
+ # evaluate all configurations here
20
+ @dynamic_config = {}
21
+ DYNAMIC_PARAM_SYMBOLS.each_with_index { |var, i|
22
+ value = expand_param(self.instance_variable_get(var), nil, nil, nil)
23
+ key = DYNAMIC_PARAM_NAMES[i]
24
+ @dynamic_config[key] = value.to_s
25
+ }
26
+ # end eval all configs
27
+ @current_config = nil
28
+ end
28
29
 
29
- def create_meta_config_map
30
- {'id_key' => '_id', 'parent_key' => '_parent', 'routing_key' => '_routing'}
31
- end
30
+ def create_meta_config_map
31
+ {'id_key' => '_id', 'parent_key' => '_parent', 'routing_key' => '_routing'}
32
+ end
32
33
 
33
- def client(host = nil)
34
-
35
- # check here to see if we already have a client connection for the given host
36
- connection_options = get_connection_options(host)
37
-
38
- @_es = nil unless is_existing_connection(connection_options[:hosts])
39
-
40
- @_es ||= begin
41
- @current_config = connection_options[:hosts].clone
42
- excon_options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
43
- adapter_conf = lambda {|f| f.adapter :excon, excon_options }
44
- transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(connection_options.merge(
45
- options: {
46
- reload_connections: @reload_connections,
47
- reload_on_failure: @reload_on_failure,
48
- resurrect_after: @resurrect_after,
49
- retry_on_failure: 5,
50
- logger: @transport_logger,
51
- transport_options: {
52
- headers: { 'Content-Type' => 'application/json' },
53
- request: { timeout: @request_timeout },
54
- ssl: { verify: @ssl_verify, ca_file: @ca_file }
55
- },
56
- http: {
57
- user: @user,
58
- password: @password
59
- }
60
- }), &adapter_conf)
61
- es = Elasticsearch::Client.new transport: transport
34
+ def client(host)
35
+
36
+ # check here to see if we already have a client connection for the given host
37
+ connection_options = get_connection_options(host)
38
+
39
+ @_es = nil unless is_existing_connection(connection_options[:hosts])
40
+
41
+ @_es ||= begin
42
+ @current_config = connection_options[:hosts].clone
43
+ excon_options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
44
+ adapter_conf = lambda {|f| f.adapter :excon, excon_options }
45
+ transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(connection_options.merge(
46
+ options: {
47
+ reload_connections: @reload_connections,
48
+ reload_on_failure: @reload_on_failure,
49
+ resurrect_after: @resurrect_after,
50
+ retry_on_failure: 5,
51
+ transport_options: {
52
+ headers: { 'Content-Type' => 'application/json' },
53
+ request: { timeout: @request_timeout },
54
+ ssl: { verify: @ssl_verify, ca_file: @ca_file }
55
+ }
56
+ }), &adapter_conf)
57
+ es = Elasticsearch::Client.new transport: transport
58
+
59
+ begin
60
+ raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description(host)})!" unless es.ping
61
+ rescue *es.transport.host_unreachable_exceptions => e
62
+ raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description(host)})! #{e.message}"
63
+ end
62
64
 
63
- begin
64
- raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description(host)})!" unless es.ping
65
- rescue *es.transport.host_unreachable_exceptions => e
66
- raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description(host)})! #{e.message}"
65
+ log.info "Connection opened to Elasticsearch cluster => #{connection_options_description(host)}"
66
+ es
67
67
  end
68
-
69
- log.info "Connection opened to Elasticsearch cluster => #{connection_options_description(host)}"
70
- es
71
68
  end
72
- end
73
69
 
74
- def get_connection_options(con_host)
75
- raise "`password` must be present if `user` is present" if @user && !@password
76
-
77
- hosts = if con_host || @hosts
78
- (con_host || @hosts).split(',').map do |host_str|
79
- # Support legacy hosts format host:port,host:port,host:port...
80
- if host_str.match(%r{^[^:]+(\:\d+)?$})
81
- {
82
- host: host_str.split(':')[0],
83
- port: (host_str.split(':')[1] || @port).to_i,
84
- scheme: @scheme.to_s
85
- }
86
- else
87
- # New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
88
- uri = URI(get_escaped_userinfo(host_str))
89
- %w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
90
- hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
91
- hash
70
+ def get_connection_options(con_host)
71
+ raise "`password` must be present if `user` is present" if @user && !@password
72
+
73
+ hosts = if con_host || @hosts
74
+ (con_host || @hosts).split(',').map do |host_str|
75
+ # Support legacy hosts format host:port,host:port,host:port...
76
+ if host_str.match(%r{^[^:]+(\:\d+)?$})
77
+ {
78
+ host: host_str.split(':')[0],
79
+ port: (host_str.split(':')[1] || @port).to_i,
80
+ scheme: @scheme
81
+ }
82
+ else
83
+ # New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
84
+ uri = URI(host_str)
85
+ %w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
86
+ hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
87
+ hash
88
+ end
92
89
  end
93
- end
94
- end.compact
95
- else
96
- [{host: @host, port: @port.to_i, scheme: @scheme.to_s}]
97
- end.each do |host|
98
- host.merge!(user: @user, password: @password) if !host[:user] && @user
99
- host.merge!(path: @path) if !host[:path] && @path
100
- end
90
+ end.compact
91
+ else
92
+ [{host: @host, port: @port.to_i, scheme: @scheme}]
93
+ end.each do |host|
94
+ host.merge!(user: @user, password: @password) if !host[:user] && @user
95
+ host.merge!(path: @path) if !host[:path] && @path
96
+ end
101
97
 
102
- {
103
- hosts: hosts
104
- }
105
- end
98
+ {
99
+ hosts: hosts
100
+ }
101
+ end
106
102
 
107
- def connection_options_description(host)
108
- get_connection_options(host)[:hosts].map do |host_info|
109
- attributes = host_info.dup
110
- attributes[:password] = 'obfuscated' if attributes.has_key?(:password)
111
- attributes.inspect
112
- end.join(', ')
113
- end
103
+ def connection_options_description(host)
104
+ get_connection_options(host)[:hosts].map do |host_info|
105
+ attributes = host_info.dup
106
+ attributes[:password] = 'obfuscated' if attributes.has_key?(:password)
107
+ attributes.inspect
108
+ end.join(', ')
109
+ end
114
110
 
115
- def write_objects(tag, chunk)
116
- bulk_message = Hash.new { |h,k| h[k] = '' }
117
- dynamic_conf = @dynamic_config.clone
111
+ def write(chunk)
112
+ bulk_message = Hash.new { |h,k| h[k] = '' }
113
+ dynamic_conf = @dynamic_config.clone
118
114
 
119
- headers = {
120
- UPDATE_OP => {},
121
- UPSERT_OP => {},
122
- CREATE_OP => {},
123
- INDEX_OP => {}
124
- }
115
+ headers = {
116
+ UPDATE_OP => {},
117
+ UPSERT_OP => {},
118
+ CREATE_OP => {},
119
+ INDEX_OP => {}
120
+ }
125
121
 
126
- chunk.msgpack_each do |time, record|
127
- next unless record.is_a? Hash
122
+ tag = chunk.metadata.tag
128
123
 
129
- if @hash_config
130
- record = generate_hash_id_key(record)
131
- end
124
+ chunk.msgpack_each do |time, record|
125
+ next unless record.is_a? Hash
132
126
 
133
- begin
134
127
  # evaluate all configurations here
135
128
  DYNAMIC_PARAM_SYMBOLS.each_with_index { |var, i|
136
129
  k = DYNAMIC_PARAM_NAMES[i]
@@ -141,142 +134,135 @@ class Fluent::ElasticsearchOutputDynamic < Fluent::ElasticsearchOutput
141
134
  dynamic_conf[k] = value
142
135
  end
143
136
  }
144
- # end eval all configs
145
- rescue => e
146
- # handle dynamic parameters misconfigurations
147
- router.emit_error_event(tag, time, record, e)
148
- next
149
- end
137
+ # end eval all configs
138
+
139
+ if eval_or_val(dynamic_conf['logstash_format'])
140
+ if record.has_key?("@timestamp")
141
+ time = Time.parse record["@timestamp"]
142
+ elsif record.has_key?(dynamic_conf['time_key'])
143
+ time = Time.parse record[dynamic_conf['time_key']]
144
+ record['@timestamp'] = record[dynamic_conf['time_key']] unless time_key_exclude_timestamp
145
+ else
146
+ record.merge!({"@timestamp" => Time.at(time).to_datetime.to_s})
147
+ end
150
148
 
151
- if eval_or_val(dynamic_conf['logstash_format']) || eval_or_val(dynamic_conf['include_timestamp'])
152
- if record.has_key?("@timestamp")
153
- time = Time.parse record["@timestamp"]
154
- elsif record.has_key?(dynamic_conf['time_key'])
155
- time = Time.parse record[dynamic_conf['time_key']]
156
- record['@timestamp'] = record[dynamic_conf['time_key']] unless time_key_exclude_timestamp
149
+ if eval_or_val(dynamic_conf['utc_index'])
150
+ target_index = "#{dynamic_conf['logstash_prefix']}-#{Time.at(time).getutc.strftime("#{dynamic_conf['logstash_dateformat']}")}"
151
+ else
152
+ target_index = "#{dynamic_conf['logstash_prefix']}-#{Time.at(time).strftime("#{dynamic_conf['logstash_dateformat']}")}"
153
+ end
157
154
  else
158
- record.merge!({"@timestamp" => Time.at(time).to_datetime.to_s})
155
+ target_index = dynamic_conf['index_name']
159
156
  end
160
- end
161
157
 
162
- if eval_or_val(dynamic_conf['logstash_format'])
163
- if eval_or_val(dynamic_conf['utc_index'])
164
- target_index = "#{dynamic_conf['logstash_prefix']}-#{Time.at(time).getutc.strftime("#{dynamic_conf['logstash_dateformat']}")}"
165
- else
166
- target_index = "#{dynamic_conf['logstash_prefix']}-#{Time.at(time).strftime("#{dynamic_conf['logstash_dateformat']}")}"
158
+ # Change target_index to lower-case since Elasticsearch doesn't
159
+ # allow upper-case characters in index names.
160
+ target_index = target_index.downcase
161
+
162
+ if @include_tag_key
163
+ record.merge!(dynamic_conf['tag_key'] => tag)
167
164
  end
168
- else
169
- target_index = dynamic_conf['index_name']
170
- end
171
165
 
172
- # Change target_index to lower-case since Elasticsearch doesn't
173
- # allow upper-case characters in index names.
174
- target_index = target_index.downcase
166
+ meta = {"_index" => target_index, "_type" => dynamic_conf['type_name']}
175
167
 
176
- if @include_tag_key
177
- record.merge!(dynamic_conf['tag_key'] => tag)
178
- end
168
+ @meta_config_map.each_pair do |config_name, meta_key|
169
+ if dynamic_conf[config_name] && record[dynamic_conf[config_name]]
170
+ meta[meta_key] = record[dynamic_conf[config_name]]
171
+ end
172
+ end
179
173
 
180
- meta = {"_index" => target_index, "_type" => dynamic_conf['type_name']}
174
+ if dynamic_conf['hosts']
175
+ host = dynamic_conf['hosts']
176
+ else
177
+ host = "#{dynamic_conf['host']}:#{dynamic_conf['port']}"
178
+ end
181
179
 
182
- @meta_config_map.each_pair do |config_name, meta_key|
183
- if dynamic_conf[config_name] && record[dynamic_conf[config_name]]
184
- meta[meta_key] = record[dynamic_conf[config_name]]
180
+ if @remove_keys
181
+ @remove_keys.each { |key| record.delete(key) }
185
182
  end
186
- end
187
183
 
188
- if dynamic_conf['hosts']
189
- host = dynamic_conf['hosts']
190
- else
191
- host = "#{dynamic_conf['host']}:#{dynamic_conf['port']}"
184
+ write_op = dynamic_conf["write_operation"]
185
+ append_record_to_messages(write_op, meta, headers[write_op], record, bulk_message[host])
192
186
  end
193
187
 
194
- if @remove_keys
195
- @remove_keys.each { |key| record.delete(key) }
188
+ bulk_message.each do |hKey, msgs|
189
+ send_bulk(msgs, hKey) unless msgs.empty?
190
+ msgs.clear
196
191
  end
197
-
198
- write_op = dynamic_conf["write_operation"]
199
- append_record_to_messages(write_op, meta, headers[write_op], record, bulk_message[host])
200
192
  end
201
193
 
202
- bulk_message.each do |hKey, msgs|
203
- send_bulk(msgs, hKey) unless msgs.empty?
204
- msgs.clear
205
- end
206
- end
207
-
208
- def send_bulk(data, host)
209
- retries = 0
210
- begin
211
- response = client(host).bulk body: data
212
- if response['errors']
213
- log.error "Could not push log to Elasticsearch: #{response}"
214
- end
215
- rescue *client(host).transport.host_unreachable_exceptions => e
216
- if retries < 2
217
- retries += 1
218
- @_es = nil
219
- log.warn "Could not push logs to Elasticsearch, resetting connection and trying again. #{e.message}"
220
- sleep 2**retries
221
- retry
194
+ def send_bulk(data, host)
195
+ retries = 0
196
+ begin
197
+ response = client(host).bulk body: data
198
+ if response['errors']
199
+ log.error "Could not push log to Elasticsearch: #{response}"
200
+ end
201
+ rescue *client(host).transport.host_unreachable_exceptions => e
202
+ if retries < 2
203
+ retries += 1
204
+ @_es = nil
205
+ log.warn "Could not push logs to Elasticsearch, resetting connection and trying again. #{e.message}"
206
+ sleep 2**retries
207
+ retry
208
+ end
209
+ raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
210
+ rescue Exception
211
+ @_es = nil if @reconnect_on_error
212
+ raise
222
213
  end
223
- raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
224
- rescue Exception
225
- @_es = nil if @reconnect_on_error
226
- raise
227
214
  end
228
- end
229
215
 
230
- def eval_or_val(var)
231
- return var unless var.is_a?(String)
232
- eval(var)
233
- end
216
+ def eval_or_val(var)
217
+ return var unless var.is_a?(String)
218
+ eval(var)
219
+ end
234
220
 
235
- def expand_param(param, tag, time, record)
236
- # check for '${ ... }'
237
- # yes => `eval`
238
- # no => return param
239
- return param if (param =~ /\${.+}/).nil?
240
-
241
- # check for 'tag_parts[]'
242
- # separated by a delimiter (default '.')
243
- tag_parts = tag.split(@delimiter) unless (param =~ /tag_parts\[.+\]/).nil? || tag.nil?
244
-
245
- # pull out section between ${} then eval
246
- inner = param.clone
247
- while inner.match(/\${.+}/)
248
- to_eval = inner.match(/\${(.+?)}/){$1}
249
-
250
- if !(to_eval =~ /record\[.+\]/).nil? && record.nil?
251
- return to_eval
252
- elsif !(to_eval =~/tag_parts\[.+\]/).nil? && tag_parts.nil?
253
- return to_eval
254
- elsif !(to_eval =~/time/).nil? && time.nil?
255
- return to_eval
256
- else
257
- inner.sub!(/\${.+?}/, eval( to_eval ))
221
+ def expand_param(param, tag, time, record)
222
+ # check for '${ ... }'
223
+ # yes => `eval`
224
+ # no => return param
225
+ return param if (param =~ /\${.+}/).nil?
226
+
227
+ # check for 'tag_parts[]'
228
+ # separated by a delimiter (default '.')
229
+ tag_parts = tag.split(@delimiter) unless (param =~ /tag_parts\[.+\]/).nil? || tag.nil?
230
+
231
+ # pull out section between ${} then eval
232
+ inner = param.clone
233
+ while inner.match(/\${.+}/)
234
+ to_eval = inner.match(/\${(.+?)}/){$1}
235
+
236
+ if !(to_eval =~ /record\[.+\]/).nil? && record.nil?
237
+ return to_eval
238
+ elsif !(to_eval =~/tag_parts\[.+\]/).nil? && tag_parts.nil?
239
+ return to_eval
240
+ elsif !(to_eval =~/time/).nil? && time.nil?
241
+ return to_eval
242
+ else
243
+ inner.sub!(/\${.+?}/, eval( to_eval ))
244
+ end
258
245
  end
246
+ inner
259
247
  end
260
- inner
261
- end
262
248
 
263
- def is_valid_expand_param_type(param)
264
- return false if [:@buffer_type].include?(param)
265
- return self.instance_variable_get(param).is_a?(String)
266
- end
249
+ def is_valid_expand_param_type(param)
250
+ return false if [:@buffer_type].include?(param)
251
+ return self.instance_variable_get(param).is_a?(String)
252
+ end
267
253
 
268
- def is_existing_connection(host)
269
- # check if the host provided match the current connection
270
- return false if @_es.nil?
271
- return false if @current_config.nil?
272
- return false if host.length != @current_config.length
254
+ def is_existing_connection(host)
255
+ # check if the host provided match the current connection
256
+ return false if @_es.nil?
257
+ return false if host.length != @current_config.length
273
258
 
274
- for i in 0...host.length
275
- if !host[i][:host].eql? @current_config[i][:host] || host[i][:port] != @current_config[i][:port]
276
- return false
259
+ for i in 0...host.length
260
+ if !host[i][:host].eql? @current_config[i][:host] || host[i][:port] != @current_config[i][:port]
261
+ return false
262
+ end
277
263
  end
278
- end
279
264
 
280
- return true
265
+ return true
266
+ end
281
267
  end
282
268
  end