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.
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