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

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.
@@ -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