fluent-plugin-elasticsearch 4.0.7 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,13 @@ module Fluent::Plugin
7
7
  Fluent::Plugin.register_filter('elasticsearch_genid', self)
8
8
 
9
9
  config_param :hash_id_key, :string, :default => '_hash'
10
+ config_param :include_tag_in_seed, :bool, :default => false
11
+ config_param :include_time_in_seed, :bool, :default => false
12
+ config_param :use_record_as_seed, :bool, :default => false
13
+ config_param :use_entire_record, :bool, :default => false
14
+ config_param :record_keys, :array, :default => []
15
+ config_param :separator, :string, :default => '_'
16
+ config_param :hash_type, :enum, list: [:md5, :sha1, :sha256, :sha512], :default => :sha1
10
17
 
11
18
  def initialize
12
19
  super
@@ -14,12 +21,57 @@ module Fluent::Plugin
14
21
 
15
22
  def configure(conf)
16
23
  super
24
+
25
+ if !@use_entire_record
26
+ if @record_keys.empty? && @use_record_as_seed
27
+ raise Fluent::ConfigError, "When using record as hash seed, users must specify `record_keys`."
28
+ end
29
+ end
30
+
31
+ if @use_record_as_seed
32
+ class << self
33
+ alias_method :filter, :filter_seed_as_record
34
+ end
35
+ else
36
+ class << self
37
+ alias_method :filter, :filter_simple
38
+ end
39
+ end
17
40
  end
18
41
 
19
42
  def filter(tag, time, record)
43
+ # for safety.
44
+ end
45
+
46
+ def filter_simple(tag, time, record)
20
47
  record[@hash_id_key] = Base64.strict_encode64(SecureRandom.uuid)
21
48
  record
22
49
  end
23
50
 
51
+ def filter_seed_as_record(tag, time, record)
52
+ seed = ""
53
+ seed += tag + separator if @include_tag_in_seed
54
+ seed += time.to_s + separator if @include_time_in_seed
55
+ if @use_entire_record
56
+ record.each {|k,v| seed += "|#{k}|#{v}"}
57
+ else
58
+ seed += record_keys.map {|k| record[k]}.join(separator)
59
+ end
60
+ record[@hash_id_key] = Base64.strict_encode64(encode_hash(@hash_type, seed))
61
+ record
62
+ end
63
+
64
+ def encode_hash(type, seed)
65
+ case type
66
+ when :md5
67
+ Digest::MD5.digest(seed)
68
+ when :sha1
69
+ Digest::SHA1.digest(seed)
70
+ when :sha256
71
+ Digest::SHA256.digest(seed)
72
+ when :sha512
73
+ Digest::SHA512.digest(seed)
74
+ end
75
+ end
24
76
  end
25
77
  end
@@ -25,6 +25,7 @@ require_relative 'elasticsearch_error_handler'
25
25
  require_relative 'elasticsearch_index_template'
26
26
  require_relative 'elasticsearch_index_lifecycle_management'
27
27
  require_relative 'elasticsearch_tls'
28
+ require_relative 'elasticsearch_fallback_selector'
28
29
  begin
29
30
  require_relative 'oj_serializer'
30
31
  rescue LoadError
@@ -55,6 +56,7 @@ module Fluent::Plugin
55
56
  attr_reader :alias_indexes
56
57
  attr_reader :template_names
57
58
  attr_reader :ssl_version_options
59
+ attr_reader :compressable_connection
58
60
 
59
61
  helpers :event_emitter, :compat_parameters, :record_accessor, :timer
60
62
 
@@ -89,6 +91,7 @@ EOC
89
91
  config_param :logstash_dateformat, :string, :default => "%Y.%m.%d"
90
92
  config_param :utc_index, :bool, :default => true
91
93
  config_param :type_name, :string, :default => DEFAULT_TYPE_NAME
94
+ config_param :suppress_type_name, :bool, :default => false
92
95
  config_param :index_name, :string, :default => "fluentd"
93
96
  config_param :id_key, :string, :default => nil
94
97
  config_param :write_operation, :string, :default => "index"
