logstash-output-elasticsearch 11.15.1-java → 11.15.4-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: 6ad6b80e4c6db7a54c8b7a830f6a922a7057b2e7585820db8a6c093b57db3800
4
- data.tar.gz: 82cd893394fc7ac08ae7a79b2cd953f80fbf258ec2faad225300a2ce516ab208
3
+ metadata.gz: 35c442c72cb3629cc3d17b28bffce6351c6e61a6332c254bef41ad89c9cc2810
4
+ data.tar.gz: 396df2b1625d0b62f8f71982203f01f1fa8901a6ccaf2098e10b51c4f77c9d80
5
5
  SHA512:
6
- metadata.gz: b861d195377c3eeadf4489d21780be6627597aa0b93992bd7378b7cf680f6f99ac120ae342a4b48739ee6ef2652aa9238ee9fc08838cbed0f6dae0c887b06daa
7
- data.tar.gz: 16091be406132dbb590ad76a0ba2e83cb0729717d8229b410bccbcabc660a7a26146fd2c7aaae6976f954bd17f1f763c6353552699ffd67173efb9917b913658
6
+ metadata.gz: 3acda17f89403df63a709bedb84481c259178c97963120cce9452565d1d93981ac4a8b3766c9de2ee356d540f38109afbaf03cba3de17402551ee55cc09e74ce
7
+ data.tar.gz: fb405743b1746631fe84e4b346810df84b21955bb7ef0d5adf122d03d9e7fc739fde8623947fc9ae94d1139cb34ecef869eb18141344231183d844a334b3b6dd
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 11.15.4
2
+ - Improved connection handling under several partial-failure scenarios [#1130](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1130)
3
+ - Ensures an HTTP connection can be established before adding the connection to the pool
4
+ - Ensures that the version of the connected Elasticsearch is retrieved _successfully_ before the connection is added to the pool.
5
+ - Fixes a crash that could occur when the plugin is configured to connect to a live HTTP resource that is _not_ Elasticsearch
6
+
7
+ ## 11.15.3
8
+ - Removes the ECS v8 unreleased preview warning [#1131](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1131)
9
+
10
+ ## 11.15.2
11
+ - Restores DLQ logging behavior from 11.8.x to include the action-tuple as structured [#1105](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1105)
12
+
1
13
  ## 11.15.1
2
14
  - Move async finish_register to bottom of register to avoid race condition [#1125](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/1125)
3
15
 
@@ -229,14 +229,16 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
229
229
  end
230
230
 
231
231
  def health_check_request(url)
232
- logger.debug("Running health check to see if an ES connection is working", url: url.sanitized.to_s, path: @healthcheck_path)
233
- perform_request_to_url(url, :head, @healthcheck_path)
232
+ response = perform_request_to_url(url, :head, @healthcheck_path)
233
+ raise BadResponseCodeError.new(response.code, url, nil, response.body) unless (200..299).cover?(response.code)
234
234
  end
235
235
 
236
236
  def healthcheck!(register_phase = true)
237
237
  # Try to keep locking granularity low such that we don't affect IO...
238
238
  @state_mutex.synchronize { @url_info.select {|url,meta| meta[:state] != :alive } }.each do |url,meta|
239
239
  begin
240
+ logger.debug("Running health check to see if an Elasticsearch connection is working",
241
+ :healthcheck_url => url.sanitized.to_s, :path => @healthcheck_path)
240
242
  health_check_request(url)
241
243
 
242
244
  # when called from resurrectionist skip the product check done during register phase
@@ -249,6 +251,10 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
249
251
  logger.warn("Restored connection to ES instance", url: url.sanitized.to_s)
250
252
  # We reconnected to this node, check its ES version
251
253
  es_version = get_es_version(url)
254
+ if es_version.nil?
255
+ logger.warn("Failed to retrieve Elasticsearch version data from connected endpoint, connection aborted", :url => url.sanitized.to_s)
256
+ next
257
+ end
252
258
  @state_mutex.synchronize do
253
259
  meta[:version] = es_version
254
260
  set_last_es_version(es_version, url)
@@ -464,8 +470,12 @@ module LogStash; module Outputs; class ElasticSearch; class HttpClient;
464
470
  end
465
471
 
466
472
  def get_es_version(url)
467
- request = perform_request_to_url(url, :get, ROOT_URI_PATH)
468
- LogStash::Json.load(request.body)["version"]["number"] # e.g. "7.10.0"
473
+ response = perform_request_to_url(url, :get, ROOT_URI_PATH)
474
+ return nil unless (200..299).cover?(response.code)
475
+
476
+ response = LogStash::Json.load(response.body)
477
+
478
+ response.fetch('version', {}).fetch('number', nil)
469
479
  end
470
480
 
471
481
  def last_es_version
@@ -330,11 +330,6 @@ class LogStash::Outputs::ElasticSearch < LogStash::Outputs::Base
330
330
  @bulk_request_metrics = metric.namespace(:bulk_requests)
331
331
  @document_level_metrics = metric.namespace(:documents)
332
332
 
333
- if ecs_compatibility == :v8
334
- @logger.warn("Elasticsearch Output configured with `ecs_compatibility => v8`, which resolved to an UNRELEASED preview of version 8.0.0 of the Elastic Common Schema. " +
335
- "Once ECS v8 and an updated release of this plugin are publicly available, you will need to update this plugin to resolve this warning.")
336
- end
337
-
338
333
  @after_successful_connection_thread = after_successful_connection do
339
334
  begin
340
335
  finish_register
@@ -405,7 +400,7 @@ class LogStash::Outputs::ElasticSearch < LogStash::Outputs::Base
405
400
 
406
401
  event_mapping_errors.each do |event_mapping_error|
407
402
  detailed_message = "#{event_mapping_error.message}; event: `#{event_mapping_error.event.to_hash_with_metadata}`"
408
- handle_dlq_status(event_mapping_error.event, :warn, detailed_message)
403
+ @dlq_writer ? @dlq_writer.write(event_mapping_error.event, detailed_message) : @logger.warn(detailed_message)
409
404
  end
410
405
  @document_level_metrics.increment(:non_retryable_failures, event_mapping_errors.size)
411
406
  end
@@ -422,7 +417,7 @@ class LogStash::Outputs::ElasticSearch < LogStash::Outputs::Base
422
417
  successful_events << @event_mapper.call(event)
423
418
  rescue EventMappingError => ie
424
419
  event_mapping_errors << FailedEventMapping.new(event, ie.message)
425
- end
420
+ end
426
421
  end
427
422
  MapEventsResult.new(successful_events, event_mapping_errors)
428
423
  end
@@ -227,22 +227,16 @@ module LogStash; module PluginMixins; module ElasticSearch
227
227
  end
228
228
 
229
229
  def handle_dlq_response(message, action, status, response)
230
- _, action_params = action.event, [action[0], action[1], action[2]]
230
+ event, action_params = action.event, [action[0], action[1], action[2]]
231
231
 
232
- # TODO: Change this to send a map with { :status => status, :action => action } in the future
233
- detailed_message = "#{message} status: #{status}, action: #{action_params}, response: #{response}"
234
-
235
- log_level = dig_value(response, 'index', 'error', 'type') == 'invalid_index_name_exception' ? :error : :warn
236
-
237
- handle_dlq_status(action.event, log_level, detailed_message)
238
- end
239
-
240
- def handle_dlq_status(event, log_level, message)
241
- # To support bwc, we check if DLQ exists. otherwise we log and drop event (previous behavior)
242
232
  if @dlq_writer
243
- @dlq_writer.write(event, "#{message}")
233
+ # TODO: Change this to send a map with { :status => status, :action => action } in the future
234
+ detailed_message = "#{message} status: #{status}, action: #{action_params}, response: #{response}"
235
+ @dlq_writer.write(event, "#{detailed_message}")
244
236
  else
245
- @logger.send log_level, message
237
+ log_level = dig_value(response, 'index', 'error', 'type') == 'invalid_index_name_exception' ? :error : :warn
238
+
239
+ @logger.public_send(log_level, message, status: status, action: action_params, response: response)
246
240
  end
247
241
  end
248
242
 
@@ -1,12 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-elasticsearch'
3
- s.version = '11.15.1'
3
+ s.version = '11.15.4'
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"
7
7
  s.authors = ["Elastic"]
8
8
  s.email = 'info@elastic.co'
9
- s.homepage = "http://logstash.net/"
9
+ s.homepage = "https://www.elastic.co/guide/en/logstash/current/index.html"
10
10
  s.require_paths = ["lib"]
11
11
 
12
12
  s.platform = RUBY_PLATFORM
@@ -67,19 +67,24 @@ module ESHelper
67
67
  end
68
68
 
69
69
  RSpec::Matchers.define :have_hits do |expected|
70
+ hits_count_path = ESHelper.es_version_satisfies?(">=7") ? %w(hits total value) : %w(hits total)
71
+
70
72
  match do |actual|
71
- if ESHelper.es_version_satisfies?(">=7")
72
- expected == actual['hits']['total']['value']
73
- else
74
- expected == actual['hits']['total']
75
- end
73
+ @actual_hits_count = actual&.dig(*hits_count_path)
74
+ values_match? expected, @actual_hits_count
75
+ end
76
+ failure_message do |actual|
77
+ "expected that #{actual} with #{@actual_hits_count || "UNKNOWN" } hits would have #{expected} hits"
76
78
  end
77
79
  end
78
80
 
79
81
  RSpec::Matchers.define :have_index_pattern do |expected|
80
82
  match do |actual|
81
- test_against = Array(actual['index_patterns'].nil? ? actual['template'] : actual['index_patterns'])
82
- test_against.include?(expected)
83
+ @actual_index_pattterns = Array(actual['index_patterns'].nil? ? actual['template'] : actual['index_patterns'])
84
+ @actual_index_pattterns.any? { |v| values_match? expected, v }
85
+ end
86
+ failure_message do |actual|
87
+ "expected that #{actual} with index patterns #{@actual_index_pattterns} would have included `#{expected}`"
83
88
  end
84
89
  end
85
90
 
@@ -23,7 +23,9 @@ describe "index template expected behavior", :integration => true do
23
23
 
24
24
  elasticsearch_client.indices.delete_template(:name => '*')
25
25
  # This can fail if there are no indexes, ignore failure.
26
- elasticsearch_client.indices.delete(:index => '*') rescue nil
26
+ elasticsearch_client.indices.delete(:index => '*') rescue puts("DELETE INDICES ERROR: #{$!}")
27
+ # Since we are pinned to ES client 7.x, we need to delete data streams the hard way...
28
+ elasticsearch_client.perform_request("DELETE", "/_data_stream/*") rescue puts("DELETE DATA STREAMS ERROR: #{$!}")
27
29
  end
28
30
 
29
31
  context 'with ecs_compatibility => disabled' do
@@ -48,19 +50,19 @@ describe "index template expected behavior", :integration => true do
48
50
 
49
51
  # Wait or fail until everything's indexed.
50
52
  Stud::try(20.times) do
51
- r = @es.search(index: 'logstash-*')
53
+ r = @es.search(index: 'logstash*')
52
54
  expect(r).to have_hits(8)
53
55
  end
54
56
  end
55
57
 
56
58
  it "permits phrase searching on string fields" do
57
- results = @es.search(:q => "message:\"sample message\"")
59
+ results = @es.search(index: 'logstash*', q: "message:\"sample message\"")
58
60
  expect(results).to have_hits(1)
59
61
  expect(results["hits"]["hits"][0]["_source"]["message"]).to eq("sample message here")
60
62
  end
61
63
 
62
64
  it "numbers dynamically map to a numeric type and permit range queries" do
63
- results = @es.search(:q => "somevalue:[5 TO 105]")
65
+ results = @es.search(index: 'logstash*', q: "somevalue:[5 TO 105]")
64
66
  expect(results).to have_hits(2)
65
67
 
66
68
  values = results["hits"]["hits"].collect { |r| r["_source"]["somevalue"] }
@@ -70,22 +72,22 @@ describe "index template expected behavior", :integration => true do
70
72
  end
71
73
 
72
74
  it "does not create .keyword field for top-level message field" do
73
- results = @es.search(:q => "message.keyword:\"sample message here\"")
75
+ results = @es.search(index: 'logstash*', q: "message.keyword:\"sample message here\"")
74
76
  expect(results).to have_hits(0)
75
77
  end
76
78
 
77
79
  it "creates .keyword field for nested message fields" do
78
- results = @es.search(:q => "somemessage.message.keyword:\"sample nested message here\"")
80
+ results = @es.search(index: 'logstash*', q: "somemessage.message.keyword:\"sample nested message here\"")
79
81
  expect(results).to have_hits(1)
80
82
  end
81
83
 
82
84
  it "creates .keyword field from any string field which is not_analyzed" do
83
- results = @es.search(:q => "country.keyword:\"us\"")
85
+ results = @es.search(index: 'logstash*', q: "country.keyword:\"us\"")
84
86
  expect(results).to have_hits(1)
85
87
  expect(results["hits"]["hits"][0]["_source"]["country"]).to eq("us")
86
88
 
87
89
  # partial or terms should not work.
88
- results = @es.search(:q => "country.keyword:\"u\"")
90
+ results = @es.search(index: 'logstash*', q: "country.keyword:\"u\"")
89
91
  expect(results).to have_hits(0)
90
92
  end
91
93
 
@@ -94,7 +96,7 @@ describe "index template expected behavior", :integration => true do
94
96
  end
95
97
 
96
98
  it "aggregate .keyword results correctly " do
97
- results = @es.search(:body => { "aggregations" => { "my_agg" => { "terms" => { "field" => "country.keyword" } } } })["aggregations"]["my_agg"]
99
+ results = @es.search(index: 'logstash*', body: { "aggregations" => { "my_agg" => { "terms" => { "field" => "country.keyword" } } } })["aggregations"]["my_agg"]
98
100
  terms = results["buckets"].collect { |b| b["key"] }
99
101
 
100
102
  expect(terms).to include("us")
@@ -50,6 +50,8 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
50
50
 
51
51
  describe "healthcheck url handling" do
52
52
  let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://localhost:9200")] }
53
+ let(:success_response) { double("Response", :code => 200) }
54
+
53
55
  before(:example) do
54
56
  expect(adapter).to receive(:perform_request).with(anything, :get, "/", anything, anything) do |url, _, _, _, _|
55
57
  expect(url.path).to be_empty
@@ -60,6 +62,8 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
60
62
  it "performs the healthcheck to the root" do
61
63
  expect(adapter).to receive(:perform_request).with(anything, :head, "/", anything, anything) do |url, _, _, _, _|
62
64
  expect(url.path).to be_empty
65
+
66
+ success_response
63
67
  end
64
68
  expect { subject.healthcheck! }.to raise_error(LogStash::ConfigurationError, "Could not connect to a compatible version of Elasticsearch")
65
69
  end
@@ -71,6 +75,8 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
71
75
  it "performs the healthcheck to the healthcheck_path" do
72
76
  expect(adapter).to receive(:perform_request).with(anything, :head, eq(healthcheck_path), anything, anything) do |url, _, _, _, _|
73
77
  expect(url.path).to be_empty
78
+
79
+ success_response
74
80
  end
75
81
  expect { subject.healthcheck! }.to raise_error(LogStash::ConfigurationError, "Could not connect to a compatible version of Elasticsearch")
76
82
  end
@@ -197,9 +203,10 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
197
203
  "build_flavor" => 'default'}
198
204
  })
199
205
  end
206
+ let(:success_response) { double("head_req", :code => 200)}
200
207
 
201
208
  before :each do
202
- allow(adapter).to receive(:perform_request).with(anything, :head, subject.healthcheck_path, {}, nil)
209
+ allow(adapter).to receive(:perform_request).with(anything, :head, subject.healthcheck_path, {}, nil).and_return(success_response)
203
210
  allow(adapter).to receive(:perform_request).with(anything, :get, subject.healthcheck_path, {}, nil).and_return(version_ok)
204
211
  end
205
212
  let(:initial_urls) { [ ::LogStash::Util::SafeURI.new("http://localhost:9200"), ::LogStash::Util::SafeURI.new("http://localhost:9201"), ::LogStash::Util::SafeURI.new("http://localhost:9202") ] }
@@ -225,7 +232,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
225
232
  u,m = subject.get_connection
226
233
 
227
234
  # The resurrectionist will call this to check on the backend
228
- response = double("response")
235
+ response = double("response", :code => 200)
229
236
  expect(adapter).to receive(:perform_request).with(u, :head, subject.healthcheck_path, {}, nil).and_return(response)
230
237
 
231
238
  subject.return_connection(u)
@@ -1121,41 +1121,63 @@ describe LogStash::Outputs::ElasticSearch do
1121
1121
  end if LOGSTASH_VERSION > '6.0'
1122
1122
 
1123
1123
  context 'handling elasticsearch document-level status meant for the DLQ' do
1124
+ let(:es_api_action) { "CUSTOM_ACTION" }
1125
+ let(:es_api_params) { Hash['_index' => 'MY_INDEX'] }
1126
+
1124
1127
  let(:options) { { "manage_template" => false, "data_stream" => 'false' } }
1125
- let(:action) { LogStash::Outputs::ElasticSearch::EventActionTuple.new(:action, :params, LogStash::Event.new("foo" => "bar")) }
1128
+ let(:action) { LogStash::Outputs::ElasticSearch::EventActionTuple.new(es_api_action, es_api_params, LogStash::Event.new("foo" => "bar")) }
1129
+
1130
+ let(:logger) { double('logger').as_null_object }
1131
+ before(:each) { subject.instance_variable_set(:@logger, logger) }
1126
1132
 
1127
1133
  context 'when @dlq_writer is nil' do
1128
1134
  before { subject.instance_variable_set '@dlq_writer', nil }
1129
- let(:action) { LogStash::Outputs::ElasticSearch::EventActionTuple.new(:action, :params, LogStash::Event.new("foo" => "bar")) }
1130
1135
 
1131
1136
  context 'resorting to previous behaviour of logging the error' do
1132
1137
  context 'getting an invalid_index_name_exception' do
1133
1138
  it 'should log at ERROR level' do
1134
- subject.instance_variable_set(:@logger, double("logger").as_null_object)
1139
+ # logger = double("logger").as_null_object
1140
+ # subject.instance_variable_set(:@logger, logger)
1141
+
1135
1142
  mock_response = { 'index' => { 'error' => { 'type' => 'invalid_index_name_exception' } } }
1136
1143
  subject.handle_dlq_response("Could not index event to Elasticsearch.", action, :some_status, mock_response)
1144
+
1145
+ expect(logger).to have_received(:error).with(a_string_including("Could not index event to Elasticsearch"),
1146
+ a_hash_including(:status => :some_status,
1147
+ :action => [es_api_action, es_api_params, action.event.to_hash],
1148
+ :response => mock_response))
1137
1149
  end
1138
1150
  end
1139
1151
 
1140
1152
  context 'when getting any other exception' do
1141
1153
  it 'should log at WARN level' do
1142
- logger = double("logger").as_null_object
1143
- subject.instance_variable_set(:@logger, logger)
1144
- expect(logger).to receive(:warn).with(a_string_including "Could not index event to Elasticsearch. status: some_status, action: [:action, :params, {")
1154
+ # logger = double("logger").as_null_object
1155
+ # subject.instance_variable_set(:@logger, logger)
1156
+
1145
1157
  mock_response = { 'index' => { 'error' => { 'type' => 'illegal_argument_exception' } } }
1146
1158
  subject.handle_dlq_response("Could not index event to Elasticsearch.", action, :some_status, mock_response)
1159
+
1160
+ expect(logger).to have_received(:warn).with(a_string_including("Could not index event to Elasticsearch"),
1161
+ a_hash_including(:status => :some_status,
1162
+ :action => [es_api_action, es_api_params, action.event.to_hash],
1163
+ :response => mock_response))
1147
1164
  end
1148
1165
  end
1149
1166
 
1150
1167
  context 'when the response does not include [error]' do
1151
1168
  it 'should not fail, but just log a warning' do
1152
- logger = double("logger").as_null_object
1153
- subject.instance_variable_set(:@logger, logger)
1154
- expect(logger).to receive(:warn).with(a_string_including "Could not index event to Elasticsearch. status: some_status, action: [:action, :params, {")
1169
+ # logger = double("logger").as_null_object
1170
+ # subject.instance_variable_set(:@logger, logger)
1171
+
1155
1172
  mock_response = { 'index' => {} }
1156
1173
  expect do
1157
1174
  subject.handle_dlq_response("Could not index event to Elasticsearch.", action, :some_status, mock_response)
1158
1175
  end.to_not raise_error
1176
+
1177
+ expect(logger).to have_received(:warn).with(a_string_including("Could not index event to Elasticsearch"),
1178
+ a_hash_including(:status => :some_status,
1179
+ :action => [es_api_action, es_api_params, action.event.to_hash],
1180
+ :response => mock_response))
1159
1181
  end
1160
1182
  end
1161
1183
  end
@@ -1175,6 +1197,8 @@ describe LogStash::Outputs::ElasticSearch do
1175
1197
  mock_response = { 'index' => { 'error' => { 'type' => 'illegal_argument_exception' } } }
1176
1198
  action = LogStash::Outputs::ElasticSearch::EventActionTuple.new(:action, :params, event)
1177
1199
  subject.handle_dlq_response("Could not index event to Elasticsearch.", action, 404, mock_response)
1200
+
1201
+ expect(logger).to_not have_received(:warn).with(a_string_including("Could not index event to Elasticsearch"))
1178
1202
  end
1179
1203
  end
1180
1204
 
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.15.1
4
+ version: 11.15.4
5
5
  platform: java
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-20 00:00:00.000000000 Z
11
+ date: 2023-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -335,7 +335,7 @@ files:
335
335
  - spec/unit/outputs/elasticsearch_ssl_spec.rb
336
336
  - spec/unit/outputs/error_whitelist_spec.rb
337
337
  - spec/unit/outputs/license_check_spec.rb
338
- homepage: http://logstash.net/
338
+ homepage: https://www.elastic.co/guide/en/logstash/current/index.html
339
339
  licenses:
340
340
  - apache-2.0
341
341
  metadata:
@@ -356,7 +356,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
356
356
  - !ruby/object:Gem::Version
357
357
  version: '0'
358
358
  requirements: []
359
- rubygems_version: 3.2.29
359
+ rubygems_version: 3.2.33
360
360
  signing_key:
361
361
  specification_version: 4
362
362
  summary: Stores logs in Elasticsearch