fluent-plugin-elasticsearch 5.0.0 → 5.2.3
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/.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
|
|