@@ -135,6 +138,7 @@ EOC
135
138
  config_param :with_transporter_log, :bool, :default => false
136
139
  config_param :emit_error_for_missing_id, :bool, :default => false
137
140
  config_param :sniffer_class_name, :string, :default => nil
141
+ config_param :selector_class_name, :string, :default => nil
138
142
  config_param :reload_after, :integer, :default => DEFAULT_RELOAD_AFTER
139
143
  config_param :content_type, :enum, list: [:"application/json", :"application/x-ndjson"], :default => :"application/json",
140
144
  :deprecated => <<EOC
@@ -159,6 +163,7 @@ EOC
159
163
  config_param :enable_ilm, :bool, :default => false
160
164
  config_param :ilm_policy_id, :string, :default => DEFAULT_POLICY_ID
161
165
  config_param :ilm_policy, :hash, :default => {}
166
+ config_param :ilm_policies, :hash, :default => {}
162
167
  config_param :ilm_policy_overwrite, :bool, :default => false
163
168
  config_param :truncate_caches_interval, :time, :default => nil
164
169
 
@@ -219,6 +224,11 @@ EOC
219
224
  log.info "host placeholder and template installation makes your Elasticsearch cluster a bit slow down(beta)."
220
225
  end
221
226
 
227
+ raise Fluent::ConfigError, "You can't specify ilm_policy and ilm_policies at the same time" unless @ilm_policy.empty? or @ilm_policies.empty?
228
+
229
+ unless @ilm_policy.empty?
230
+ @ilm_policies = { @ilm_policy_id => @ilm_policy }
231
+ end
222
232
  @alias_indexes = []
223
233
  @template_names = []
224
234
  if !dry_run?
@@ -227,14 +237,14 @@ EOC
227
237
  raise Fluent::ConfigError, "deflector_alias is prohibited to use with 'logstash_format at same time." if @logstash_format and @deflector_alias
228
238
  end
229
239
  if @ilm_policy.empty? && @ilm_policy_overwrite
230
- raise Fluent::ConfigError, "ilm_policy_overwrite can work with non empty ilm_policy. Specify non-empty ilm policy into ilm_policy. "
240
+ raise Fluent::ConfigError, "ilm_policy_overwrite requires a non empty ilm_policy."
231
241
  end
232
242
  if @logstash_format || placeholder_substitution_needed_for_template?
233
243
  class << self
234
244
  alias_method :template_installation, :template_installation_actual
235
245
  end
236
246
  else
237
- template_installation_actual(@deflector_alias ? @deflector_alias : @index_name, @template_name, @customize_template, @application_name, @index_name)
247
+ template_installation_actual(@deflector_alias ? @deflector_alias : @index_name, @template_name, @customize_template, @application_name, @index_name, @ilm_policy_id)
238
248
  end
239
249
  verify_ilm_working if @enable_ilm
240
250
  elsif @templates
@@ -292,21 +302,32 @@ EOC
292
302
  raise Fluent::ConfigError, "Could not load sniffer class #{@sniffer_class_name}: #{ex}"
293
303
  end
294
304
 
305
+ @selector_class = nil
306
+ begin
307
+ @selector_class = Object.const_get(@selector_class_name) if @selector_class_name
308
+ rescue Exception => ex
309
+ raise Fluent::ConfigError, "Could not load selector class #{@selector_class_name}: #{ex}"
310
+ end
311
+
295
312
  @last_seen_major_version = if major_version = handle_last_seen_es_major_version
296
313
  major_version
297
314
  else
298
315
  @default_elasticsearch_version
299
316
  end
300
- if @last_seen_major_version == 6 && @type_name != DEFAULT_TYPE_NAME_ES_7x
301
- log.info "Detected ES 6.x: ES 7.x will only accept `_doc` in type_name."
302
- end
303
- if @last_seen_major_version == 7 && @type_name != DEFAULT_TYPE_NAME_ES_7x
304
- log.warn "Detected ES 7.x: `_doc` will be used as the document `_type`."
305
- @type_name = '_doc'.freeze
306
- end
307
- if @last_seen_major_version >= 8 && @type_name != DEFAULT_TYPE_NAME_ES_7x
308
- log.info "Detected ES 8.x or above: This parameter has no effect."
317
+ if @suppress_type_name && @last_seen_major_version >= 7
309
318
  @type_name = nil
