fluent-plugin-elasticsearch 2.10.5 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +3 -0
- data/README.md +10 -0
- data/fluent-plugin-elasticsearch.gemspec +1 -1
- data/lib/fluent/plugin/elasticsearch_error_handler.rb +1 -1
- data/lib/fluent/plugin/out_elasticsearch.rb +25 -2
- data/test/plugin/test_elasticsearch_error_handler.rb +2 -1
- data/test/plugin/test_out_elasticsearch.rb +104 -4
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b98a52f6ea81dcfb963cb072468dfdbbfd0ad23202c9974d1e19032ad3059b56
|
4
|
+
data.tar.gz: 79ab8568a0d34dd71adb9c8b3e1016bdad3cabda102603a3da49cd19f9f15e1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 843a03b70c2e6da558da1674b877b4771bbfe55f8ce444cd2c53a279b47772413c680059a09b57e420638f4bf6b91549f680177ef4881f32d4258c02780de105
|
7
|
+
data.tar.gz: 321d22427a0f2c0770dd48f13ebcc710390a44b908329853cac4f4b7c69e6eda0d31591af507c58b99e71bacbd70f9a9fc80a793a4651336f4c21cf43172ede7
|
data/History.md
CHANGED
data/README.md
CHANGED
@@ -17,6 +17,7 @@ Current maintainers: @cosmo0920
|
|
17
17
|
* [Usage](#usage)
|
18
18
|
+ [Index templates](#index-templates)
|
19
19
|
* [Configuration](#configuration)
|
20
|
+
+ [emit_error_for_missing_id](#emit_error_for_missing_id)
|
20
21
|
+ [hosts](#hosts)
|
21
22
|
+ [user, password, path, scheme, ssl_verify](#user-password-path-scheme-ssl_verify)
|
22
23
|
+ [logstash_format](#logstash_format)
|
@@ -104,6 +105,15 @@ This plugin creates Elasticsearch indices by merely writing to them. Consider us
|
|
104
105
|
|
105
106
|
## Configuration
|
106
107
|
|
108
|
+
### emit_error_for_missing_id
|
109
|
+
|
110
|
+
```
|
111
|
+
emit_error_for_missing_id true
|
112
|
+
```
|
113
|
+
When `write_operation` is configured to anything other then `index`, setting this value to `true` will
|
114
|
+
cause the plugin to `emit_error_event` of any records which do not include an `_id` field. The default (`false`)
|
115
|
+
behavior is to silently drop the records.
|
116
|
+
|
107
117
|
### hosts
|
108
118
|
|
109
119
|
```
|
@@ -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 = '2.
|
6
|
+
s.version = '2.11.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}
|
@@ -32,7 +32,7 @@ class Fluent::Plugin::ElasticsearchErrorHandler
|
|
32
32
|
begin
|
33
33
|
# we need a deep copy for process_message to alter
|
34
34
|
processrecord = Marshal.load(Marshal.dump(rawrecord))
|
35
|
-
@plugin.process_message(tag, meta, header, time, processrecord, bulk_message, extracted_values)
|
35
|
+
next unless @plugin.process_message(tag, meta, header, time, processrecord, bulk_message, extracted_values)
|
36
36
|
rescue => e
|
37
37
|
stats[:bad_chunk_record] += 1
|
38
38
|
next
|
@@ -20,6 +20,10 @@ module Fluent::Plugin
|
|
20
20
|
class ElasticsearchOutput < Output
|
21
21
|
class ConnectionFailure < Fluent::UnrecoverableError; end
|
22
22
|
|
23
|
+
# MissingIdFieldError is raised for records that do not
|
24
|
+
# include the field for the unique record identifier
|
25
|
+
class MissingIdFieldError < StandardError; end
|
26
|
+
|
23
27
|
# RetryStreamError privides a stream to be
|
24
28
|
# put back in the pipeline for cases where a bulk request
|
25
29
|
# failed (e.g some records succeed while others failed)
|
@@ -94,6 +98,7 @@ EOC
|
|
94
98
|
config_param :reconnect_on_error, :bool, :default => false
|
95
99
|
config_param :pipeline, :string, :default => nil
|
96
100
|
config_param :with_transporter_log, :bool, :default => false
|
101
|
+
config_param :emit_error_for_missing_id, :bool, :default => false
|
97
102
|
config_param :content_type, :enum, list: [:"application/json", :"application/x-ndjson"], :default => :"application/json",
|
98
103
|
:deprecated => <<EOC
|
99
104
|
elasticsearch gem v6.0.2 starts to use correct Content-Type. Please upgrade elasticserach gem and stop to use this option.
|
@@ -324,6 +329,13 @@ EOC
|
|
324
329
|
end.join(', ')
|
325
330
|
end
|
326
331
|
|
332
|
+
# append_record_to_messages adds a record to the bulk message
|
333
|
+
# payload to be submitted to Elasticsearch. Records that do
|
334
|
+
# not include '_id' field are skipped when 'write_operation'
|
335
|
+
# is configured for 'create' or 'update'
|
336
|
+
#
|
337
|
+
# returns 'true' if record was appended to the bulk message
|
338
|
+
# and 'false' otherwise
|
327
339
|
def append_record_to_messages(op, meta, header, record, msgs)
|
328
340
|
case op
|
329
341
|
when UPDATE_OP, UPSERT_OP
|
@@ -331,18 +343,22 @@ EOC
|
|
331
343
|
header[UPDATE_OP] = meta
|
332
344
|
msgs << @dump_proc.call(header) << BODY_DELIMITER
|
333
345
|
msgs << @dump_proc.call(update_body(record, op)) << BODY_DELIMITER
|
346
|
+
return true
|
334
347
|
end
|
335
348
|
when CREATE_OP
|
336
349
|
if meta.has_key?(ID_FIELD)
|
337
350
|
header[CREATE_OP] = meta
|
338
351
|
msgs << @dump_proc.call(header) << BODY_DELIMITER
|
339
352
|
msgs << @dump_proc.call(record) << BODY_DELIMITER
|
353
|
+
return true
|
340
354
|
end
|
341
355
|
when INDEX_OP
|
342
356
|
header[INDEX_OP] = meta
|
343
357
|
msgs << @dump_proc.call(header) << BODY_DELIMITER
|
344
358
|
msgs << @dump_proc.call(record) << BODY_DELIMITER
|
359
|
+
return true
|
345
360
|
end
|
361
|
+
return false
|
346
362
|
end
|
347
363
|
|
348
364
|
def update_body(record, op)
|
@@ -406,8 +422,15 @@ EOC
|
|
406
422
|
chunk.msgpack_each do |time, record|
|
407
423
|
next unless record.is_a? Hash
|
408
424
|
begin
|
409
|
-
process_message(tag, meta, header, time, record, bulk_message, extracted_values)
|
410
|
-
|
425
|
+
if process_message(tag, meta, header, time, record, bulk_message, extracted_values)
|
426
|
+
bulk_message_count += 1
|
427
|
+
else
|
428
|
+
if @emit_error_for_missing_id
|
429
|
+
raise MissingIdFieldError, "Missing '_id' field. Write operation is #{@write_operation}"
|
430
|
+
else
|
431
|
+
log.on_debug { log.debug("Dropping record because its missing an '_id' field and write_operation is #{@write_operation}: #{record}") }
|
432
|
+
end
|
433
|
+
end
|
411
434
|
rescue => e
|
412
435
|
router.emit_error_event(tag, time, record, e)
|
413
436
|
end
|
@@ -26,6 +26,7 @@ class TestElasticsearchErrorHandler < Test::Unit::TestCase
|
|
26
26
|
if record.has_key?('raise') && record['raise']
|
27
27
|
raise Exception('process_message')
|
28
28
|
end
|
29
|
+
return true
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -54,7 +55,7 @@ class TestElasticsearchErrorHandler < Test::Unit::TestCase
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def test_dlq_400_responses
|
57
|
-
records = [{time: 123, record: {"foo" => "bar"}}]
|
58
|
+
records = [{time: 123, record: {"foo" => "bar", '_id' => 'abc'}}]
|
58
59
|
response = parse_response(%({
|
59
60
|
"took" : 0,
|
60
61
|
"errors" : true,
|
@@ -41,8 +41,8 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
41
41
|
Fluent::Plugin::ElasticsearchOutput::DEFAULT_TYPE_NAME
|
42
42
|
end
|
43
43
|
|
44
|
-
def sample_record
|
45
|
-
{'age' => 26, 'request_id' => '42', 'parent_id' => 'parent', 'routing_id' => 'routing'}
|
44
|
+
def sample_record(content={})
|
45
|
+
{'age' => 26, 'request_id' => '42', 'parent_id' => 'parent', 'routing_id' => 'routing'}.merge(content)
|
46
46
|
end
|
47
47
|
|
48
48
|
def nested_sample_record
|
@@ -178,9 +178,9 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
178
178
|
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' } } })
|
179
179
|
end
|
180
180
|
|
181
|
-
def assert_logs_include(logs, msg)
|
181
|
+
def assert_logs_include(logs, msg, exp_matches=1)
|
182
182
|
matches = logs.grep /#{msg}/
|
183
|
-
assert_equal(
|
183
|
+
assert_equal(exp_matches, matches.length, "Logs do not contain '#{msg}' '#{logs}'")
|
184
184
|
end
|
185
185
|
|
186
186
|
def test_configure
|
@@ -1799,6 +1799,106 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
1799
1799
|
assert_equal [['retry', 1, sample_record]], driver.events
|
1800
1800
|
end
|
1801
1801
|
|
1802
|
+
def test_create_should_write_records_with_ids_and_skip_those_without
|
1803
|
+
driver.configure("write_operation create\nid_key my_id\n@log_level debug")
|
1804
|
+
stub_elastic_ping
|
1805
|
+
stub_request(:post, 'http://localhost:9200/_bulk')
|
1806
|
+
.to_return(lambda do |req|
|
1807
|
+
{ :status => 200,
|
1808
|
+
:headers => { 'Content-Type' => 'json' },
|
1809
|
+
:body => %({
|
1810
|
+
"took" : 1,
|
1811
|
+
"errors" : true,
|
1812
|
+
"items" : [
|
1813
|
+
{
|
1814
|
+
"create" : {
|
1815
|
+
"_index" : "foo",
|
1816
|
+
"_type" : "bar",
|
1817
|
+
"_id" : "abc"
|
1818
|
+
}
|
1819
|
+
},
|
1820
|
+
{
|
1821
|
+
"create" : {
|
1822
|
+
"_index" : "foo",
|
1823
|
+
"_type" : "bar",
|
1824
|
+
"_id" : "xyz",
|
1825
|
+
"status" : 500,
|
1826
|
+
"error" : {
|
1827
|
+
"type" : "some unrecognized type",
|
1828
|
+
"reason":"some error to cause version mismatch"
|
1829
|
+
}
|
1830
|
+
}
|
1831
|
+
}
|
1832
|
+
]
|
1833
|
+
})
|
1834
|
+
}
|
1835
|
+
end)
|
1836
|
+
sample_record1 = sample_record('my_id' => 'abc')
|
1837
|
+
sample_record4 = sample_record('my_id' => 'xyz')
|
1838
|
+
|
1839
|
+
driver.run(default_tag: 'test') do
|
1840
|
+
driver.feed(1, sample_record1)
|
1841
|
+
driver.feed(2, sample_record)
|
1842
|
+
driver.feed(3, sample_record)
|
1843
|
+
driver.feed(4, sample_record4)
|
1844
|
+
end
|
1845
|
+
|
1846
|
+
logs = driver.logs
|
1847
|
+
# one record succeeded while the other should be 'retried'
|
1848
|
+
assert_equal [['test', 4, sample_record4]], driver.events
|
1849
|
+
assert_logs_include(logs, /(Dropping record)/, 2)
|
1850
|
+
end
|
1851
|
+
|
1852
|
+
def test_create_should_write_records_with_ids_and_emit_those_without
|
1853
|
+
driver.configure("write_operation create\nid_key my_id\nemit_error_for_missing_id true\n@log_level debug")
|
1854
|
+
stub_elastic_ping
|
1855
|
+
stub_request(:post, 'http://localhost:9200/_bulk')
|
1856
|
+
.to_return(lambda do |req|
|
1857
|
+
{ :status => 200,
|
1858
|
+
:headers => { 'Content-Type' => 'json' },
|
1859
|
+
:body => %({
|
1860
|
+
"took" : 1,
|
1861
|
+
"errors" : true,
|
1862
|
+
"items" : [
|
1863
|
+
{
|
1864
|
+
"create" : {
|
1865
|
+
"_index" : "foo",
|
1866
|
+
"_type" : "bar",
|
1867
|
+
"_id" : "abc"
|
1868
|
+
}
|
1869
|
+
},
|
1870
|
+
{
|
1871
|
+
"create" : {
|
1872
|
+
"_index" : "foo",
|
1873
|
+
"_type" : "bar",
|
1874
|
+
"_id" : "xyz",
|
1875
|
+
"status" : 500,
|
1876
|
+
"error" : {
|
1877
|
+
"type" : "some unrecognized type",
|
1878
|
+
"reason":"some error to cause version mismatch"
|
1879
|
+
}
|
1880
|
+
}
|
1881
|
+
}
|
1882
|
+
]
|
1883
|
+
})
|
1884
|
+
}
|
1885
|
+
end)
|
1886
|
+
sample_record1 = sample_record('my_id' => 'abc')
|
1887
|
+
sample_record4 = sample_record('my_id' => 'xyz')
|
1888
|
+
|
1889
|
+
driver.run(default_tag: 'test') do
|
1890
|
+
driver.feed(1, sample_record1)
|
1891
|
+
driver.feed(2, sample_record)
|
1892
|
+
driver.feed(3, sample_record)
|
1893
|
+
driver.feed(4, sample_record4)
|
1894
|
+
end
|
1895
|
+
|
1896
|
+
error_log = driver.error_events.map {|e| e.last.message }
|
1897
|
+
# one record succeeded while the other should be 'retried'
|
1898
|
+
assert_equal [['test', 4, sample_record4]], driver.events
|
1899
|
+
assert_logs_include(error_log, /(Missing '_id' field)/, 2)
|
1900
|
+
end
|
1901
|
+
|
1802
1902
|
def test_bulk_error
|
1803
1903
|
stub_elastic_ping
|
1804
1904
|
stub_request(:post, 'http://localhost:9200/_bulk')
|