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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/linux.yml +5 -2
  4. data/.github/workflows/macos.yml +5 -2
  5. data/.github/workflows/windows.yml +5 -2
  6. data/Gemfile +1 -1
  7. data/History.md +65 -1
  8. data/README.Troubleshooting.md +91 -0
  9. data/README.md +129 -4
  10. data/fluent-plugin-elasticsearch.gemspec +2 -1
  11. data/lib/fluent/plugin/elasticsearch_compat.rb +30 -0
  12. data/lib/fluent/plugin/elasticsearch_error_handler.rb +19 -4
  13. data/lib/fluent/plugin/elasticsearch_fallback_selector.rb +2 -2
  14. data/lib/fluent/plugin/elasticsearch_index_lifecycle_management.rb +18 -4
  15. data/lib/fluent/plugin/elasticsearch_index_template.rb +20 -4
  16. data/lib/fluent/plugin/elasticsearch_simple_sniffer.rb +2 -1
  17. data/lib/fluent/plugin/filter_elasticsearch_genid.rb +1 -1
  18. data/lib/fluent/plugin/in_elasticsearch.rb +2 -1
  19. data/lib/fluent/plugin/oj_serializer.rb +2 -1
  20. data/lib/fluent/plugin/out_elasticsearch.rb +80 -19
  21. data/lib/fluent/plugin/out_elasticsearch_data_stream.rb +132 -62
  22. data/lib/fluent/plugin/out_elasticsearch_dynamic.rb +3 -1
  23. data/test/plugin/mock_chunk.dat +0 -0
  24. data/test/plugin/test_elasticsearch_error_handler.rb +130 -23
  25. data/test/plugin/test_elasticsearch_fallback_selector.rb +16 -8
  26. data/test/plugin/test_elasticsearch_index_lifecycle_management.rb +55 -15
  27. data/test/plugin/test_filter_elasticsearch_genid.rb +16 -16
  28. data/test/plugin/test_in_elasticsearch.rb +20 -0
  29. data/test/plugin/test_out_elasticsearch.rb +795 -134
  30. data/test/plugin/test_out_elasticsearch_data_stream.rb +717 -117
  31. data/test/plugin/test_out_elasticsearch_dynamic.rb +150 -18
  32. metadata +21 -5
  33. data/.travis.yml +0 -40
  34. 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 Elasticsearch::Transport::Transport::Errors::NotFound
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 = Elasticsearch::Transport::Transport::Errors.constants.map{ |c| Elasticsearch::Transport::Transport::Errors.const_get c } if catch_trasport_exceptions
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 *client.transport.host_unreachable_exceptions, *transport_errors, Timeout::Error => e
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 Elasticsearch::Transport::Transport::Error => e
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 < Elasticsearch::Transport::Transport::Sniffer
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,v| seed += "|#{k}|#{v}"}
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 = Elasticsearch::Transport::Transport::HTTP::Faraday.new(
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 Elasticsearch::Transport::Transport::Serializer::Base
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
- begin
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
- TARGET_BULK_BYTES = 20 * 1024 * 1024
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 => TARGET_BULK_BYTES
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(::Elasticsearch::Transport::VERSION) < Gem::Version.create("7.2.0")
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 #{Elasticsearch::Transport::VERSION}.
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
- @_es_info ||= client.info
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 = Elasticsearch::Transport::Transport::HTTP::Faraday.new(connection_options.merge(
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
- [{host: @host, port: @port, scheme: @scheme.to_s}]
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 >= 7
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
- begin
21
- require 'elasticsearch/xpack'
22
- rescue LoadError
23
- raise Fluent::ConfigError, "'elasticsearch/xpack'' is required for <@elasticsearch_data_stream>."
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
- validate_data_stream_name
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
- create_ilm_policy(@data_stream_name)
39
- create_index_template(@data_stream_name)
40
- create_data_stream(@data_stream_name)
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 validate_data_stream_name
48
- unless valid_data_stream_name?
49
- unless start_with_valid_characters?
50
- if not_dots?
51
- raise Fluent::ConfigError, "'data_stream_name' must not start with #{INVALID_START_CHRACTERS.join(",")}: <#{@data_stream_name}>"
52
- else
53
- raise Fluent::ConfigError, "'data_stream_name' must not be . or ..: <#{@data_stream_name}>"
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(name)
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
- policy_id: "#{name}_policy",
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
- @client.xpack.ilm.put_policy(params)
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(name)
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" => ["#{name}*"],
116
+ "index_patterns" => ["#{datastream_name}*"],
83
117
  "data_stream" => {},
84
118
  "template" => {
85
119
  "settings" => {
86
- "index.lifecycle.name" => "#{name}_policy"
120
+ "index.lifecycle.name" => "#{ilm_name}"
87
121
  }
88
122
  }
89
123
  }
90
124
  params = {
91
- name: 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
- @client.indices.put_index_template(params)
131
+ client(host).indices.put_index_template(params)
98
132
  end
99
133
  end
100
134
 
101
- def data_stream_exist?(name)
135
+ def data_stream_exist?(datastream_name, host = nil)
102
136
  params = {
103
- "name": name
137
+ name: datastream_name
104
138
  }
105
139
  begin
106
- response = @client.indices.get_data_stream(params)
107
- return (not response.is_a?(Elasticsearch::Transport::Transport::Errors::NotFound))
108
- rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
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(name)
115
- return if data_stream_exist?(name)
148
+ def create_data_stream(datastream_name, host = nil)
149
+ return if data_stream_exist?(datastream_name, host)
116
150
  params = {
117
- "name": name
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
- @client.indices.create_data_stream(params)
156
+ client(host).indices.create_data_stream(params)
123
157
  end
124
158
  end
125
159
 
126
- def valid_data_stream_name?
127
- lowercase_only? and
128
- valid_characters? and
129
- start_with_valid_characters? and
130
- not_dots? and
131
- @data_stream_name.bytes.size <= 255
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 lowercase_only?
135
- @data_stream_name.downcase == @data_stream_name
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 valid_characters?
139
- not (INVALID_CHARACTERS.each.any? do |v| @data_stream_name.include?(v) end)
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 start_with_valid_characters?
143
- not (INVALID_START_CHRACTERS.each.any? do |v| @data_stream_name.start_with?(v) end)
192
+ def lowercase_only?(data_stream_parameter)
193
+ data_stream_parameter.downcase == data_stream_parameter
144
194
  end
145
195
 
146
- def not_dots?
147
- not (@data_stream_name == "." or @data_stream_name == "..")
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.merge!({"@timestamp" => Time.at(time).iso8601(@time_precision)})
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 = @client.bulk(params)
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
- log.error "Could not bulk insert to Data Stream: #{data_stream_name} #{e.message}"
270
+ raise RecoverableRequestFailure, "could not push logs to Elasticsearch cluster (#{data_stream_name}): #{e.message}"
201
271
  end
202
272
  end
203
273