319
+ else
320
+ if @last_seen_major_version == 6 && @type_name != DEFAULT_TYPE_NAME_ES_7x
321
+ log.info "Detected ES 6.x: ES 7.x will only accept `_doc` in type_name."
322
+ end
323
+ if @last_seen_major_version == 7 && @type_name != DEFAULT_TYPE_NAME_ES_7x
324
+ log.warn "Detected ES 7.x: `_doc` will be used as the document `_type`."
325
+ @type_name = '_doc'.freeze
326
+ end
327
+ if @last_seen_major_version >= 8 && @type_name != DEFAULT_TYPE_NAME_ES_7x
328
+ log.info "Detected ES 8.x or above: This parameter has no effect."
329
+ @type_name = nil
330
+ end
310
331
  end
311
332
 
312
333
  if @validate_client_version && !dry_run?
@@ -343,6 +364,7 @@ EOC
343
364
  @routing_key_name = configure_routing_key_name
344
365
  @meta_config_map = create_meta_config_map
345
366
  @current_config = nil
367
+ @compressable_connection = false
346
368
 
347
369
  @ignore_exception_classes = @ignore_exceptions.map do |exception|
348
370
  unless Object.const_defined?(exception)
@@ -511,13 +533,15 @@ EOC
511
533
  return Time.at(event_time).to_datetime
512
534
  end
513
535
 
514
- def client(host = nil)
536
+ def client(host = nil, compress_connection = false)
515
537
  # check here to see if we already have a client connection for the given host
516
538
  connection_options = get_connection_options(host)
517
539
 
518
540
  @_es = nil unless is_existing_connection(connection_options[:hosts])
541
+ @_es = nil unless @compressable_connection == compress_connection
519
542
 
520
543
  @_es ||= begin
544
+ @compressable_connection = compress_connection
521
545
  @current_config = connection_options[:hosts].clone
522
546
  adapter_conf = lambda {|f| f.adapter @http_backend, @backend_options }
523
547
  local_reload_connections = @reload_connections
@@ -525,7 +549,7 @@ EOC
525
549
  local_reload_connections = @reload_after
526
550
  end
527
551
 
528
- gzip_headers = if compression
552
+ gzip_headers = if compress_connection
529
553
  {'Content-Encoding' => 'gzip'}
530
554
  else
531
555
  {}
@@ -551,7 +575,8 @@ EOC
551
575
  },
552
576
  sniffer_class: @sniffer_class,
553
577
  serializer_class: @serializer_class,
554
- compression: compression,
578
+ selector_class: @selector_class,
579
+ compression: compress_connection,
555
580
  }), &adapter_conf)
556
581
  Elasticsearch::Client.new transport: transport
557
582
  end
@@ -715,7 +740,12 @@ EOC
715
740
  else
716
741
  pipeline = nil
717
742
  end
718
- return logstash_prefix, logstash_dateformat, index_name, type_name, template_name, customize_template, deflector_alias, application_name, pipeline
743
+ if @ilm_policy_id
744
+ ilm_policy_id = extract_placeholders(@ilm_policy_id, chunk)
745
+ else
746
+ ilm_policy_id = nil
747
+ end
748
+ return logstash_prefix, logstash_dateformat, index_name, type_name, template_name, customize_template, deflector_alias, application_name, pipeline, ilm_policy_id
719
749
  end
720
750
 
721
751
  def multi_workers_ready?
@@ -741,9 +771,9 @@ EOC
741
771
  begin
742
772
  meta, header, record = process_message(tag, meta, header, time, record, extracted_values)
743
773
  info = if @include_index_in_url
744
- RequestInfo.new(host, meta.delete("_index".freeze), meta["_index".freeze])
774
+ RequestInfo.new(host, meta.delete("_index".freeze), meta.delete("_alias".freeze))
745
775
  else
