fluent-plugin-elasticsearch 1.13.4 → 1.14.0
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 +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
|