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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35b11b6ba12d6bfa22a084db14fd4503d2f16769dc9db7d30d22dafa3dbad035
4
- data.tar.gz: a0812a380eb81188fa705924ae51df7bbc773b0d565469f48540807bc323ec33
3
+ metadata.gz: 995e9b0bb1ae8dc5fbf60b80c6d038c0b1f5b696aa4e27842ab23b1609eeef74
4
+ data.tar.gz: c1b249d0d02c7381b0a115fccad53e68ea170906101913c36a2b52c294bb3275
5
5
  SHA512:
6
- metadata.gz: 942a5d5e57f7fa81697cdc13ad0024ab40df80d7a2a45a1108cd9acb1909ad87c5b0e4e220ce4df91d35949a12ae413fb0209c59d6f8ddb908beefa2c257799b
7
- data.tar.gz: f4f4d3d3c1e0cb2800a31516052a5ca3aecd5ba0c451bcb3dcbf0e91d7e492bbf8da4caf4f1871d24e898276fb8758c76ae52813ade2ab6884bf061de73cff63
6
+ metadata.gz: 158848be63d3b28213856a45026b65e769e480285b43400a5600a7f5e7d01aed24f44bbbc9fad60d0530f64dad2717cc90d57c0ac84f75c1f46c3f439ab56a5f
7
+ data.tar.gz: 223f97c244f7488f364d3a553e44b0d1fddf4c3f9c93545dd696e84e6a2bfd92abc8565e42ce7b5c402e4ca9bca242b6ac66a88337165579b6b818a7d3261ff2
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  .DS_Store
19
+ vendor/
data/History.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ### [Unreleased]
4
4
 
5
+ ### 1.14.0
6
+ - introduce dead letter queue to handle issues unpacking file buffer chunks (#398)
7
+
5
8
  ### 1.13.4
6
9
  - backport auth: Fix missing auth tokens after reloading connections (#397)
7
10
 
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.13.4'
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
- if @flatten_hashes
326
- record = flatten_record(record)
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
- if @hash_config
330
- record = generate_hash_id_key(record)
331
- end
370
+ send_bulk(bulk_message) unless bulk_message.empty?
371
+ bulk_message.clear
372
+ end
332
373
 
333
- dt = nil
334
- if @logstash_format || @include_timestamp
335
- if record.has_key?(TIMESTAMP_FIELD)
336
- rts = record[TIMESTAMP_FIELD]
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
- target_index_parent, target_index_child_key = @target_index_key ? get_parent_of(record, @target_index_key) : nil
349
- if target_index_parent && target_index_parent[target_index_child_key]
350
- target_index = target_index_parent.delete(target_index_child_key)
351
- elsif @logstash_format
352
- dt = dt.new_offset(0) if @utc_index
353
- target_index = "#{@logstash_prefix}#{@logstash_prefix_separator}#{dt.strftime(@logstash_dateformat)}"
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
- target_index = @index_name
393
+ dt = Time.at(time).to_datetime
394
+ record[TIMESTAMP_FIELD] = dt.iso8601(@time_precision)
356
395
  end
396
+ end
357
397
 
358
- # Change target_index to lower-case since Elasticsearch doesn't
359
- # allow upper-case characters in index names.
360
- target_index = target_index.downcase
361
- if @include_tag_key
362
- record[@tag_key] = tag
363
- end
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
- target_type_parent, target_type_child_key = @target_type_key ? get_parent_of(record, @target_type_key) : nil
366
- if target_type_parent && target_type_parent[target_type_child_key]
367
- target_type = target_type_parent.delete(target_type_child_key)
368
- else
369
- target_type = @type_name
370
- end
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
- meta.clear
373
- meta["_index".freeze] = target_index
374
- meta["_type".freeze] = target_type
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
- if @pipeline
377
- meta["pipeline".freeze] = @pipeline
378
- end
422
+ meta.clear
423
+ meta["_index".freeze] = target_index
424
+ meta["_type".freeze] = target_type
379
425
 
380
- @meta_config_map.each do |record_key, meta_key|
381
- meta[meta_key] = record[record_key] if record[record_key]
382
- end
426
+ if @pipeline
427
+ meta["pipeline".freeze] = @pipeline
428
+ end
383
429
 
384
- if @remove_keys
385
- @remove_keys.each { |key| record.delete(key) }
386
- end
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
- append_record_to_messages(@write_operation, meta, header, record, bulk_message)
389
- @error.bulk_message_count += 1
434
+ if @remove_keys
435
+ @remove_keys.each { |key| record.delete(key) }
390
436
  end
391
437
 
392
- send_bulk(bulk_message) unless bulk_message.empty?
393
- bulk_message.clear
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
- @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
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.13.4
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-10 00:00:00.000000000 Z
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