logstash-output-elasticsearch 11.15.1-java → 11.15.4-java

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