746
- RequestInfo.new(host, nil, meta["_index".freeze])
776
+ RequestInfo.new(host, nil, meta.delete("_alias".freeze))
747
777
  end
748
778
 
749
779
  if split_request?(bulk_message, info)
@@ -789,7 +819,7 @@ EOC
789
819
  end
790
820
 
791
821
  def process_message(tag, meta, header, time, record, extracted_values)
792
- logstash_prefix, logstash_dateformat, index_name, type_name, _template_name, _customize_template, _deflector_alias, _application_name, pipeline = extracted_values
822
+ logstash_prefix, logstash_dateformat, index_name, type_name, _template_name, _customize_template, _deflector_alias, application_name, pipeline, _ilm_policy_id = extracted_values
793
823
 
794
824
  if @flatten_hashes
795
825
  record = flatten_record(record)
@@ -812,17 +842,19 @@ EOC
812
842
 
813
843
  target_index_parent, target_index_child_key = @target_index_key ? get_parent_of(record, @target_index_key) : nil
814
844
  if target_index_parent && target_index_parent[target_index_child_key]
815
- target_index = target_index_parent.delete(target_index_child_key)
845
+ target_index_alias = target_index = target_index_parent.delete(target_index_child_key)
816
846
  elsif @logstash_format
817
847
  dt = dt.new_offset(0) if @utc_index
818
848
  target_index = "#{logstash_prefix}#{@logstash_prefix_separator}#{dt.strftime(logstash_dateformat)}"
849
+ target_index_alias = "#{logstash_prefix}#{@logstash_prefix_separator}#{application_name}#{@logstash_prefix_separator}#{dt.strftime(logstash_dateformat)}"
819
850
  else
820
- target_index = index_name
851
+ target_index_alias = target_index = index_name
821
852
  end
822
853
 
823
854
  # Change target_index to lower-case since Elasticsearch doesn't
824
855
  # allow upper-case characters in index names.
825
856
  target_index = target_index.downcase
857
+ target_index_alias = target_index_alias.downcase
826
858
  if @include_tag_key
827
859
  record[@tag_key] = tag
828
860
  end
@@ -841,7 +873,9 @@ EOC
841
873
  target_type = nil
842
874
  end
843
875
  else
844
- if @last_seen_major_version == 7 && @type_name != DEFAULT_TYPE_NAME_ES_7x
876
+ if @suppress_type_name && @last_seen_major_version >= 7
877
+ target_type = nil
878
+ elsif @last_seen_major_version == 7 && @type_name != DEFAULT_TYPE_NAME_ES_7x
845
879
  log.warn "Detected ES 7.x: `_doc` will be used as the document `_type`."
846
880
  target_type = '_doc'.freeze
847
881
  elsif @last_seen_major_version >= 8
@@ -855,6 +889,7 @@ EOC
855
889
  meta.clear
856
890
  meta["_index".freeze] = target_index
857
891
  meta["_type".freeze] = target_type unless @last_seen_major_version >= 8
892
+ meta["_alias".freeze] = target_index_alias
858
893
 
859
894
  if @pipeline
860
895
  meta["pipeline".freeze] = pipeline
@@ -897,16 +932,17 @@ EOC
897
932
  placeholder?(:logstash_prefix, @logstash_prefix.to_s) ||
898
933
  placeholder?(:logstash_dateformat, @logstash_dateformat.to_s) ||
899
934
  placeholder?(:deflector_alias, @deflector_alias.to_s) ||
900
- placeholder?(:application_name, @application_name.to_s)
935
+ placeholder?(:application_name, @application_name.to_s) ||
936
+ placeholder?(:ilm_policy_id, @ilm_policy_id.to_s)
901
937
  log.debug("Need substitution: #{need_substitution}")
902
938
  need_substitution
903
939
  end
904
940
 
905
- def template_installation(deflector_alias, template_name, customize_template, application_name, target_index, host)
941
+ def template_installation(deflector_alias, template_name, customize_template, application_name, ilm_policy_id, target_index, host)
906
942
  # for safety.
907
943
  end
908
944
 
