logstash-output-elasticsearch 11.8.0-java → 11.9.0-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28dfa1d2a757a4cb14aa7972f13c13f78975a52db0cac8d39425fa6bb34a462c
4
- data.tar.gz: 7a9b9e05f59e2d9024f8faf7d6d5faa58aeb123cb77fd4f89b88826787d5d4f5
3
+ metadata.gz: ad30f3f3af7faaebf3409ac23375e99531def7d30940330f0f617e007f341b1d
4
+ data.tar.gz: d06a9954211995b266d08cfbc6586164275d5d269ef5716ba18d4f7e3ca4cf5c
5
5
  SHA512:
6
- metadata.gz: 8ee227e4dcb1f1af9cb7d1baf11cabb1e4f1a8ab87781494ca71f2d3a9ad57e6635819827c00090f597f79d33d8724f1a2e5e4047f506a0fde144c0dc5d4d71a
7
- data.tar.gz: 6ad8e717ade5b09f46cf4bab5d9749c782cf654dd3e69f79010a230237cd251b7fd034b18f42ed09ffb46aa729c8596eae62a3cb41a8b33c36a7c23e7084adcf
6
+ metadata.gz: ad84072b2dcc3d3a567b18cc89bbd3390e93303e952188053a8f61cbefbddb0a8bf46453fb8df0b94cecaf29304452b85fbe7a822bb9ece0763c4278b514ff91
7
+ data.tar.gz: 348d53c674b7b2730ce918403e705ddbda5f9745ca2564e922d3762bff9de7a455ff871c269669dc1e74d5e0c5f89c7997221eca4c851a073b1540b2a8bbdc15
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 11.9.0
2
+ - Feature: force unresolved dynamic index names to be sent into DLQ. This feature could be explicitly disabled using `dlq_on_failed_indexname_interpolation` setting [#1084](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1084)
3
+
1
4
  ## 11.8.0
2
5
  - Feature: Adds a new `dlq_custom_codes` option to customize DLQ codes [#1067](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1067)
3
6
 
data/docs/index.asciidoc CHANGED
@@ -320,6 +320,7 @@ This plugin supports the following configuration options plus the
320
320
  | <<plugins-{type}s-{plugin}-data_stream_sync_fields>> |<<boolean,boolean>>|No
321
321
  | <<plugins-{type}s-{plugin}-data_stream_type>> |<<string,string>>|No
322
322
  | <<plugins-{type}s-{plugin}-dlq_custom_codes>> |<<number,number>>|No
323
+ | <<plugins-{type}s-{plugin}-dlq_on_failed_indexname_interpolation>> |<<boolean,boolean>>|No
323
324
  | <<plugins-{type}s-{plugin}-doc_as_upsert>> |<<boolean,boolean>>|No
324
325
  | <<plugins-{type}s-{plugin}-document_id>> |<<string,string>>|No
325
326
  | <<plugins-{type}s-{plugin}-document_type>> |<<string,string>>|No
@@ -533,6 +534,14 @@ This list is an addition to the ordinary error codes considered for this feature
533
534
  It's considered a configuration error to re-use the same predefined codes for success, DLQ or conflict.
534
535
  The option accepts a list of natural numbers corresponding to HTTP errors codes.
535
536
 
537
+ [id="plugins-{type}s-{plugin}-dlq_on_failed_indexname_interpolation"]
538
+ ===== `dlq_on_failed_indexname_interpolation`
539
+
540
+ * Value type is <<boolean,boolean>>
541
+ * Default value is `true`.
542
+
543
+ If enabled, failed index name interpolation events go into dead letter queue.
544
+
536
545
  [id="plugins-{type}s-{plugin}-doc_as_upsert"]
537
546
  ===== `doc_as_upsert`
538
547
 
@@ -261,6 +261,9 @@ class LogStash::Outputs::ElasticSearch < LogStash::Outputs::Base
261
261
  # The option accepts a list of natural numbers corresponding to HTTP errors codes.
262
262
  config :dlq_custom_codes, :validate => :number, :list => true, :default => []
263
263
 
264
+ # if enabled, failed index name interpolation events go into dead letter queue.
265
+ config :dlq_on_failed_indexname_interpolation, :validate => :boolean, :default => true
266
+
264
267
  attr_reader :client
265
268
  attr_reader :default_index
266
269
  attr_reader :default_ilm_rollover_alias
@@ -362,11 +365,43 @@ class LogStash::Outputs::ElasticSearch < LogStash::Outputs::Base
362
365
  # Receive an array of events and immediately attempt to index them (no buffering)
363
366
  def multi_receive(events)
364
367
  wait_for_successful_connection if @after_successful_connection_done
365
- retrying_submit map_events(events)
368
+ events_mapped = safe_interpolation_map_events(events)
369
+ retrying_submit(events_mapped.successful_events)
370
+ unless events_mapped.failed_events.empty?
371
+ @logger.error("Can't map some events, needs to be handled by DLQ #{events_mapped.failed_events}")
372
+ send_failed_resolutions_to_dlq(events_mapped.failed_events)
373
+ end
374
+ end
375
+
376
+ # @param: Arrays of EventActionTuple
377
+ private
378
+ def send_failed_resolutions_to_dlq(failed_action_tuples)
379
+ failed_action_tuples.each do |action|
380
+ handle_dlq_status(action, "warn", "Could not resolve dynamic index")
381
+ end
382
+ end
383
+
384
+ MapEventsResult = Struct.new(:successful_events, :failed_events)
385
+
386
+ private
387
+ def safe_interpolation_map_events(events)
388
+ successful_events = [] # list of LogStash::Outputs::ElasticSearch::EventActionTuple
389
+ failed_events = [] # list of LogStash::Event
390
+ events.each do |event|
391
+ begin
392
+ successful_events << @event_mapper.call(event)
393
+ rescue IndexInterpolationError, e
394
+ action = event.sprintf(@action || 'index')
395
+ event_action_tuple = EventActionTuple.new(action, [], event)
396
+ failed_events << event_action_tuple
397
+ end
398
+ end
399
+ MapEventsResult.new(successful_events, failed_events)
366
400
  end
367
401
 
402
+ public
368
403
  def map_events(events)
369
- events.map(&@event_mapper)
404
+ safe_interpolation_map_events(events).successful_events
370
405
  end
371
406
 
372
407
  def wait_for_successful_connection
@@ -441,12 +476,24 @@ class LogStash::Outputs::ElasticSearch < LogStash::Outputs::Base
441
476
 
442
477
  end
443
478
 
479
+ class IndexInterpolationError < ArgumentError
480
+ attr_reader :bad_formatted_index
481
+
482
+ def initialize(bad_formatted_index)
483
+ super("Badly formatted index, after interpolation still contains placeholder: [#{bad_formatted_index}]")
484
+
485
+ @bad_formatted_index = bad_formatted_index
486
+ end
487
+ end
488
+
444
489
  # @return Hash (initial) parameters for given event
445
490
  # @private shared event params factory between index and data_stream mode
446
491
  def common_event_params(event)
492
+ sprintf_index = @event_target.call(event)
493
+ raise IndexInterpolationError, sprintf_index if sprintf_index.match(/%{.*?}/) && dlq_on_failed_indexname_interpolation
447
494
  params = {
448
495
  :_id => @document_id ? event.sprintf(@document_id) : nil,
449
- :_index => @event_target.call(event),
496
+ :_index => sprintf_index,
450
497
  routing_field_name => @routing ? event.sprintf(@routing) : nil
451
498
  }
452
499
 
@@ -206,19 +206,23 @@ module LogStash; module PluginMixins; module ElasticSearch
206
206
  doubled > @retry_max_interval ? @retry_max_interval : doubled
207
207
  end
208
208
 
209
- def handle_dlq_status(message, action, status, response)
209
+ def handle_dlq_response(message, action, status, response)
210
+ _, action_params = action.event, [action[0], action[1], action[2]]
211
+
212
+ # TODO: Change this to send a map with { :status => status, :action => action } in the future
213
+ detailed_message = "#{message} status: #{status}, action: #{action_params}, response: #{response}"
214
+
215
+ log_level = dig_value(response, 'index', 'error', 'type') == 'invalid_index_name_exception' ? :error : :warn
216
+
217
+ handle_dlq_status(action, log_level, detailed_message)
218
+ end
219
+
220
+ def handle_dlq_status(action, log_level, message)
210
221
  # To support bwc, we check if DLQ exists. otherwise we log and drop event (previous behavior)
211
222
  if @dlq_writer
212
- event, action = action.event, [action[0], action[1], action[2]]
213
- # TODO: Change this to send a map with { :status => status, :action => action } in the future
214
- @dlq_writer.write(event, "#{message} status: #{status}, action: #{action}, response: #{response}")
223
+ @dlq_writer.write(action.event, "#{message}")
215
224
  else
216
- if dig_value(response, 'index', 'error', 'type') == 'invalid_index_name_exception'
217
- level = :error
218
- else
219
- level = :warn
220
- end
221
- @logger.send level, message, status: status, action: action, response: response
225
+ @logger.send log_level, message
222
226
  end
223
227
  end
224
228
 
@@ -269,7 +273,7 @@ module LogStash; module PluginMixins; module ElasticSearch
269
273
  @logger.warn "Failed action", status: status, action: action, response: response if log_failure_type?(error)
270
274
  next
271
275
  elsif @dlq_codes.include?(status)
272
- handle_dlq_status("Could not index event to Elasticsearch.", action, status, response)
276
+ handle_dlq_response("Could not index event to Elasticsearch.", action, status, response)
273
277
  @document_level_metrics.increment(:non_retryable_failures)
274
278
  next
275
279
  else
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-elasticsearch'
3
- s.version = '11.8.0'
3
+ s.version = '11.9.0'
4
4
  s.licenses = ['apache-2.0']
5
5
  s.summary = "Stores logs in Elasticsearch"
6
6
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -1,5 +1,6 @@
1
1
  require_relative "../../../spec/es_spec_helper"
2
2
  require "logstash/outputs/elasticsearch"
3
+ require 'cgi'
3
4
 
4
5
  describe "TARGET_BULK_BYTES", :integration => true do
5
6
  let(:target_bulk_bytes) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES }
@@ -45,16 +46,57 @@ describe "TARGET_BULK_BYTES", :integration => true do
45
46
  end
46
47
  end
47
48
 
48
- describe "indexing" do
49
+ def curl_and_get_json_response(url, method: :get, retrieve_err_payload: false); require 'open3'
50
+ cmd = "curl -s -v --show-error #{curl_opts} -X #{method.to_s.upcase} -k #{url}"
51
+ begin
52
+ out, err, status = Open3.capture3(cmd)
53
+ rescue Errno::ENOENT
54
+ fail "curl not available, make sure curl binary is installed and available on $PATH"
55
+ end
56
+
57
+ if status.success?
58
+ http_status = err.match(/< HTTP\/1.1 (\d+)/)[1] || '0' # < HTTP/1.1 200 OK\r\n
59
+
60
+ if http_status.strip[0].to_i > 2
61
+ error = (LogStash::Json.load(out)['error']) rescue nil
62
+ if error
63
+ if retrieve_err_payload
64
+ return error
65
+ else
66
+ fail "#{cmd.inspect} received an error: #{http_status}\n\n#{error.inspect}"
67
+ end
68
+ else
69
+ warn out
70
+ fail "#{cmd.inspect} unexpected response: #{http_status}\n\n#{err}"
71
+ end
72
+ end
73
+
74
+ LogStash::Json.load(out)
75
+ else
76
+ warn out
77
+ fail "#{cmd.inspect} process failed: #{status}\n\n#{err}"
78
+ end
79
+ end
80
+
81
+ describe "indexing with sprintf resolution", :integration => true do
49
82
  let(:message) { "Hello from #{__FILE__}" }
50
83
  let(:event) { LogStash::Event.new("message" => message, "type" => type) }
51
- let(:index) { 10.times.collect { rand(10).to_s }.join("") }
84
+ let (:index) { "%{[index_name]}_dynamic" }
52
85
  let(:type) { ESHelper.es_version_satisfies?("< 7") ? "doc" : "_doc" }
53
- let(:event_count) { 1 + rand(2) }
54
- let(:config) { "not implemented" }
86
+ let(:event_count) { 1 }
87
+ let(:user) { "simpleuser" }
88
+ let(:password) { "abc123" }
89
+ let(:config) do
90
+ {
91
+ "hosts" => [ get_host_port ],
92
+ "user" => user,
93
+ "password" => password,
94
+ "index" => index
95
+ }
96
+ end
55
97
  let(:events) { event_count.times.map { event }.to_a }
56
98
  subject { LogStash::Outputs::ElasticSearch.new(config) }
57
-
99
+
58
100
  let(:es_url) { "http://#{get_host_port}" }
59
101
  let(:index_url) { "#{es_url}/#{index}" }
60
102
 
@@ -63,33 +105,65 @@ describe "indexing" do
63
105
  let(:es_admin) { 'admin' } # default user added in ES -> 8.x requires auth credentials for /_refresh etc
64
106
  let(:es_admin_pass) { 'elastic' }
65
107
 
66
- def curl_and_get_json_response(url, method: :get); require 'open3'
67
- cmd = "curl -s -v --show-error #{curl_opts} -X #{method.to_s.upcase} -k #{url}"
68
- begin
69
- out, err, status = Open3.capture3(cmd)
70
- rescue Errno::ENOENT
71
- fail "curl not available, make sure curl binary is installed and available on $PATH"
72
- end
108
+ let(:initial_events) { [] }
73
109
 
74
- if status.success?
75
- http_status = err.match(/< HTTP\/1.1 (\d+)/)[1] || '0' # < HTTP/1.1 200 OK\r\n
110
+ let(:do_register) { true }
76
111
 
77
- if http_status.strip[0].to_i > 2
78
- error = (LogStash::Json.load(out)['error']) rescue nil
79
- if error
80
- fail "#{cmd.inspect} received an error: #{http_status}\n\n#{error.inspect}"
81
- else
82
- warn out
83
- fail "#{cmd.inspect} unexpected response: #{http_status}\n\n#{err}"
84
- end
85
- end
112
+ before do
113
+ subject.register if do_register
114
+ subject.multi_receive(initial_events) if initial_events
115
+ end
86
116
 
87
- LogStash::Json.load(out)
88
- else
89
- warn out
90
- fail "#{cmd.inspect} process failed: #{status}\n\n#{err}"
117
+ after do
118
+ subject.do_close
119
+ end
120
+
121
+ let(:event) { LogStash::Event.new("message" => message, "type" => type, "index_name" => "test") }
122
+
123
+ it "should index successfully when field is resolved" do
124
+ expected_index_name = "test_dynamic"
125
+ subject.multi_receive(events)
126
+
127
+ # curl_and_get_json_response "#{es_url}/_refresh", method: :post
128
+
129
+ result = curl_and_get_json_response "#{es_url}/#{expected_index_name}"
130
+
131
+ expect(result[expected_index_name]).not_to be(nil)
132
+ end
133
+
134
+ context "when dynamic field doesn't resolve the index_name" do
135
+ let(:event) { LogStash::Event.new("message" => message, "type" => type) }
136
+ let(:dlq_writer) { double('DLQ writer') }
137
+ before { subject.instance_variable_set('@dlq_writer', dlq_writer) }
138
+
139
+ it "should doesn't create an index name with unresolved placeholders" do
140
+ expect(dlq_writer).to receive(:write).once.with(event, /Could not resolve dynamic index/)
141
+ subject.multi_receive(events)
142
+
143
+ escaped_index_name = CGI.escape("%{[index_name]}_dynamic")
144
+ result = curl_and_get_json_response "#{es_url}/#{escaped_index_name}", retrieve_err_payload: true
145
+ expect(result["root_cause"].first()["type"]).to eq("index_not_found_exception")
91
146
  end
92
147
  end
148
+ end
149
+
150
+ describe "indexing" do
151
+ let(:message) { "Hello from #{__FILE__}" }
152
+ let(:event) { LogStash::Event.new("message" => message, "type" => type) }
153
+ let(:index) { 10.times.collect { rand(10).to_s }.join("") }
154
+ let(:type) { ESHelper.es_version_satisfies?("< 7") ? "doc" : "_doc" }
155
+ let(:event_count) { 1 + rand(2) }
156
+ let(:config) { "not implemented" }
157
+ let(:events) { event_count.times.map { event }.to_a }
158
+ subject { LogStash::Outputs::ElasticSearch.new(config) }
159
+
160
+ let(:es_url) { "http://#{get_host_port}" }
161
+ let(:index_url) { "#{es_url}/#{index}" }
162
+
163
+ let(:curl_opts) { nil }
164
+
165
+ let(:es_admin) { 'admin' } # default user added in ES -> 8.x requires auth credentials for /_refresh etc
166
+ let(:es_admin_pass) { 'elastic' }
93
167
 
94
168
  let(:initial_events) { [] }
95
169
 
@@ -768,6 +768,7 @@ describe LogStash::Outputs::ElasticSearch do
768
768
 
769
769
  context 'handling elasticsearch document-level status meant for the DLQ' do
770
770
  let(:options) { { "manage_template" => false } }
771
+ let(:action) { LogStash::Outputs::ElasticSearch::EventActionTuple.new(:action, :params, LogStash::Event.new("foo" => "bar")) }
771
772
 
772
773
  context 'when @dlq_writer is nil' do
773
774
  before { subject.instance_variable_set '@dlq_writer', nil }
@@ -777,8 +778,7 @@ describe LogStash::Outputs::ElasticSearch do
777
778
  it 'should log at ERROR level' do
778
779
  subject.instance_variable_set(:@logger, double("logger").as_null_object)
779
780
  mock_response = { 'index' => { 'error' => { 'type' => 'invalid_index_name_exception' } } }
780
- subject.handle_dlq_status("Could not index event to Elasticsearch.",
781
- [:action, :params, :event], :some_status, mock_response)
781
+ subject.handle_dlq_response("Could not index event to Elasticsearch.", action, :some_status, mock_response)
782
782
  end
783
783
  end
784
784
 
@@ -786,10 +786,9 @@ describe LogStash::Outputs::ElasticSearch do
786
786
  it 'should log at WARN level' do
787
787
  logger = double("logger").as_null_object
788
788
  subject.instance_variable_set(:@logger, logger)
789
- expect(logger).to receive(:warn).with(/Could not index/, hash_including(:status, :action, :response))
789
+ expect(logger).to receive(:warn).with(a_string_including "Could not index event to Elasticsearch. status: some_status, action: [:action, :params, {")
790
790
  mock_response = { 'index' => { 'error' => { 'type' => 'illegal_argument_exception' } } }
791
- subject.handle_dlq_status("Could not index event to Elasticsearch.",
792
- [:action, :params, :event], :some_status, mock_response)
791
+ subject.handle_dlq_response("Could not index event to Elasticsearch.", action, :some_status, mock_response)
793
792
  end
794
793
  end
795
794
 
@@ -797,11 +796,10 @@ describe LogStash::Outputs::ElasticSearch do
797
796
  it 'should not fail, but just log a warning' do
798
797
  logger = double("logger").as_null_object
799
798
  subject.instance_variable_set(:@logger, logger)
800
- expect(logger).to receive(:warn).with(/Could not index/, hash_including(:status, :action, :response))
799
+ expect(logger).to receive(:warn).with(a_string_including "Could not index event to Elasticsearch. status: some_status, action: [:action, :params, {")
801
800
  mock_response = { 'index' => {} }
802
801
  expect do
803
- subject.handle_dlq_status("Could not index event to Elasticsearch.",
804
- [:action, :params, :event], :some_status, mock_response)
802
+ subject.handle_dlq_response("Could not index event to Elasticsearch.", action, :some_status, mock_response)
805
803
  end.to_not raise_error
806
804
  end
807
805
  end
@@ -821,7 +819,7 @@ describe LogStash::Outputs::ElasticSearch do
821
819
  expect(dlq_writer).to receive(:write).once.with(event, /Could not index/)
822
820
  mock_response = { 'index' => { 'error' => { 'type' => 'illegal_argument_exception' } } }
823
821
  action = LogStash::Outputs::ElasticSearch::EventActionTuple.new(:action, :params, event)
824
- subject.handle_dlq_status("Could not index event to Elasticsearch.", action, 404, mock_response)
822
+ subject.handle_dlq_response("Could not index event to Elasticsearch.", action, 404, mock_response)
825
823
  end
826
824
  end
827
825
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.8.0
4
+ version: 11.9.0
5
5
  platform: java
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-15 00:00:00.000000000 Z
11
+ date: 2022-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement