fluent-plugin-elasticsearch 5.0.0 → 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/linux.yml +5 -2
- data/.github/workflows/macos.yml +5 -2
- data/.github/workflows/windows.yml +5 -2
- data/Gemfile +1 -1
- data/History.md +65 -1
- data/README.Troubleshooting.md +91 -0
- data/README.md +129 -4
- data/fluent-plugin-elasticsearch.gemspec +2 -1
- data/lib/fluent/plugin/elasticsearch_compat.rb +30 -0
- data/lib/fluent/plugin/elasticsearch_error_handler.rb +19 -4
- data/lib/fluent/plugin/elasticsearch_fallback_selector.rb +2 -2
- data/lib/fluent/plugin/elasticsearch_index_lifecycle_management.rb +18 -4
- data/lib/fluent/plugin/elasticsearch_index_template.rb +20 -4
- data/lib/fluent/plugin/elasticsearch_simple_sniffer.rb +2 -1
- data/lib/fluent/plugin/filter_elasticsearch_genid.rb +1 -1
- data/lib/fluent/plugin/in_elasticsearch.rb +2 -1
- data/lib/fluent/plugin/oj_serializer.rb +2 -1
- data/lib/fluent/plugin/out_elasticsearch.rb +80 -19
- data/lib/fluent/plugin/out_elasticsearch_data_stream.rb +132 -62
- data/lib/fluent/plugin/out_elasticsearch_dynamic.rb +3 -1
- data/test/plugin/mock_chunk.dat +0 -0
- data/test/plugin/test_elasticsearch_error_handler.rb +130 -23
- data/test/plugin/test_elasticsearch_fallback_selector.rb +16 -8
- data/test/plugin/test_elasticsearch_index_lifecycle_management.rb +55 -15
- data/test/plugin/test_filter_elasticsearch_genid.rb +16 -16
- data/test/plugin/test_in_elasticsearch.rb +20 -0
- data/test/plugin/test_out_elasticsearch.rb +795 -134
- data/test/plugin/test_out_elasticsearch_data_stream.rb +717 -117
- data/test/plugin/test_out_elasticsearch_dynamic.rb +150 -18
- metadata +21 -5
- data/.travis.yml +0 -40
- data/appveyor.yml +0 -20
@@ -28,17 +28,33 @@ module Fluent::ElasticsearchIndexTemplate
|
|
28
28
|
client(host).indices.get_index_template(:name => name)
|
29
29
|
end
|
30
30
|
return true
|
31
|
-
rescue
|
31
|
+
rescue TRANSPORT_CLASS::Transport::Errors::NotFound
|
32
32
|
return false
|
33
33
|
end
|
34
34
|
|
35
|
+
def host_unreachable_exceptions
|
36
|
+
if Gem::Version.new(::TRANSPORT_CLASS::VERSION) >= Gem::Version.new("8.0.0")
|
37
|
+
# elasticsearch-ruby 8.0.0's elastic-transport uses
|
38
|
+
# direct callable #host_unreachable_exceptions again.
|
39
|
+
client.transport.host_unreachable_exceptions
|
40
|
+
elsif Gem::Version.new(::TRANSPORT_CLASS::VERSION) >= Gem::Version.new("7.14.0")
|
41
|
+
# elasticsearch-ruby 7.14.0's elasticsearch-transport does not extends
|
42
|
+
# Elasticsearch class on Transport.
|
43
|
+
# This is why #host_unreachable_exceptions is not callable directly
|
44
|
+
# via transport (not transport's transport instance accessor) any more.
|
45
|
+
client.transport.transport.host_unreachable_exceptions
|
46
|
+
else
|
47
|
+
client.transport.host_unreachable_exceptions
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
35
51
|
def retry_operate(max_retries, fail_on_retry_exceed = true, catch_trasport_exceptions = true)
|
36
52
|
return unless block_given?
|
37
53
|
retries = 0
|
38
|
-
transport_errors =
|
54
|
+
transport_errors = TRANSPORT_CLASS::Transport::Errors.constants.map{ |c| TRANSPORT_CLASS::Transport::Errors.const_get c } if catch_trasport_exceptions
|
39
55
|
begin
|
40
56
|
yield
|
41
|
-
rescue *
|
57
|
+
rescue *host_unreachable_exceptions, *transport_errors, Timeout::Error => e
|
42
58
|
@_es = nil
|
43
59
|
@_es_info = nil
|
44
60
|
if retries < max_retries
|
@@ -66,7 +82,7 @@ module Fluent::ElasticsearchIndexTemplate
|
|
66
82
|
|
67
83
|
def indexcreation(index_name, host = nil)
|
68
84
|
client(host).indices.create(:index => index_name)
|
69
|
-
rescue
|
85
|
+
rescue TRANSPORT_CLASS::Transport::Error => e
|
70
86
|
if e.message =~ /"already exists"/ || e.message =~ /resource_already_exists_exception/
|
71
87
|
log.debug("Index #{index_name} already exists")
|
72
88
|
else
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'elasticsearch'
|
2
|
+
require_relative 'elasticsearch_compat'
|
2
3
|
|
3
|
-
class Fluent::Plugin::ElasticsearchSimpleSniffer <
|
4
|
+
class Fluent::Plugin::ElasticsearchSimpleSniffer < TRANSPORT_CLASS::Transport::Sniffer
|
4
5
|
|
5
6
|
def hosts
|
6
7
|
@transport.logger.debug "In Fluent::Plugin::ElasticsearchSimpleSniffer hosts #{@transport.hosts}" if @transport.logger
|
@@ -53,7 +53,7 @@ module Fluent::Plugin
|
|
53
53
|
seed += tag + separator if @include_tag_in_seed
|
54
54
|
seed += time.to_s + separator if @include_time_in_seed
|
55
55
|
if @use_entire_record
|
56
|
-
record.each {|k
|
56
|
+
record.keys.sort.each {|k| seed += "|#{k}|#{record[k]}"}
|
57
57
|
else
|
58
58
|
seed += record_keys.map {|k| record[k]}.join(separator)
|
59
59
|
end
|
@@ -3,6 +3,7 @@ require 'elasticsearch'
|
|
3
3
|
require 'fluent/log-ext'
|
4
4
|
require 'fluent/plugin/input'
|
5
5
|
require_relative 'elasticsearch_constants'
|
6
|
+
require_relative 'elasticsearch_compat'
|
6
7
|
|
7
8
|
module Fluent::Plugin
|
8
9
|
class ElasticsearchInput < Input
|
@@ -218,7 +219,7 @@ module Fluent::Plugin
|
|
218
219
|
|
219
220
|
headers = { 'Content-Type' => "application/json" }.merge(@custom_headers)
|
220
221
|
|
221
|
-
transport =
|
222
|
+
transport = TRANSPORT_CLASS::Transport::HTTP::Faraday.new(
|
222
223
|
connection_options.merge(
|
223
224
|
options: {
|
224
225
|
reload_connections: local_reload_connections,
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'oj'
|
2
|
+
require_relative 'elasticsearch_compat'
|
2
3
|
|
3
4
|
module Fluent::Plugin
|
4
5
|
module Serializer
|
5
6
|
|
6
7
|
class Oj
|
7
|
-
include
|
8
|
+
include TRANSPORT_CLASS::Transport::Serializer::Base
|
8
9
|
|
9
10
|
# De-serialize a Hash from JSON string
|
10
11
|
#
|
@@ -2,10 +2,7 @@
|
|
2
2
|
require 'date'
|
3
3
|
require 'excon'
|
4
4
|
require 'elasticsearch'
|
5
|
-
|
6
|
-
require 'elasticsearch/xpack'
|
7
|
-
rescue LoadError
|
8
|
-
end
|
5
|
+
require 'set'
|
9
6
|
require 'json'
|
10
7
|
require 'uri'
|
11
8
|
require 'base64'
|
@@ -13,6 +10,7 @@ begin
|
|
13
10
|
require 'strptime'
|
14
11
|
rescue LoadError
|
15
12
|
end
|
13
|
+
require 'resolv'
|
16
14
|
|
17
15
|
require 'fluent/plugin/output'
|
18
16
|
require 'fluent/event'
|
@@ -21,6 +19,7 @@ require 'fluent/time'
|
|
21
19
|
require 'fluent/unique_id'
|
22
20
|
require 'fluent/log-ext'
|
23
21
|
require 'zlib'
|
22
|
+
require_relative 'elasticsearch_compat'
|
24
23
|
require_relative 'elasticsearch_constants'
|
25
24
|
require_relative 'elasticsearch_error'
|
26
25
|
require_relative 'elasticsearch_error_handler'
|
@@ -70,7 +69,7 @@ module Fluent::Plugin
|
|
70
69
|
DEFAULT_TYPE_NAME_ES_7x = "_doc".freeze
|
71
70
|
DEFAULT_TYPE_NAME = "fluentd".freeze
|
72
71
|
DEFAULT_RELOAD_AFTER = -1
|
73
|
-
|
72
|
+
DEFAULT_TARGET_BULK_BYTES = -1
|
74
73
|
DEFAULT_POLICY_ID = "logstash-policy"
|
75
74
|
|
76
75
|
config_param :host, :string, :default => 'localhost'
|
@@ -164,7 +163,7 @@ EOC
|
|
164
163
|
config_param :suppress_doc_wrap, :bool, :default => false
|
165
164
|
config_param :ignore_exceptions, :array, :default => [], value_type: :string, :desc => "Ignorable exception list"
|
166
165
|
config_param :exception_backup, :bool, :default => true, :desc => "Chunk backup flag when ignore exception occured"
|
167
|
-
config_param :bulk_message_request_threshold, :size, :default =>
|
166
|
+
config_param :bulk_message_request_threshold, :size, :default => DEFAULT_TARGET_BULK_BYTES
|
168
167
|
config_param :compression_level, :enum, list: [:no_compression, :best_speed, :best_compression, :default_compression], :default => :no_compression
|
169
168
|
config_param :enable_ilm, :bool, :default => false
|
170
169
|
config_param :ilm_policy_id, :string, :default => DEFAULT_POLICY_ID
|
@@ -174,6 +173,7 @@ EOC
|
|
174
173
|
config_param :truncate_caches_interval, :time, :default => nil
|
175
174
|
config_param :use_legacy_template, :bool, :default => true
|
176
175
|
config_param :catch_transport_exception_on_retry, :bool, :default => true
|
176
|
+
config_param :target_index_affinity, :bool, :default => false
|
177
177
|
|
178
178
|
config_section :metadata, param_name: :metainfo, multi: false do
|
179
179
|
config_param :include_chunk_id, :bool, :default => false
|
@@ -410,11 +410,11 @@ EOC
|
|
410
410
|
end
|
411
411
|
end
|
412
412
|
|
413
|
-
if Gem::Version.create(::
|
413
|
+
if Gem::Version.create(::TRANSPORT_CLASS::VERSION) < Gem::Version.create("7.2.0")
|
414
414
|
if compression
|
415
415
|
raise Fluent::ConfigError, <<-EOC
|
416
416
|
Cannot use compression with elasticsearch-transport plugin version < 7.2.0
|
417
|
-
Your elasticsearch-transport plugin version version is #{
|
417
|
+
Your elasticsearch-transport plugin version version is #{TRANSPORT_CLASS::VERSION}.
|
418
418
|
Please consider to upgrade ES client.
|
419
419
|
EOC
|
420
420
|
end
|
@@ -492,7 +492,11 @@ EOC
|
|
492
492
|
end
|
493
493
|
|
494
494
|
def detect_es_major_version
|
495
|
-
|
495
|
+
begin
|
496
|
+
@_es_info ||= client.info
|
497
|
+
rescue ::Elasticsearch::UnsupportedProductError => e
|
498
|
+
raise Fluent::ConfigError, "Using Elasticsearch client #{client_library_version} is not compatible for your Elasticsearch server. Please check your using elasticsearch gem version and Elasticsearch server."
|
499
|
+
end
|
496
500
|
begin
|
497
501
|
unless version = @_es_info.dig("version", "number")
|
498
502
|
version = @default_elasticsearch_version
|
@@ -610,7 +614,7 @@ EOC
|
|
610
614
|
.merge(gzip_headers)
|
611
615
|
ssl_options = { verify: @ssl_verify, ca_file: @ca_file}.merge(@ssl_version_options)
|
612
616
|
|
613
|
-
transport =
|
617
|
+
transport = TRANSPORT_CLASS::Transport::HTTP::Faraday.new(connection_options.merge(
|
614
618
|
options: {
|
615
619
|
reload_connections: local_reload_connections,
|
616
620
|
reload_on_failure: @reload_on_failure,
|
@@ -668,7 +672,11 @@ EOC
|
|
668
672
|
end
|
669
673
|
end.compact
|
670
674
|
else
|
671
|
-
|
675
|
+
if Resolv::IPv6::Regex.match(@host)
|
676
|
+
[{host: "[#{@host}]", scheme: @scheme.to_s, port: @port}]
|
677
|
+
else
|
678
|
+
[{host: @host, port: @port, scheme: @scheme.to_s}]
|
679
|
+
end
|
672
680
|
end.each do |host|
|
673
681
|
host.merge!(user: @user, password: @password) if !host[:user] && @user
|
674
682
|
host.merge!(path: @path) if !host[:path] && @path
|
@@ -819,6 +827,7 @@ EOC
|
|
819
827
|
bulk_message = Hash.new { |h,k| h[k] = '' }
|
820
828
|
header = {}
|
821
829
|
meta = {}
|
830
|
+
unpackedMsgArr = {}
|
822
831
|
|
823
832
|
tag = chunk.metadata.tag
|
824
833
|
chunk_id = dump_unique_id_hex(chunk.unique_id)
|
@@ -829,22 +838,27 @@ EOC
|
|
829
838
|
extract_placeholders(@host, chunk)
|
830
839
|
end
|
831
840
|
|
841
|
+
affinity_target_indices = get_affinity_target_indices(chunk)
|
832
842
|
chunk.msgpack_each do |time, record|
|
833
843
|
next unless record.is_a? Hash
|
834
844
|
|
835
845
|
record = inject_chunk_id_to_record_if_needed(record, chunk_id)
|
836
846
|
|
837
847
|
begin
|
838
|
-
meta, header, record = process_message(tag, meta, header, time, record, extracted_values)
|
848
|
+
meta, header, record = process_message(tag, meta, header, time, record, affinity_target_indices, extracted_values)
|
839
849
|
info = if @include_index_in_url
|
840
850
|
RequestInfo.new(host, meta.delete("_index".freeze), meta["_index".freeze], meta.delete("_alias".freeze))
|
841
851
|
else
|
842
852
|
RequestInfo.new(host, nil, meta["_index".freeze], meta.delete("_alias".freeze))
|
843
853
|
end
|
844
854
|
|
855
|
+
unpackedMsgArr[info] = [] if unpackedMsgArr[info].nil?
|
856
|
+
unpackedMsgArr[info] << {:time => time, :record => record}
|
857
|
+
|
845
858
|
if split_request?(bulk_message, info)
|
846
859
|
bulk_message.each do |info, msgs|
|
847
|
-
send_bulk(msgs, tag, chunk, bulk_message_count[info], extracted_values, info) unless msgs.empty?
|
860
|
+
send_bulk(msgs, tag, chunk, bulk_message_count[info], extracted_values, info, unpackedMsgArr[info]) unless msgs.empty?
|
861
|
+
unpackedMsgArr[info].clear
|
848
862
|
msgs.clear
|
849
863
|
# Clear bulk_message_count for this info.
|
850
864
|
bulk_message_count[info] = 0;
|
@@ -867,11 +881,49 @@ EOC
|
|
867
881
|
end
|
868
882
|
|
869
883
|
bulk_message.each do |info, msgs|
|
870
|
-
send_bulk(msgs, tag, chunk, bulk_message_count[info], extracted_values, info) unless msgs.empty?
|
884
|
+
send_bulk(msgs, tag, chunk, bulk_message_count[info], extracted_values, info, unpackedMsgArr[info]) unless msgs.empty?
|
885
|
+
|
886
|
+
unpackedMsgArr[info].clear
|
871
887
|
msgs.clear
|
872
888
|
end
|
873
889
|
end
|
874
890
|
|
891
|
+
def target_index_affinity_enabled?()
|
892
|
+
@target_index_affinity && @logstash_format && @id_key && (@write_operation == UPDATE_OP || @write_operation == UPSERT_OP)
|
893
|
+
end
|
894
|
+
|
895
|
+
def get_affinity_target_indices(chunk)
|
896
|
+
indices = Hash.new
|
897
|
+
if target_index_affinity_enabled?()
|
898
|
+
id_key_accessor = record_accessor_create(@id_key)
|
899
|
+
ids = Set.new
|
900
|
+
chunk.msgpack_each do |time, record|
|
901
|
+
next unless record.is_a? Hash
|
902
|
+
begin
|
903
|
+
ids << id_key_accessor.call(record)
|
904
|
+
end
|
905
|
+
end
|
906
|
+
log.debug("Find affinity target_indices by quering on ES (write_operation #{@write_operation}) for ids: #{ids.to_a}")
|
907
|
+
options = {
|
908
|
+
:index => "#{logstash_prefix}#{@logstash_prefix_separator}*",
|
909
|
+
}
|
910
|
+
query = {
|
911
|
+
'query' => { 'ids' => { 'values' => ids.to_a } },
|
912
|
+
'_source' => false,
|
913
|
+
'sort' => [
|
914
|
+
{"_index" => {"order" => "desc"}}
|
915
|
+
]
|
916
|
+
}
|
917
|
+
result = client.search(options.merge(:body => Yajl.dump(query)))
|
918
|
+
# There should be just one hit per _id, but in case there still is multiple, just the oldest index is stored to map
|
919
|
+
result['hits']['hits'].each do |hit|
|
920
|
+
indices[hit["_id"]] = hit["_index"]
|
921
|
+
log.debug("target_index for id: #{hit["_id"]} from es: #{hit["_index"]}")
|
922
|
+
end
|
923
|
+
end
|
924
|
+
indices
|
925
|
+
end
|
926
|
+
|
875
927
|
def split_request?(bulk_message, info)
|
876
928
|
# For safety.
|
877
929
|
end
|
@@ -884,7 +936,7 @@ EOC
|
|
884
936
|
false
|
885
937
|
end
|
886
938
|
|
887
|
-
def process_message(tag, meta, header, time, record, extracted_values)
|
939
|
+
def process_message(tag, meta, header, time, record, affinity_target_indices, extracted_values)
|
888
940
|
logstash_prefix, logstash_dateformat, index_name, type_name, _template_name, _customize_template, _deflector_alias, application_name, pipeline, _ilm_policy_id = extracted_values
|
889
941
|
|
890
942
|
if @flatten_hashes
|
@@ -925,6 +977,15 @@ EOC
|
|
925
977
|
record[@tag_key] = tag
|
926
978
|
end
|
927
979
|
|
980
|
+
# If affinity target indices map has value for this particular id, use it as target_index
|
981
|
+
if !affinity_target_indices.empty?
|
982
|
+
id_accessor = record_accessor_create(@id_key)
|
983
|
+
id_value = id_accessor.call(record)
|
984
|
+
if affinity_target_indices.key?(id_value)
|
985
|
+
target_index = affinity_target_indices[id_value]
|
986
|
+
end
|
987
|
+
end
|
988
|
+
|
928
989
|
target_type_parent, target_type_child_key = @target_type_key ? get_parent_of(record, @target_type_key) : nil
|
929
990
|
if target_type_parent && target_type_parent[target_type_child_key]
|
930
991
|
target_type = target_type_parent.delete(target_type_child_key)
|
@@ -934,12 +995,12 @@ EOC
|
|
934
995
|
elsif @last_seen_major_version == 7
|
935
996
|
log.warn "Detected ES 7.x: `_doc` will be used as the document `_type`."
|
936
997
|
target_type = '_doc'.freeze
|
937
|
-
elsif @last_seen_major_version >=8
|
998
|
+
elsif @last_seen_major_version >= 8
|
938
999
|
log.debug "Detected ES 8.x or above: document type will not be used."
|
939
1000
|
target_type = nil
|
940
1001
|
end
|
941
1002
|
else
|
942
|
-
if @suppress_type_name && @last_seen_major_version
|
1003
|
+
if @suppress_type_name && @last_seen_major_version == 7
|
943
1004
|
target_type = nil
|
944
1005
|
elsif @last_seen_major_version == 7 && @type_name != DEFAULT_TYPE_NAME_ES_7x
|
945
1006
|
log.warn "Detected ES 7.x: `_doc` will be used as the document `_type`."
|
@@ -1036,7 +1097,7 @@ EOC
|
|
1036
1097
|
|
1037
1098
|
# send_bulk given a specific bulk request, the original tag,
|
1038
1099
|
# chunk, and bulk_message_count
|
1039
|
-
def send_bulk(data, tag, chunk, bulk_message_count, extracted_values, info)
|
1100
|
+
def send_bulk(data, tag, chunk, bulk_message_count, extracted_values, info, unpacked_msg_arr)
|
1040
1101
|
_logstash_prefix, _logstash_dateformat, index_name, _type_name, template_name, customize_template, deflector_alias, application_name, _pipeline, ilm_policy_id = extracted_values
|
1041
1102
|
if deflector_alias
|
1042
1103
|
template_installation(deflector_alias, template_name, customize_template, application_name, index_name, ilm_policy_id, info.host)
|
@@ -1059,7 +1120,7 @@ EOC
|
|
1059
1120
|
|
1060
1121
|
if response['errors']
|
1061
1122
|
error = Fluent::Plugin::ElasticsearchErrorHandler.new(self)
|
1062
|
-
error.handle_error(response, tag, chunk, bulk_message_count, extracted_values)
|
1123
|
+
error.handle_error(response, tag, chunk, bulk_message_count, extracted_values, unpacked_msg_arr)
|
1063
1124
|
end
|
1064
1125
|
rescue RetryStreamError => e
|
1065
1126
|
log.trace "router.emit_stream for retry stream doing..."
|
@@ -1,3 +1,4 @@
|
|
1
|
+
|
1
2
|
require_relative 'out_elasticsearch'
|
2
3
|
|
3
4
|
module Fluent::Plugin
|
@@ -8,6 +9,11 @@ module Fluent::Plugin
|
|
8
9
|
helpers :event_emitter
|
9
10
|
|
10
11
|
config_param :data_stream_name, :string
|
12
|
+
config_param :data_stream_ilm_name, :string, :default => nil
|
13
|
+
config_param :data_stream_template_name, :string, :default => nil
|
14
|
+
config_param :data_stream_ilm_policy, :string, :default => nil
|
15
|
+
config_param :data_stream_ilm_policy_overwrite, :bool, :default => false
|
16
|
+
|
11
17
|
# Elasticsearch 7.9 or later always support new style of index template.
|
12
18
|
config_set_default :use_legacy_template, false
|
13
19
|
|
@@ -17,134 +23,186 @@ module Fluent::Plugin
|
|
17
23
|
def configure(conf)
|
18
24
|
super
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
if Gem::Version.new(TRANSPORT_CLASS::VERSION) < Gem::Version.new("8.0.0")
|
27
|
+
begin
|
28
|
+
require 'elasticsearch/api'
|
29
|
+
require 'elasticsearch/xpack'
|
30
|
+
rescue LoadError
|
31
|
+
raise Fluent::ConfigError, "'elasticsearch/api', 'elasticsearch/xpack' are required for <@elasticsearch_data_stream>."
|
32
|
+
end
|
33
|
+
else
|
34
|
+
begin
|
35
|
+
require 'elasticsearch/api'
|
36
|
+
rescue LoadError
|
37
|
+
raise Fluent::ConfigError, "'elasticsearch/api is required for <@elasticsearch_data_stream>."
|
38
|
+
end
|
24
39
|
end
|
25
40
|
|
41
|
+
@data_stream_ilm_name = "#{@data_stream_name}_policy" if @data_stream_ilm_name.nil?
|
42
|
+
@data_stream_template_name = "#{@data_stream_name}_template" if @data_stream_template_name.nil?
|
43
|
+
@data_stream_ilm_policy = File.read(File.join(File.dirname(__FILE__), "default-ilm-policy.json")) if @data_stream_ilm_policy.nil?
|
44
|
+
|
26
45
|
# ref. https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-create-data-stream.html
|
27
46
|
unless placeholder?(:data_stream_name_placeholder, @data_stream_name)
|
28
|
-
|
47
|
+
validate_data_stream_parameters
|
29
48
|
else
|
30
49
|
@use_placeholder = true
|
31
50
|
@data_stream_names = []
|
32
51
|
end
|
33
52
|
|
34
|
-
@client = client
|
35
53
|
unless @use_placeholder
|
36
54
|
begin
|
37
55
|
@data_stream_names = [@data_stream_name]
|
38
|
-
|
39
|
-
|
40
|
-
|
56
|
+
retry_operate(@max_retry_putting_template,
|
57
|
+
@fail_on_putting_template_retry_exceed,
|
58
|
+
@catch_transport_exception_on_retry) do
|
59
|
+
create_ilm_policy(@data_stream_name, @data_stream_template_name, @data_stream_ilm_name)
|
60
|
+
create_index_template(@data_stream_name, @data_stream_template_name, @data_stream_ilm_name)
|
61
|
+
create_data_stream(@data_stream_name)
|
62
|
+
end
|
41
63
|
rescue => e
|
42
64
|
raise Fluent::ConfigError, "Failed to create data stream: <#{@data_stream_name}> #{e.message}"
|
43
65
|
end
|
44
66
|
end
|
45
67
|
end
|
46
68
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
69
|
+
def validate_data_stream_parameters
|
70
|
+
{"data_stream_name" => @data_stream_name,
|
71
|
+
"data_stream_template_name"=> @data_stream_template_name,
|
72
|
+
"data_stream_ilm_name" => @data_stream_ilm_name}.each do |parameter, value|
|
73
|
+
unless valid_data_stream_parameters?(value)
|
74
|
+
unless start_with_valid_characters?(value)
|
75
|
+
if not_dots?(value)
|
76
|
+
raise Fluent::ConfigError, "'#{parameter}' must not start with #{INVALID_START_CHRACTERS.join(",")}: <#{value}>"
|
77
|
+
else
|
78
|
+
raise Fluent::ConfigError, "'#{parameter}' must not be . or ..: <#{value}>"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
unless valid_characters?(value)
|
82
|
+
raise Fluent::ConfigError, "'#{parameter}' must not contain invalid characters #{INVALID_CHARACTERS.join(",")}: <#{value}>"
|
83
|
+
end
|
84
|
+
unless lowercase_only?(value)
|
85
|
+
raise Fluent::ConfigError, "'#{parameter}' must be lowercase only: <#{value}>"
|
86
|
+
end
|
87
|
+
if value.bytes.size > 255
|
88
|
+
raise Fluent::ConfigError, "'#{parameter}' must not be longer than 255 bytes: <#{value}>"
|
54
89
|
end
|
55
|
-
end
|
56
|
-
unless valid_characters?
|
57
|
-
raise Fluent::ConfigError, "'data_stream_name' must not contain invalid characters #{INVALID_CHARACTERS.join(",")}: <#{@data_stream_name}>"
|
58
|
-
end
|
59
|
-
unless lowercase_only?
|
60
|
-
raise Fluent::ConfigError, "'data_stream_name' must be lowercase only: <#{@data_stream_name}>"
|
61
|
-
end
|
62
|
-
if @data_stream_name.bytes.size > 255
|
63
|
-
raise Fluent::ConfigError, "'data_stream_name' must not be longer than 255 bytes: <#{@data_stream_name}>"
|
64
90
|
end
|
65
91
|
end
|
66
92
|
end
|
67
93
|
|
68
|
-
def create_ilm_policy(
|
94
|
+
def create_ilm_policy(datastream_name, template_name, ilm_name, host = nil)
|
95
|
+
unless @data_stream_ilm_policy_overwrite
|
96
|
+
return if data_stream_exist?(datastream_name, host) or template_exists?(template_name, host) or ilm_policy_exists?(ilm_name, host)
|
97
|
+
end
|
98
|
+
|
69
99
|
params = {
|
70
|
-
|
71
|
-
body: File.read(File.join(File.dirname(__FILE__), "default-ilm-policy.json"))
|
100
|
+
body: @data_stream_ilm_policy
|
72
101
|
}
|
73
102
|
retry_operate(@max_retry_putting_template,
|
74
103
|
@fail_on_putting_template_retry_exceed,
|
75
104
|
@catch_transport_exception_on_retry) do
|
76
|
-
|
105
|
+
if Gem::Version.new(TRANSPORT_CLASS::VERSION) >= Gem::Version.new("8.0.0")
|
106
|
+
client(host).enrich.put_policy(params.merge(name: ilm_name))
|
107
|
+
else
|
108
|
+
client(host).xpack.ilm.put_policy(params.merge(policy_id: ilm_name))
|
109
|
+
end
|
77
110
|
end
|
78
111
|
end
|
79
112
|
|
80
|
-
def create_index_template(
|
113
|
+
def create_index_template(datastream_name, template_name, ilm_name, host = nil)
|
114
|
+
return if data_stream_exist?(datastream_name, host) or template_exists?(template_name, host)
|
81
115
|
body = {
|
82
|
-
"index_patterns" => ["#{
|
116
|
+
"index_patterns" => ["#{datastream_name}*"],
|
83
117
|
"data_stream" => {},
|
84
118
|
"template" => {
|
85
119
|
"settings" => {
|
86
|
-
"index.lifecycle.name" => "#{
|
120
|
+
"index.lifecycle.name" => "#{ilm_name}"
|
87
121
|
}
|
88
122
|
}
|
89
123
|
}
|
90
124
|
params = {
|
91
|
-
name:
|
125
|
+
name: template_name,
|
92
126
|
body: body
|
93
127
|
}
|
94
128
|
retry_operate(@max_retry_putting_template,
|
95
129
|
@fail_on_putting_template_retry_exceed,
|
96
130
|
@catch_transport_exception_on_retry) do
|
97
|
-
|
131
|
+
client(host).indices.put_index_template(params)
|
98
132
|
end
|
99
133
|
end
|
100
134
|
|
101
|
-
def data_stream_exist?(
|
135
|
+
def data_stream_exist?(datastream_name, host = nil)
|
102
136
|
params = {
|
103
|
-
|
137
|
+
name: datastream_name
|
104
138
|
}
|
105
139
|
begin
|
106
|
-
response =
|
107
|
-
return (not response.is_a?(
|
108
|
-
rescue
|
140
|
+
response = client(host).indices.get_data_stream(params)
|
141
|
+
return (not response.is_a?(TRANSPORT_CLASS::Transport::Errors::NotFound))
|
142
|
+
rescue TRANSPORT_CLASS::Transport::Errors::NotFound => e
|
109
143
|
log.info "Specified data stream does not exist. Will be created: <#{e}>"
|
110
144
|
return false
|
111
145
|
end
|
112
146
|
end
|
113
147
|
|
114
|
-
def create_data_stream(
|
115
|
-
return if data_stream_exist?(
|
148
|
+
def create_data_stream(datastream_name, host = nil)
|
149
|
+
return if data_stream_exist?(datastream_name, host)
|
116
150
|
params = {
|
117
|
-
|
151
|
+
name: datastream_name
|
118
152
|
}
|
119
153
|
retry_operate(@max_retry_putting_template,
|
120
154
|
@fail_on_putting_template_retry_exceed,
|
121
155
|
@catch_transport_exception_on_retry) do
|
122
|
-
|
156
|
+
client(host).indices.create_data_stream(params)
|
123
157
|
end
|
124
158
|
end
|
125
159
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
160
|
+
def ilm_policy_exists?(policy_id, host = nil)
|
161
|
+
begin
|
162
|
+
if Gem::Version.new(TRANSPORT_CLASS::VERSION) >= Gem::Version.new("8.0.0")
|
163
|
+
client(host).enrich.get_policy(name: policy_id)
|
164
|
+
else
|
165
|
+
client(host).ilm.get_policy(policy_id: policy_id)
|
166
|
+
end
|
167
|
+
true
|
168
|
+
rescue
|
169
|
+
false
|
170
|
+
end
|
132
171
|
end
|
133
172
|
|
134
|
-
def
|
135
|
-
|
173
|
+
def template_exists?(name, host = nil)
|
174
|
+
if @use_legacy_template
|
175
|
+
client(host).indices.get_template(:name => name)
|
176
|
+
else
|
177
|
+
client(host).indices.get_index_template(:name => name)
|
178
|
+
end
|
179
|
+
return true
|
180
|
+
rescue TRANSPORT_CLASS::Transport::Errors::NotFound
|
181
|
+
return false
|
136
182
|
end
|
137
183
|
|
138
|
-
def
|
139
|
-
|
184
|
+
def valid_data_stream_parameters?(data_stream_parameter)
|
185
|
+
lowercase_only?(data_stream_parameter) and
|
186
|
+
valid_characters?(data_stream_parameter) and
|
187
|
+
start_with_valid_characters?(data_stream_parameter) and
|
188
|
+
not_dots?(data_stream_parameter) and
|
189
|
+
data_stream_parameter.bytes.size <= 255
|
140
190
|
end
|
141
191
|
|
142
|
-
def
|
143
|
-
|
192
|
+
def lowercase_only?(data_stream_parameter)
|
193
|
+
data_stream_parameter.downcase == data_stream_parameter
|
144
194
|
end
|
145
195
|
|
146
|
-
def
|
147
|
-
not (
|
196
|
+
def valid_characters?(data_stream_parameter)
|
197
|
+
not (INVALID_CHARACTERS.each.any? do |v| data_stream_parameter.include?(v) end)
|
198
|
+
end
|
199
|
+
|
200
|
+
def start_with_valid_characters?(data_stream_parameter)
|
201
|
+
not (INVALID_START_CHRACTERS.each.any? do |v| data_stream_parameter.start_with?(v) end)
|
202
|
+
end
|
203
|
+
|
204
|
+
def not_dots?(data_stream_parameter)
|
205
|
+
not (data_stream_parameter == "." or data_stream_parameter == "..")
|
148
206
|
end
|
149
207
|
|
150
208
|
def client_library_version
|
@@ -157,12 +215,18 @@ module Fluent::Plugin
|
|
157
215
|
|
158
216
|
def write(chunk)
|
159
217
|
data_stream_name = @data_stream_name
|
218
|
+
data_stream_template_name = @data_stream_template_name
|
219
|
+
data_stream_ilm_name = @data_stream_ilm_name
|
220
|
+
host = nil
|
160
221
|
if @use_placeholder
|
222
|
+
host = extract_placeholders(@host, chunk)
|
161
223
|
data_stream_name = extract_placeholders(@data_stream_name, chunk)
|
224
|
+
data_stream_template_name = extract_placeholders(@data_stream_template_name, chunk)
|
225
|
+
data_stream_ilm_name = extract_placeholders(@data_stream_ilm_name, chunk)
|
162
226
|
unless @data_stream_names.include?(data_stream_name)
|
163
227
|
begin
|
164
|
-
create_ilm_policy(data_stream_name)
|
165
|
-
create_index_template(data_stream_name)
|
228
|
+
create_ilm_policy(data_stream_name, data_stream_template_name, data_stream_ilm_name, host)
|
229
|
+
create_index_template(data_stream_name, data_stream_template_name, data_stream_ilm_name, host)
|
166
230
|
create_data_stream(data_stream_name)
|
167
231
|
@data_stream_names << data_stream_name
|
168
232
|
rescue => e
|
@@ -179,8 +243,14 @@ module Fluent::Plugin
|
|
179
243
|
chunk.msgpack_each do |time, record|
|
180
244
|
next unless record.is_a? Hash
|
181
245
|
|
246
|
+
if @include_tag_key
|
247
|
+
record[@tag_key] = tag
|
248
|
+
end
|
249
|
+
|
182
250
|
begin
|
183
|
-
record.
|
251
|
+
unless record.has_key?("@timestamp")
|
252
|
+
record.merge!({"@timestamp" => Time.at(time).iso8601(@time_precision)})
|
253
|
+
end
|
184
254
|
bulk_message = append_record_to_messages(CREATE_OP, {}, headers, record, bulk_message)
|
185
255
|
rescue => e
|
186
256
|
router.emit_error_event(tag, time, record, e)
|
@@ -192,12 +262,12 @@ module Fluent::Plugin
|
|
192
262
|
body: bulk_message
|
193
263
|
}
|
194
264
|
begin
|
195
|
-
response =
|
265
|
+
response = client(host).bulk(params)
|
196
266
|
if response['errors']
|
197
267
|
log.error "Could not bulk insert to Data Stream: #{data_stream_name} #{response}"
|
198
268
|
end
|
199
269
|
rescue => e
|
200
|
-
|
270
|
+
raise RecoverableRequestFailure, "could not push logs to Elasticsearch cluster (#{data_stream_name}): #{e.message}"
|
201
271
|
end
|
202
272
|
end
|
203
273
|
|