909
- def template_installation_actual(deflector_alias, template_name, customize_template, application_name, target_index, host=nil)
945
+ def template_installation_actual(deflector_alias, template_name, customize_template, application_name, target_index, ilm_policy_id, host=nil)
910
946
  if template_name && @template_file
911
947
  if @alias_indexes.include? deflector_alias
912
948
  log.debug("Index alias #{deflector_alias} already exists (cached)")
@@ -915,11 +951,12 @@ EOC
915
951
  else
916
952
  retry_operate(@max_retry_putting_template, @fail_on_putting_template_retry_exceed) do
917
953
  if customize_template
918
- template_custom_install(template_name, @template_file, @template_overwrite, customize_template, @enable_ilm, deflector_alias, @ilm_policy_id, host)
954
+ template_custom_install(template_name, @template_file, @template_overwrite, customize_template, @enable_ilm, deflector_alias, ilm_policy_id, host)
919
955
  else
920
- template_install(template_name, @template_file, @template_overwrite, @enable_ilm, deflector_alias, @ilm_policy_id, host)
956
+ template_install(template_name, @template_file, @template_overwrite, @enable_ilm, deflector_alias, ilm_policy_id, host)
921
957
  end
922
- create_rollover_alias(target_index, @rollover_index, deflector_alias, application_name, @index_date_pattern, @index_separator, @enable_ilm, @ilm_policy_id, @ilm_policy, @ilm_policy_overwrite, host)
958
+ ilm_policy = @ilm_policies[ilm_policy_id] || {}
959
+ create_rollover_alias(target_index, @rollover_index, deflector_alias, application_name, @index_date_pattern, @index_separator, @enable_ilm, ilm_policy_id, ilm_policy, @ilm_policy_overwrite, host)
923
960
  end
924
961
  @alias_indexes << deflector_alias unless deflector_alias.nil?
925
962
  @template_names << template_name unless template_name.nil?
@@ -930,11 +967,11 @@ EOC
930
967
  # send_bulk given a specific bulk request, the original tag,
931
968
  # chunk, and bulk_message_count
932
969
  def send_bulk(data, tag, chunk, bulk_message_count, extracted_values, info)
933
- logstash_prefix, _logstash_dateformat, index_name, _type_name, template_name, customize_template, deflector_alias, application_name, _pipeline = extracted_values
970
+ logstash_prefix, _logstash_dateformat, index_name, _type_name, template_name, customize_template, deflector_alias, application_name, _pipeline, ilm_policy_id = extracted_values
934
971
  if deflector_alias
935
- template_installation(deflector_alias, template_name, customize_template, application_name, index_name, info.host)
972
+ template_installation(deflector_alias, template_name, customize_template, application_name, index_name, ilm_policy_id, info.host)
936
973
  else
937
- template_installation(info.ilm_index, template_name, customize_template, application_name, @logstash_format ? logstash_prefix : index_name, info.host)
974
+ template_installation(info.ilm_index, template_name, customize_template, application_name, @logstash_format ? logstash_prefix : index_name, ilm_policy_id, info.host)
938
975
  end
939
976
 
940
977
  begin
@@ -947,7 +984,7 @@ EOC
947
984
  data
948
985
  end
949
986
 
950
- response = client(info.host).bulk body: prepared_data, index: info.index
987
+ response = client(info.host, compression).bulk body: prepared_data, index: info.index
951
988
  log.on_trace { log.trace "bulk response: #{response}" }
952
989
 
953
990
  if response['errors']
@@ -35,16 +35,18 @@ module Fluent::Plugin
35
35
  end
36
36
 
37
37
 
38
- def client(host = nil)
38
+ def client(host = nil, compress_connection = false)
39
39
  # check here to see if we already have a client connection for the given host
40
40
  connection_options = get_connection_options(host)
41
41
 
42
42
  @_es = nil unless is_existing_connection(connection_options[:hosts])
43
+ @_es = nil unless @compressable_connection == compress_connection
43
44
 
44
45
  @_es ||= begin
46
+ @compressable_connection = compress_connection
45
47
  @current_config = connection_options[:hosts].clone
