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.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/.travis.yml +0 -1
- data/History.md +4 -96
- data/README.md +23 -162
- data/fluent-plugin-elasticsearch.gemspec +5 -5
- data/lib/fluent/plugin/elasticsearch_index_template.rb +3 -8
- data/lib/fluent/plugin/out_elasticsearch.rb +314 -420
- data/lib/fluent/plugin/out_elasticsearch_dynamic.rb +206 -220
- data/test/plugin/test_out_elasticsearch.rb +303 -806
- data/test/plugin/test_out_elasticsearch_dynamic.rb +180 -257
- metadata +13 -24
- data/Gemfile.v0.12 +0 -11
- data/lib/fluent/log-ext.rb +0 -38
- data/lib/fluent/plugin/elasticsearch_constants.rb +0 -11
- data/lib/fluent/plugin/elasticsearch_error_handler.rb +0 -89
- data/lib/fluent/plugin/elasticsearch_simple_sniffer.rb +0 -10
- data/lib/fluent/plugin/filter_elasticsearch_genid.rb +0 -25
- data/test/plugin/test_elasticsearch_error_handler.rb +0 -264
- data/test/plugin/test_filter_elasticsearch_genid.rb +0 -40
- data/test/test_log-ext.rb +0 -33
@@ -1,136 +1,129 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require_relative 'out_elasticsearch'
|
3
3
|
|
4
|
-
|
4
|
+
module Fluent::Plugin
|
5
|
+
class ElasticsearchOutputDynamic < ElasticsearchOutput
|
5
6
|
|
6
|
-
|
7
|
+
Fluent::Plugin.register_output('elasticsearch_dynamic', self)
|
7
8
|
|
8
|
-
|
9
|
+
config_param :delimiter, :string, :default => "."
|
9
10
|
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
+
attr_reader :dynamic_config
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
def configure(conf)
|
17
|
+
super
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
30
|
+
def create_meta_config_map
|
31
|
+
{'id_key' => '_id', 'parent_key' => '_parent', 'routing_key' => '_routing'}
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
},
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
98
|
+
{
|
99
|
+
hosts: hosts
|
100
|
+
}
|
101
|
+
end
|
106
102
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
111
|
+
def write(chunk)
|
112
|
+
bulk_message = Hash.new { |h,k| h[k] = '' }
|
113
|
+
dynamic_conf = @dynamic_config.clone
|
118
114
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
115
|
+
headers = {
|
116
|
+
UPDATE_OP => {},
|
117
|
+
UPSERT_OP => {},
|
118
|
+
CREATE_OP => {},
|
119
|
+
INDEX_OP => {}
|
120
|
+
}
|
125
121
|
|
126
|
-
|
127
|
-
next unless record.is_a? Hash
|
122
|
+
tag = chunk.metadata.tag
|
128
123
|
|
129
|
-
|
130
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
-
|
155
|
+
target_index = dynamic_conf['index_name']
|
159
156
|
end
|
160
|
-
end
|
161
157
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
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
|
-
|
183
|
-
|
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
|
-
|
189
|
-
|
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
|
-
|
195
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
retries
|
218
|
-
|
219
|
-
|
220
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
216
|
+
def eval_or_val(var)
|
217
|
+
return var unless var.is_a?(String)
|
218
|
+
eval(var)
|
219
|
+
end
|
234
220
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
-
|
275
|
-
|
276
|
-
|
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
|
-
|
265
|
+
return true
|
266
|
+
end
|
281
267
|
end
|
282
268
|
end
|