fluent-plugin-elasticsearch 1.13.4 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/History.md +3 -0
- data/README.md +12 -0
- data/fluent-plugin-elasticsearch.gemspec +1 -1
- data/lib/fluent/plugin/dead_letter_queue_drop_handler.rb +10 -0
- data/lib/fluent/plugin/dead_letter_queue_file_handler.rb +14 -0
- data/lib/fluent/plugin/out_elasticsearch.rb +101 -55
- data/test/plugin/test_out_elasticsearch.rb +62 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 995e9b0bb1ae8dc5fbf60b80c6d038c0b1f5b696aa4e27842ab23b1609eeef74
|
4
|
+
data.tar.gz: c1b249d0d02c7381b0a115fccad53e68ea170906101913c36a2b52c294bb3275
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 158848be63d3b28213856a45026b65e769e480285b43400a5600a7f5e7d01aed24f44bbbc9fad60d0530f64dad2717cc90d57c0ac84f75c1f46c3f439ab56a5f
|
7
|
+
data.tar.gz: 223f97c244f7488f364d3a553e44b0d1fddf4c3f9c93545dd696e84e6a2bfd92abc8565e42ce7b5c402e4ca9bca242b6ac66a88337165579b6b818a7d3261ff2
|
data/.gitignore
CHANGED
data/History.md
CHANGED
data/README.md
CHANGED
@@ -49,6 +49,7 @@ Note: For Amazon Elasticsearch Service please consider using [fluent-plugin-aws-
|
|
49
49
|
+ [time_parse_error_tag](#time_parse_error_tag)
|
50
50
|
+ [reconnect_on_error](#reconnect_on_error)
|
51
51
|
+ [with_transporter_log](#with_transporter_log)
|
52
|
+
+ [dlq_handler](#dlq_handler)
|
52
53
|
+ [Client/host certificate options](#clienthost-certificate-options)
|
53
54
|
+ [Proxy Support](#proxy-support)
|
54
55
|
+ [Buffered output options](#buffered-output-options)
|
@@ -474,6 +475,17 @@ We recommend to set this true if you start to debug this plugin.
|
|
474
475
|
with_transporter_log true
|
475
476
|
```
|
476
477
|
|
478
|
+
### dlq_handler
|
479
|
+
Adds an error handler for processing corrupt messages from message buffers.
|
480
|
+
There are [known cases](https://bugzilla.redhat.com/show_bug.cgi?id=1562004) where
|
481
|
+
fluentd is stuck processing messages because the file buffer is corrupt. Fluentd
|
482
|
+
is unable to clear faulty buffer chunks.
|
483
|
+
|
484
|
+
```
|
485
|
+
dlq_handler {'type':'drop'} #default is to log and drop messages
|
486
|
+
dlq_handler {'type':'file', 'dir':'/tmp/fluentd/dlq', 'max_files':5, 'max_file_size':104857600}
|
487
|
+
```
|
488
|
+
|
477
489
|
### Client/host certificate options
|
478
490
|
|
479
491
|
Need to verify Elasticsearch's certificate? You can use the following parameter to specify a CA instead of using an environment variable.
|
@@ -3,7 +3,7 @@ $:.push File.expand_path('../lib', __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = 'fluent-plugin-elasticsearch'
|
6
|
-
s.version = '1.
|
6
|
+
s.version = '1.14.0'
|
7
7
|
s.authors = ['diogo', 'pitr']
|
8
8
|
s.email = ['pitr.vern@gmail.com', 'me@diogoterror.com']
|
9
9
|
s.description = %q{Elasticsearch output plugin for Fluent event collector}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
|
2
|
+
module Fluent::DeadLetterQueueDropHandler
|
3
|
+
def handle_chunk_error(out_plugin, tag, error, time, record)
|
4
|
+
begin
|
5
|
+
log.error("Dropping record from '#{tag}': error:#{error} time:#{time} record:#{record}")
|
6
|
+
rescue=>e
|
7
|
+
log.error("Error while trying to log and drop message from chunk '#{tag}' #{e.message}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Fluent::DeadLetterQueueFileHandler
|
4
|
+
|
5
|
+
def handle_chunk_error(out_plugin, tag, error, time, record)
|
6
|
+
begin
|
7
|
+
@dlq_file.info({processed_at: Time.now.utc, tag: tag, error: "#{error.message}", time: time, record: record}.to_json)
|
8
|
+
rescue=>e
|
9
|
+
log.error("Error while trying to log and drop message from chunk '#{tag}' #{e.message}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
@@ -69,6 +69,7 @@ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
|
|
69
69
|
config_param :reconnect_on_error, :bool, :default => false
|
70
70
|
config_param :pipeline, :string, :default => nil
|
71
71
|
config_param :with_transporter_log, :bool, :default => false
|
72
|
+
config_param :dlq_handler, :hash, :default => { 'type' =>'drop' }
|
72
73
|
|
73
74
|
include Fluent::ElasticsearchIndexTemplate
|
74
75
|
include Fluent::ElasticsearchConstants
|
@@ -129,6 +130,44 @@ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
|
|
129
130
|
log_level = conf['@log_level'] || conf['log_level']
|
130
131
|
log.warn "Consider to specify log_level with @log_level." unless log_level
|
131
132
|
end
|
133
|
+
|
134
|
+
configure_dlq_handler
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
def configure_dlq_handler
|
139
|
+
dlq_type = @dlq_handler && @dlq_handler.is_a?(Hash) ? dlq_type = @dlq_handler['type'] : nil
|
140
|
+
return unless dlq_type
|
141
|
+
|
142
|
+
case dlq_type.downcase
|
143
|
+
when 'drop'
|
144
|
+
log.info('Configuring the DROP dead letter queue handler')
|
145
|
+
require_relative 'dead_letter_queue_drop_handler'
|
146
|
+
extend Fluent::DeadLetterQueueDropHandler
|
147
|
+
when 'file'
|
148
|
+
log.info("Configuring the File dead letter queue handler: ")
|
149
|
+
dir = @dlq_handler ['dir'] || '/var/lib/fluentd/dlq'
|
150
|
+
shift_age = @dlq_handler['max_files'] || 0
|
151
|
+
shift_size = @dlq_handler['max_file_size'] || 1048576
|
152
|
+
log.info("Configuring the File dead letter queue handler: ")
|
153
|
+
log.info(" Directory: #{dir}")
|
154
|
+
log.info(" Max number of DLQ files: #{shift_age}")
|
155
|
+
log.info(" Max file size: #{shift_size}")
|
156
|
+
unless Dir.exists?(dir)
|
157
|
+
Dir.mkdir(dir)
|
158
|
+
log.info("Created DLQ directory: '#{dir}'")
|
159
|
+
end
|
160
|
+
require 'logger'
|
161
|
+
require 'json'
|
162
|
+
file = File.join(dir, 'dlq')
|
163
|
+
@dlq_file = Logger.new(file, shift_age, shift_size)
|
164
|
+
@dlq_file.level = Logger::INFO
|
165
|
+
@dlq_file.formatter = proc { |severity, datetime, progname, msg| "#{msg.dump}\n" }
|
166
|
+
log.info ("Created DLQ file #{file}")
|
167
|
+
|
168
|
+
require_relative 'dead_letter_queue_file_handler'
|
169
|
+
extend Fluent::DeadLetterQueueFileHandler
|
170
|
+
end
|
132
171
|
end
|
133
172
|
|
134
173
|
def create_meta_config_map
|
@@ -321,76 +360,83 @@ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
|
|
321
360
|
chunk.msgpack_each do |time, record|
|
322
361
|
@error.records += 1
|
323
362
|
next unless record.is_a? Hash
|
324
|
-
|
325
|
-
|
326
|
-
|
363
|
+
begin
|
364
|
+
process_message(tag, meta, header, time, record, bulk_message)
|
365
|
+
rescue=>e
|
366
|
+
handle_chunk_error(self, tag, e, time, record)
|
327
367
|
end
|
368
|
+
end
|
328
369
|
|
329
|
-
|
330
|
-
|
331
|
-
|
370
|
+
send_bulk(bulk_message) unless bulk_message.empty?
|
371
|
+
bulk_message.clear
|
372
|
+
end
|
332
373
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
dt = parse_time(rts, time, tag)
|
338
|
-
elsif record.has_key?(@time_key)
|
339
|
-
rts = record[@time_key]
|
340
|
-
dt = parse_time(rts, time, tag)
|
341
|
-
record[TIMESTAMP_FIELD] = rts unless @time_key_exclude_timestamp
|
342
|
-
else
|
343
|
-
dt = Time.at(time).to_datetime
|
344
|
-
record[TIMESTAMP_FIELD] = dt.iso8601(@time_precision)
|
345
|
-
end
|
346
|
-
end
|
374
|
+
def process_message(tag, meta, header, time, record, bulk_message)
|
375
|
+
if @flatten_hashes
|
376
|
+
record = flatten_record(record)
|
377
|
+
end
|
347
378
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
379
|
+
if @hash_config
|
380
|
+
record = generate_hash_id_key(record)
|
381
|
+
end
|
382
|
+
|
383
|
+
dt = nil
|
384
|
+
if @logstash_format || @include_timestamp
|
385
|
+
if record.has_key?(TIMESTAMP_FIELD)
|
386
|
+
rts = record[TIMESTAMP_FIELD]
|
387
|
+
dt = parse_time(rts, time, tag)
|
388
|
+
elsif record.has_key?(@time_key)
|
389
|
+
rts = record[@time_key]
|
390
|
+
dt = parse_time(rts, time, tag)
|
391
|
+
record[TIMESTAMP_FIELD] = rts unless @time_key_exclude_timestamp
|
354
392
|
else
|
355
|
-
|
393
|
+
dt = Time.at(time).to_datetime
|
394
|
+
record[TIMESTAMP_FIELD] = dt.iso8601(@time_precision)
|
356
395
|
end
|
396
|
+
end
|
357
397
|
|
358
|
-
|
359
|
-
|
360
|
-
target_index =
|
361
|
-
|
362
|
-
|
363
|
-
|
398
|
+
target_index_parent, target_index_child_key = @target_index_key ? get_parent_of(record, @target_index_key) : nil
|
399
|
+
if target_index_parent && target_index_parent[target_index_child_key]
|
400
|
+
target_index = target_index_parent.delete(target_index_child_key)
|
401
|
+
elsif @logstash_format
|
402
|
+
dt = dt.new_offset(0) if @utc_index
|
403
|
+
target_index = "#{@logstash_prefix}#{@logstash_prefix_separator}#{dt.strftime(@logstash_dateformat)}"
|
404
|
+
else
|
405
|
+
target_index = @index_name
|
406
|
+
end
|
364
407
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
408
|
+
# Change target_index to lower-case since Elasticsearch doesn't
|
409
|
+
# allow upper-case characters in index names.
|
410
|
+
target_index = target_index.downcase
|
411
|
+
if @include_tag_key
|
412
|
+
record[@tag_key] = tag
|
413
|
+
end
|
371
414
|
|
372
|
-
|
373
|
-
|
374
|
-
|
415
|
+
target_type_parent, target_type_child_key = @target_type_key ? get_parent_of(record, @target_type_key) : nil
|
416
|
+
if target_type_parent && target_type_parent[target_type_child_key]
|
417
|
+
target_type = target_type_parent.delete(target_type_child_key)
|
418
|
+
else
|
419
|
+
target_type = @type_name
|
420
|
+
end
|
375
421
|
|
376
|
-
|
377
|
-
|
378
|
-
|
422
|
+
meta.clear
|
423
|
+
meta["_index".freeze] = target_index
|
424
|
+
meta["_type".freeze] = target_type
|
379
425
|
|
380
|
-
|
381
|
-
|
382
|
-
|
426
|
+
if @pipeline
|
427
|
+
meta["pipeline".freeze] = @pipeline
|
428
|
+
end
|
383
429
|
|
384
|
-
|
385
|
-
|
386
|
-
|
430
|
+
@meta_config_map.each do |record_key, meta_key|
|
431
|
+
meta[meta_key] = record[record_key] if record[record_key]
|
432
|
+
end
|
387
433
|
|
388
|
-
|
389
|
-
@
|
434
|
+
if @remove_keys
|
435
|
+
@remove_keys.each { |key| record.delete(key) }
|
390
436
|
end
|
391
437
|
|
392
|
-
|
393
|
-
|
438
|
+
append_record_to_messages(@write_operation, meta, header, record, bulk_message)
|
439
|
+
@error.bulk_message_count += 1
|
394
440
|
end
|
395
441
|
|
396
442
|
# returns [parent, child_key] of child described by path array in record's tree
|
@@ -37,7 +37,7 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
37
37
|
|
38
38
|
def stub_elastic(url="http://localhost:9200/_bulk")
|
39
39
|
stub_request(:post, url).with do |req|
|
40
|
-
|
40
|
+
@index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -181,6 +181,11 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
181
181
|
stub_request(:post, url).to_return(lambda { |req| bodystr = make_response_body(req, 0, 500, error); body = JSON.parse(bodystr); body['items'][0]['unknown'] = body['items'][0].delete('create'); { :status => 200, :body => body.to_json, :headers => { 'Content-Type' => 'json' } } })
|
182
182
|
end
|
183
183
|
|
184
|
+
def assert_logs_include(logs, msg)
|
185
|
+
matches = logs.grep /#{msg}/
|
186
|
+
assert_equal(1, matches.length, "Logs do not contain '#{msg}' '#{logs}'")
|
187
|
+
end
|
188
|
+
|
184
189
|
def test_configure
|
185
190
|
config = %{
|
186
191
|
host logs.google.com
|
@@ -203,6 +208,38 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
203
208
|
assert_nil instance.client_cert
|
204
209
|
assert_nil instance.client_key_pass
|
205
210
|
assert_false instance.with_transporter_log
|
211
|
+
assert_not_nil instance.dlq_handler
|
212
|
+
assert_equal 'drop', instance.dlq_handler['type']
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_configure_with_dlq_file_handler
|
216
|
+
require 'tmpdir'
|
217
|
+
dir = Dir.mktmpdir
|
218
|
+
config = %Q{
|
219
|
+
host logs.google.com
|
220
|
+
port 777
|
221
|
+
scheme https
|
222
|
+
path /es/
|
223
|
+
user john
|
224
|
+
password doe
|
225
|
+
dlq_handler {"type":"file", "dir":"#{dir}"}
|
226
|
+
}
|
227
|
+
instance = driver('test', config).instance
|
228
|
+
|
229
|
+
assert_equal 'logs.google.com', instance.host
|
230
|
+
assert_equal 777, instance.port
|
231
|
+
assert_equal 'https', instance.scheme
|
232
|
+
assert_equal '/es/', instance.path
|
233
|
+
assert_equal 'john', instance.user
|
234
|
+
assert_equal 'doe', instance.password
|
235
|
+
assert_equal :TLSv1, instance.ssl_version
|
236
|
+
assert_nil instance.client_key
|
237
|
+
assert_nil instance.client_cert
|
238
|
+
assert_nil instance.client_key_pass
|
239
|
+
assert_false instance.with_transporter_log
|
240
|
+
assert_not_nil instance.dlq_handler
|
241
|
+
assert_equal 'file', instance.dlq_handler['type']
|
242
|
+
assert_true Dir.exists?(dir)
|
206
243
|
end
|
207
244
|
|
208
245
|
def test_template_already_present
|
@@ -592,6 +629,30 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
592
629
|
assert_requested(elastic_request)
|
593
630
|
end
|
594
631
|
|
632
|
+
def test_write_message_with_dlq_drop_handler
|
633
|
+
driver.configure("target_index_key bad_value\n")
|
634
|
+
log = driver.instance.router.emit_error_handler.log
|
635
|
+
stub_elastic_ping
|
636
|
+
stub_elastic
|
637
|
+
driver.emit({'bad_value'=>"\255"})
|
638
|
+
driver.run
|
639
|
+
assert_logs_include(log.out.logs, 'Dropping')
|
640
|
+
end
|
641
|
+
|
642
|
+
def test_write_message_with_dlq_file_handler
|
643
|
+
log = driver.instance.router.emit_error_handler.log
|
644
|
+
dir = Dir.mktmpdir
|
645
|
+
driver.configure("dlq_handler {\"type\":\"file\", \"dir\":\"#{dir}\"}\n
|
646
|
+
target_index_key bad_value\n
|
647
|
+
")
|
648
|
+
stub_elastic_ping
|
649
|
+
stub_elastic
|
650
|
+
driver.emit({'bad_value'=>"\255"})
|
651
|
+
driver.run
|
652
|
+
logs = File.readlines(File.join(dir,'dlq'))
|
653
|
+
assert_logs_include(logs, 'invalid')
|
654
|
+
end
|
655
|
+
|
595
656
|
def test_writes_to_default_index
|
596
657
|
stub_elastic_ping
|
597
658
|
stub_elastic
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-elasticsearch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- diogo
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-04-
|
12
|
+
date: 2018-04-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fluentd
|
@@ -144,6 +144,8 @@ files:
|
|
144
144
|
- README.md
|
145
145
|
- Rakefile
|
146
146
|
- fluent-plugin-elasticsearch.gemspec
|
147
|
+
- lib/fluent/plugin/dead_letter_queue_drop_handler.rb
|
148
|
+
- lib/fluent/plugin/dead_letter_queue_file_handler.rb
|
147
149
|
- lib/fluent/plugin/elasticsearch_constants.rb
|
148
150
|
- lib/fluent/plugin/elasticsearch_error_handler.rb
|
149
151
|
- lib/fluent/plugin/elasticsearch_index_template.rb
|