46
48
  adapter_conf = lambda {|f| f.adapter @http_backend, @backend_options }
47
- gzip_headers = if compression
49
+ gzip_headers = if compress_connection
48
50
  {'Content-Encoding' => 'gzip'}
49
51
  else
50
52
  {}
@@ -67,7 +69,7 @@ module Fluent::Plugin
67
69
  password: @password,
68
70
  scheme: @scheme
69
71
  },
70
- compression: compression,
72
+ compression: compress_connection,
71
73
  }), &adapter_conf)
72
74
  Elasticsearch::Client.new transport: transport
73
75
  end
@@ -228,7 +230,7 @@ module Fluent::Plugin
228
230
  else
229
231
  data
230
232
  end
231
- response = client(host).bulk body: prepared_data, index: index
233
+ response = client(host, compression).bulk body: prepared_data, index: index
232
234
  if response['errors']
233
235
  log.error "Could not push log to Elasticsearch: #{response}"
234
236
  end
@@ -0,0 +1,73 @@
1
+ require_relative '../helper'
2
+ require 'fluent/test/driver/output'
3
+ require 'fluent/plugin/out_elasticsearch'
4
+
5
+ class ElasticsearchFallbackSelectorTest < Test::Unit::TestCase
6
+ attr_accessor :index_cmds
7
+
8
+ def setup
9
+ Fluent::Test.setup
10
+ @driver = nil
11
+ log = Fluent::Engine.log
12
+ log.out.logs.slice!(0, log.out.logs.length)
13
+ end
14
+
15
+ def stub_elastic(url="http://localhost:9200/_bulk")
16
+ stub_request(:post, url).with do |req|
17
+ @index_cmds = req.body.split("\n").map {|r| JSON.parse(r) }
18
+ end
19
+ end
20
+
21
+ def stub_elastic_info(url="http://localhost:9200/", version="6.4.2")
22
+ body ="{\"version\":{\"number\":\"#{version}\"}}"
23
+ stub_request(:get, url).to_return({:status => 200, :body => body, :headers => { 'Content-Type' => 'json' } })
24
+ end
25
+
26
+ def stub_elastic_info_not_found(url="http://localhost:9200/", version="6.4.2")
27
+ stub_request(:get, url).to_return(:status => [404, "Not Found"])
28
+ end
29
+
30
+ def stub_elastic_info_unavailable(url="http://localhost:9200/", version="6.4.2")
31
+ stub_request(:get, url).to_return(:status => [503, "Service Unavailable"])
32
+ end
33
+
34
+ def sample_record(content={})
35
+ {'age' => 26, 'request_id' => '42', 'parent_id' => 'parent', 'routing_id' => 'routing'}.merge(content)
36
+ end
37
+
38
+ def driver(conf='')
39
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::ElasticsearchOutput) {
40
+ # v0.12's test driver assume format definition. This simulates ObjectBufferedOutput format
41
+ if !defined?(Fluent::Plugin::Output)
42
+ def format(tag, time, record)
43
+ [time, record].to_msgpack
44
+ end
45
+ end
46
+ }.configure(conf)
47
+ end
48
+
49
+ def test_fallback_on_info
50
+ stub_elastic_info_not_found("http://localhost:9202/")
51
+ stub_elastic_info_unavailable("http://localhost:9201/")
52
+ stub_elastic_info
53
+ stub_elastic
54
+ config = %[
55
+ hosts localhost:9202,localhost:9201,localhost:9200
56
+ selector_class_name Fluent::Plugin::ElasticseatchFallbackSelector
57
+ @log_level debug
58
+ with_transporter_log true
59
+ reload_connections true
60
+ reload_after 10
61
+ ]
62
+ assert_raise(Elasticsearch::Transport::Transport::Errors::NotFound) do
63
+ driver(config)
64
+ end
65
+ driver.run(default_tag: 'test') do
66
+ driver.feed(sample_record)
67
+ end
68
+ assert_equal(2, index_cmds.length)
69
+ assert_equal("fluentd", index_cmds.first['index']['_index'])
70
+ end
71
+
72
+ # TODO: on feed phase test case
73
+ end