fluent-plugin-elasticsearch 1.9.0.rc.1 → 1.9.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/History.md +4 -0
- data/README.md +17 -0
- data/fluent-plugin-elasticsearch.gemspec +1 -1
- data/lib/fluent/plugin/out_elasticsearch.rb +31 -38
- data/lib/fluent/plugin/out_elasticsearch_dynamic.rb +4 -0
- data/test/plugin/test_out_elasticsearch.rb +63 -11
- data/test/plugin/test_out_elasticsearch_dynamic.rb +48 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b756e1f1c69c5e12941b6d96242c64a09553e65
|
4
|
+
data.tar.gz: b9153e7eaf0b4b3532dd89eb9f7c2cb5f58533a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 942deb8934cecb911d1f1751760ac1484bc303bbb03079953c66e0eff9a24644c7a0df1517af5436ceca4bc0afed068b4d8a502250492be400e1e412332ddc47
|
7
|
+
data.tar.gz: a4cc8d9c20624d9773859cf1546a446e575f4486ec8f80d27dbfa323108c183e2c39cf75650c31cf96fb4c447469227d936682b2311c07090e5f5a500d191670
|
data/History.md
CHANGED
data/README.md
CHANGED
@@ -41,6 +41,8 @@ Note: For Amazon Elasticsearch Service please consider using [fluent-plugin-aws-
|
|
41
41
|
+ [remove_keys_on_update](#remove_keys_on_update)
|
42
42
|
+ [remove_keys_on_update_key](#remove_keys_on_update_key)
|
43
43
|
+ [write_operation](#write_operation)
|
44
|
+
+ [time_parse_error_tag](#time_parse_error_tag)
|
45
|
+
+ [reconnect_on_error](#reconnect_on_error)
|
44
46
|
+ [Client/host certificate options](#clienthost-certificate-options)
|
45
47
|
+ [Proxy Support](#proxy-support)
|
46
48
|
+ [Buffered output options](#buffered-output-options)
|
@@ -392,6 +394,21 @@ The write_operation can be any of:
|
|
392
394
|
|
393
395
|
**Please note, id is required in create, update, and upsert scenario. Without id, the message will be dropped.**
|
394
396
|
|
397
|
+
### time_parse_error_tag
|
398
|
+
|
399
|
+
With `logstash_format true`, elasticsearch plugin parses timestamp field for generating index name. If the record has invalid timestamp value, this plugin emits an error event to `@ERROR` label with `time_parse_error_tag` configured tag.
|
400
|
+
|
401
|
+
Default value is `Fluent::ElasticsearchOutput::TimeParser.error` for backward compatibility. `::` separated tag is not good for tag routing because some plugins assume tag is separated by `.`. We recommend to set this parameter like `time_parse_error_tag es_plugin.output.time.error`.
|
402
|
+
We will change default value to `.` separated tag.
|
403
|
+
|
404
|
+
### reconnect_on_error
|
405
|
+
Indicates that the plugin should reset connection on any error (reconnect on next send).
|
406
|
+
By default it will reconnect only on "host unreachable exceptions".
|
407
|
+
We recommended to set this true in the presence of elasticsearch shield.
|
408
|
+
```
|
409
|
+
reconnect_on_error true # defaults to false
|
410
|
+
```
|
411
|
+
|
395
412
|
### Client/host certificate options
|
396
413
|
|
397
414
|
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.9.0
|
6
|
+
s.version = '1.9.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}
|
@@ -58,17 +58,18 @@ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
|
|
58
58
|
config_param :templates, :hash, :default => nil
|
59
59
|
config_param :include_tag_key, :bool, :default => false
|
60
60
|
config_param :tag_key, :string, :default => 'tag'
|
61
|
+
config_param :time_parse_error_tag, :string, :default => 'Fluent::ElasticsearchOutput::TimeParser.error'
|
62
|
+
config_param :reconnect_on_error, :bool, :default => false
|
61
63
|
|
62
64
|
include Fluent::ElasticsearchIndexTemplate
|
63
65
|
|
64
66
|
def initialize
|
65
67
|
super
|
66
|
-
@time_parser = TimeParser.new(@time_key_format, @router)
|
67
68
|
end
|
68
69
|
|
69
70
|
def configure(conf)
|
70
71
|
super
|
71
|
-
@time_parser =
|
72
|
+
@time_parser = create_time_parser
|
72
73
|
|
73
74
|
if @remove_keys
|
74
75
|
@remove_keys = @remove_keys.split(/\s*,\s*/)
|
@@ -110,40 +111,32 @@ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
|
|
110
111
|
result
|
111
112
|
end
|
112
113
|
|
113
|
-
def start
|
114
|
-
super
|
115
|
-
end
|
116
|
-
|
117
114
|
# once fluent v0.14 is released we might be able to use
|
118
115
|
# Fluent::Parser::TimeParser, but it doesn't quite do what we want - if gives
|
119
116
|
# [sec,nsec] where as we want something we can call `strftime` on...
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
# if strptime couldn't be required (because it's not installed -- it's
|
133
|
-
# ruby 2 only)
|
134
|
-
Proc.new { |value| DateTime.strptime(value, time_key_format) }
|
135
|
-
end
|
136
|
-
else
|
137
|
-
Proc.new { |value| DateTime.parse(value) }
|
117
|
+
def create_time_parser
|
118
|
+
if @time_key_format
|
119
|
+
begin
|
120
|
+
# Strptime doesn't support all formats, but for those it does it's
|
121
|
+
# blazingly fast.
|
122
|
+
strptime = Strptime.new(@time_key_format)
|
123
|
+
Proc.new { |value| strptime.exec(value).to_datetime }
|
124
|
+
rescue
|
125
|
+
# Can happen if Strptime doesn't recognize the format; or
|
126
|
+
# if strptime couldn't be required (because it's not installed -- it's
|
127
|
+
# ruby 2 only)
|
128
|
+
Proc.new { |value| DateTime.strptime(value, @time_key_format) }
|
138
129
|
end
|
130
|
+
else
|
131
|
+
Proc.new { |value| DateTime.parse(value) }
|
139
132
|
end
|
133
|
+
end
|
140
134
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
end
|
135
|
+
def parse_time(value, event_time, tag)
|
136
|
+
@time_parser.call(value)
|
137
|
+
rescue => e
|
138
|
+
router.emit_error_event(@time_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @time_key_format, 'value' => value}, e)
|
139
|
+
return Time.at(event_time).to_datetime
|
147
140
|
end
|
148
141
|
|
149
142
|
def client
|
@@ -215,10 +208,6 @@ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
|
|
215
208
|
end.join(', ')
|
216
209
|
end
|
217
210
|
|
218
|
-
def shutdown
|
219
|
-
super
|
220
|
-
end
|
221
|
-
|
222
211
|
BODY_DELIMITER = "\n".freeze
|
223
212
|
UPDATE_OP = "update".freeze
|
224
213
|
UPSERT_OP = "upsert".freeze
|
@@ -302,11 +291,12 @@ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
|
|
302
291
|
target_index = target_index_parent.delete(target_index_child_key)
|
303
292
|
elsif @logstash_format
|
304
293
|
if record.has_key?(TIMESTAMP_FIELD)
|
305
|
-
|
306
|
-
dt =
|
294
|
+
rts = record[TIMESTAMP_FIELD]
|
295
|
+
dt = parse_time(rts, time, tag)
|
307
296
|
elsif record.has_key?(@time_key)
|
308
|
-
|
309
|
-
|
297
|
+
rts = record[@time_key]
|
298
|
+
dt = parse_time(rts, time, tag)
|
299
|
+
record[TIMESTAMP_FIELD] = rts unless @time_key_exclude_timestamp
|
310
300
|
else
|
311
301
|
dt = Time.at(time).to_datetime
|
312
302
|
record[TIMESTAMP_FIELD] = dt.to_s
|
@@ -370,6 +360,9 @@ class Fluent::ElasticsearchOutput < Fluent::ObjectBufferedOutput
|
|
370
360
|
retry
|
371
361
|
end
|
372
362
|
raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
|
363
|
+
rescue Exception
|
364
|
+
@_es = nil if @reconnect_on_error
|
365
|
+
raise
|
373
366
|
end
|
374
367
|
end
|
375
368
|
end
|
@@ -16,6 +16,7 @@ class Fluent::ElasticsearchOutputDynamic < Fluent::ElasticsearchOutput
|
|
16
16
|
config_param :reload_on_failure, :string, :default => "false"
|
17
17
|
config_param :resurrect_after, :string, :default => "60"
|
18
18
|
config_param :ssl_verify, :string, :default => "true"
|
19
|
+
config_param :reconnect_on_error, :bool, :default => false
|
19
20
|
|
20
21
|
def configure(conf)
|
21
22
|
super
|
@@ -207,6 +208,9 @@ class Fluent::ElasticsearchOutputDynamic < Fluent::ElasticsearchOutput
|
|
207
208
|
retry
|
208
209
|
end
|
209
210
|
raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
|
211
|
+
rescue Exception
|
212
|
+
@_es = nil if @reconnect_on_error
|
213
|
+
raise
|
210
214
|
end
|
211
215
|
end
|
212
216
|
|
@@ -367,8 +367,8 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
367
367
|
end
|
368
368
|
|
369
369
|
def test_writes_to_target_index_key_logstash
|
370
|
-
driver.configure("target_index_key @target_index
|
371
|
-
|
370
|
+
driver.configure("target_index_key @target_index
|
371
|
+
logstash_format true")
|
372
372
|
time = Time.parse Date.today.to_s
|
373
373
|
stub_elastic_ping
|
374
374
|
stub_elastic
|
@@ -378,8 +378,8 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
378
378
|
end
|
379
379
|
|
380
380
|
def test_writes_to_target_index_key_logstash_uppercase
|
381
|
-
driver.configure("target_index_key @target_index
|
382
|
-
|
381
|
+
driver.configure("target_index_key @target_index
|
382
|
+
logstash_format true")
|
383
383
|
time = Time.parse Date.today.to_s
|
384
384
|
stub_elastic_ping
|
385
385
|
stub_elastic
|
@@ -400,8 +400,8 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
400
400
|
end
|
401
401
|
|
402
402
|
def test_writes_to_target_index_key_fallack_logstash
|
403
|
-
driver.configure("target_index_key @target_index\n
|
404
|
-
|
403
|
+
driver.configure("target_index_key @target_index\n
|
404
|
+
logstash_format true")
|
405
405
|
time = Time.parse Date.today.to_s
|
406
406
|
logstash_index = "logstash-#{time.getutc.strftime("%Y.%m.%d")}"
|
407
407
|
stub_elastic_ping
|
@@ -441,8 +441,8 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
441
441
|
end
|
442
442
|
|
443
443
|
def test_writes_to_target_type_key_fallack_to_type_name
|
444
|
-
driver.configure("target_type_key @target_type
|
445
|
-
|
444
|
+
driver.configure("target_type_key @target_type
|
445
|
+
type_name mytype")
|
446
446
|
stub_elastic_ping
|
447
447
|
stub_elastic
|
448
448
|
driver.emit(sample_record)
|
@@ -724,9 +724,13 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
724
724
|
assert_equal(index_cmds[1]['@timestamp'], ts)
|
725
725
|
end
|
726
726
|
|
727
|
-
|
727
|
+
data(:default => nil,
|
728
|
+
:custom_tag => 'es_plugin.output.time.error')
|
729
|
+
def test_uses_custom_time_key_format_logs_an_error(tag_for_error)
|
730
|
+
tag_config = tag_for_error ? "time_parse_error_tag #{tag_for_error}" : ''
|
731
|
+
tag_for_error = 'Fluent::ElasticsearchOutput::TimeParser.error' if tag_for_error.nil?
|
728
732
|
driver.configure("logstash_format true
|
729
|
-
time_key_format %Y-%m-%dT%H:%M:%S.%N%z\n")
|
733
|
+
time_key_format %Y-%m-%dT%H:%M:%S.%N%z\n#{tag_config}\n")
|
730
734
|
stub_elastic_ping
|
731
735
|
stub_elastic
|
732
736
|
|
@@ -737,7 +741,7 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
737
741
|
driver.run
|
738
742
|
|
739
743
|
log = driver.instance.router.emit_error_handler.log
|
740
|
-
errors = log.out.logs.grep /tag="
|
744
|
+
errors = log.out.logs.grep /tag="#{tag_for_error}"/
|
741
745
|
assert_equal(1, errors.length, "Error was logged for timestamp parse failure")
|
742
746
|
|
743
747
|
assert_equal(index, index_cmds[0]['index']['_index'])
|
@@ -910,6 +914,54 @@ class ElasticsearchOutput < Test::Unit::TestCase
|
|
910
914
|
assert_equal(connection_resets, 3)
|
911
915
|
end
|
912
916
|
|
917
|
+
def test_reconnect_on_error_enabled
|
918
|
+
connection_resets = 0
|
919
|
+
|
920
|
+
stub_elastic_ping(url="http://localhost:9200").with do |req|
|
921
|
+
connection_resets += 1
|
922
|
+
end
|
923
|
+
|
924
|
+
stub_request(:post, "http://localhost:9200/_bulk").with do |req|
|
925
|
+
raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
|
926
|
+
end
|
927
|
+
|
928
|
+
driver.configure("reconnect_on_error true\n")
|
929
|
+
driver.emit(sample_record)
|
930
|
+
|
931
|
+
assert_raise(ZeroDivisionError) {
|
932
|
+
driver.run
|
933
|
+
}
|
934
|
+
|
935
|
+
assert_raise(ZeroDivisionError) {
|
936
|
+
driver.run
|
937
|
+
}
|
938
|
+
assert_equal(connection_resets, 2)
|
939
|
+
end
|
940
|
+
|
941
|
+
def test_reconnect_on_error_disabled
|
942
|
+
connection_resets = 0
|
943
|
+
|
944
|
+
stub_elastic_ping(url="http://localhost:9200").with do |req|
|
945
|
+
connection_resets += 1
|
946
|
+
end
|
947
|
+
|
948
|
+
stub_request(:post, "http://localhost:9200/_bulk").with do |req|
|
949
|
+
raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
|
950
|
+
end
|
951
|
+
|
952
|
+
driver.configure("reconnect_on_error false\n")
|
953
|
+
driver.emit(sample_record)
|
954
|
+
|
955
|
+
assert_raise(ZeroDivisionError) {
|
956
|
+
driver.run
|
957
|
+
}
|
958
|
+
|
959
|
+
assert_raise(ZeroDivisionError) {
|
960
|
+
driver.run
|
961
|
+
}
|
962
|
+
assert_equal(connection_resets, 1)
|
963
|
+
end
|
964
|
+
|
913
965
|
def test_update_should_not_write_if_theres_no_id
|
914
966
|
driver.configure("write_operation update\n")
|
915
967
|
stub_elastic_ping
|
@@ -538,6 +538,54 @@ class ElasticsearchOutputDynamic < Test::Unit::TestCase
|
|
538
538
|
assert_equal(connection_resets, 3)
|
539
539
|
end
|
540
540
|
|
541
|
+
def test_reconnect_on_error_enabled
|
542
|
+
connection_resets = 0
|
543
|
+
|
544
|
+
stub_elastic_ping(url="http://localhost:9200").with do |req|
|
545
|
+
connection_resets += 1
|
546
|
+
end
|
547
|
+
|
548
|
+
stub_request(:post, "http://localhost:9200/_bulk").with do |req|
|
549
|
+
raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
|
550
|
+
end
|
551
|
+
|
552
|
+
driver.configure("reconnect_on_error true\n")
|
553
|
+
driver.emit(sample_record)
|
554
|
+
|
555
|
+
assert_raise(ZeroDivisionError) {
|
556
|
+
driver.run
|
557
|
+
}
|
558
|
+
|
559
|
+
assert_raise(ZeroDivisionError) {
|
560
|
+
driver.run
|
561
|
+
}
|
562
|
+
assert_equal(connection_resets, 2)
|
563
|
+
end
|
564
|
+
|
565
|
+
def test_reconnect_on_error_disabled
|
566
|
+
connection_resets = 0
|
567
|
+
|
568
|
+
stub_elastic_ping(url="http://localhost:9200").with do |req|
|
569
|
+
connection_resets += 1
|
570
|
+
end
|
571
|
+
|
572
|
+
stub_request(:post, "http://localhost:9200/_bulk").with do |req|
|
573
|
+
raise ZeroDivisionError, "any not host_unreachable_exceptions exception"
|
574
|
+
end
|
575
|
+
|
576
|
+
driver.configure("reconnect_on_error false\n")
|
577
|
+
driver.emit(sample_record)
|
578
|
+
|
579
|
+
assert_raise(ZeroDivisionError) {
|
580
|
+
driver.run
|
581
|
+
}
|
582
|
+
|
583
|
+
assert_raise(ZeroDivisionError) {
|
584
|
+
driver.run
|
585
|
+
}
|
586
|
+
assert_equal(connection_resets, 1)
|
587
|
+
end
|
588
|
+
|
541
589
|
def test_update_should_not_write_if_theres_no_id
|
542
590
|
driver.configure("write_operation update\n")
|
543
591
|
stub_elastic_ping
|
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.9.0
|
4
|
+
version: 1.9.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: 2016-11-
|
12
|
+
date: 2016-11-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fluentd
|
@@ -151,9 +151,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
151
151
|
version: '2.0'
|
152
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
153
|
requirements:
|
154
|
-
- - "
|
154
|
+
- - ">="
|
155
155
|
- !ruby/object:Gem::Version
|
156
|
-
version:
|
156
|
+
version: '0'
|
157
157
|
requirements: []
|
158
158
|
rubyforge_project:
|
159
159
|
rubygems_version: 2.5.1
|