logstash-output-elasticsearch 10.8.4-java → 10.8.6-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: 7bbaed62e36b6543bf68420603a64e84f39a61e3ffaaf3456f28fad73295430c
4
- data.tar.gz: '0682c7ac69384d7db244e33186237461c148683cc205b72660251e5d3041765c'
3
+ metadata.gz: 69557d21ffe4079cabafcf86949f41d85cb6781f8898cebdc54b354117333b6b
4
+ data.tar.gz: a65b40a961335837f9ccff55472c0aeef033c5248cdcc579ffa98c6560fa377c
5
5
  SHA512:
6
- metadata.gz: 8a546bff1b7623d8e37fd11b9551691ff87072831f81b00af1c4bfd8086e8f7ee45fad2b78682f68927e97d883dfc923a90f76bc53f648885fcd3d5d3c437028
7
- data.tar.gz: 4ea4dc460aa38935cd4e72d2c35c069fb372d02c99fa5cf47e48dbce13994d3615296a3601c8a6c6641f6db587ec0776bc04fcd7b7902c32f31c8c6e95eb4340
6
+ metadata.gz: e8be38c81c89f8dca5dad83c79106180967cb5ed6806ed4a0ce97db1296a15bd8a462da80ef4a663807648164ac410d3d57fc46b2412ef497b1f9d0a4d7b57c6
7
+ data.tar.gz: 1843e98054e65374fe4b72c5938b0d808fecca799294783893d75c955977ec0d34020cd688ec7a16e4e22bf8c6c2b9e53343e9bfd4dd0cbccee10e601d0b2e0f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 10.8.6
2
+ - Fixed an issue where a single over-size event being rejected by Elasticsearch would cause the entire entire batch to be retried indefinitely. The oversize event will still be retried on its own and logging has been improved to include payload sizes in this situation [#972](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/972)
3
+ - Fixed an issue with `http_compression => true` where a well-compressed payload could fit under our outbound 20MB limit but expand beyond Elasticsearch's 100MB limit, causing bulk failures. Bulk grouping is now determined entirely by the decompressed payload size [#823](https://github.com/logstash-plugins/logstash-output-elasticsearch/issues/823)
4
+ - Improved debug-level logging about bulk requests.
5
+
6
+ ## 10.8.5
7
+ - Feat: assert returned item count from _bulk [#997](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/997)
8
+
1
9
  ## 10.8.4
2
10
  - Fixed an issue where a retried request would drop "update" parameters [#800](https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/800)
3
11
 
@@ -109,27 +109,50 @@ module LogStash; module Outputs; class ElasticSearch;
109
109
  body_stream = StringIO.new
110
110
  if http_compression
111
111
  body_stream.set_encoding "BINARY"
112
- stream_writer = Zlib::GzipWriter.new(body_stream, Zlib::DEFAULT_COMPRESSION, Zlib::DEFAULT_STRATEGY)
113
- else
112
+ stream_writer = gzip_writer(body_stream)
113
+ else
114
114
  stream_writer = body_stream
115
115
  end
116
116
  bulk_responses = []
117
- bulk_actions.each do |action|
117
+ batch_actions = []
118
+ bulk_actions.each_with_index do |action, index|
118
119
  as_json = action.is_a?(Array) ?
119
120
  action.map {|line| LogStash::Json.dump(line)}.join("\n") :
120
121
  LogStash::Json.dump(action)
121
122
  as_json << "\n"
122
- if (body_stream.size + as_json.bytesize) > TARGET_BULK_BYTES
123
- bulk_responses << bulk_send(body_stream) unless body_stream.size == 0
123
+ if (stream_writer.pos + as_json.bytesize) > TARGET_BULK_BYTES && stream_writer.pos > 0
124
+ stream_writer.flush # ensure writer has sync'd buffers before reporting sizes
125
+ logger.debug("Sending partial bulk request for batch with one or more actions remaining.",
126
+ :action_count => batch_actions.size,
127
+ :payload_size => stream_writer.pos,
128
+ :content_length => body_stream.size,
129
+ :batch_offset => (index + 1 - batch_actions.size))
130
+ bulk_responses << bulk_send(body_stream, batch_actions)
131
+ body_stream.truncate(0) && body_stream.seek(0)
132
+ stream_writer = gzip_writer(body_stream) if http_compression
133
+ batch_actions.clear
124
134
  end
125
135
  stream_writer.write(as_json)
136
+ batch_actions << action
126
137
  end
127
138
  stream_writer.close if http_compression
128
- bulk_responses << bulk_send(body_stream) if body_stream.size > 0
139
+ logger.debug("Sending final bulk request for batch.",
140
+ :action_count => batch_actions.size,
141
+ :payload_size => stream_writer.pos,
142
+ :content_length => body_stream.size,
143
+ :batch_offset => (actions.size - batch_actions.size))
144
+ bulk_responses << bulk_send(body_stream, batch_actions) if body_stream.size > 0
129
145
  body_stream.close if !http_compression
130
146
  join_bulk_responses(bulk_responses)
131
147
  end
132
148
 
149
+ def gzip_writer(io)
150
+ fail(ArgumentError, "Cannot create gzip writer on IO with unread bytes") unless io.eof?
151
+ fail(ArgumentError, "Cannot create gzip writer on non-empty IO") unless io.pos == 0
152
+
153
+ Zlib::GzipWriter.new(io, Zlib::DEFAULT_COMPRESSION, Zlib::DEFAULT_STRATEGY)
154
+ end
155
+
133
156
  def join_bulk_responses(bulk_responses)
134
157
  {
135
158
  "errors" => bulk_responses.any? {|r| r["errors"] == true},
@@ -137,25 +160,37 @@ module LogStash; module Outputs; class ElasticSearch;
137
160
  }
138
161
  end
139
162
 
140
- def bulk_send(body_stream)
163
+ def bulk_send(body_stream, batch_actions)
141
164
  params = http_compression ? {:headers => {"Content-Encoding" => "gzip"}} : {}
142
- # Discard the URL
143
165
  response = @pool.post(@bulk_path, params, body_stream.string)
144
- if !body_stream.closed?
145
- body_stream.truncate(0)
146
- body_stream.seek(0)
147
- end
148
166
 
149
167
  @bulk_response_metrics.increment(response.code.to_s)
150
168
 
151
- if response.code != 200
169
+ case response.code
170
+ when 200 # OK
171
+ LogStash::Json.load(response.body)
172
+ when 413 # Payload Too Large
173
+ logger.warn("Bulk request rejected: `413 Payload Too Large`", :action_count => batch_actions.size, :content_length => body_stream.size)
174
+ emulate_batch_error_response(batch_actions, response.code, 'payload_too_large')
175
+ else
152
176
  url = ::LogStash::Util::SafeURI.new(response.final_url)
153
177
  raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(
154
178
  response.code, url, body_stream.to_s, response.body
155
179
  )
156
180
  end
181
+ end
157
182
 
158
- LogStash::Json.load(response.body)
183
+ def emulate_batch_error_response(actions, http_code, reason)
184
+ {
185
+ "errors" => true,
186
+ "items" => actions.map do |action|
187
+ action = action.first if action.is_a?(Array)
188
+ request_action, request_parameters = action.first
189
+ {
190
+ request_action => {"status" => http_code, "error" => { "type" => reason }}
191
+ }
192
+ end
193
+ }
159
194
  end
160
195
 
161
196
  def get(path)
@@ -204,8 +204,16 @@ module LogStash; module PluginMixins; module ElasticSearch
204
204
  return
205
205
  end
206
206
 
207
+ responses = bulk_response["items"]
208
+ if responses.size != actions.size # can not map action -> response reliably
209
+ # an ES bug (on 7.10.2, 7.11.1) where a _bulk request to index X documents would return Y (> X) items
210
+ msg = "Sent #{actions.size} documents but Elasticsearch returned #{responses.size} responses"
211
+ @logger.warn(msg, actions: actions, responses: responses)
212
+ fail("#{msg} (likely a bug with _bulk endpoint)")
213
+ end
214
+
207
215
  actions_to_retry = []
208
- bulk_response["items"].each_with_index do |response,idx|
216
+ responses.each_with_index do |response,idx|
209
217
  action_type, action_props = response.first
210
218
 
211
219
  status = action_props["status"]
@@ -291,7 +299,7 @@ module LogStash; module PluginMixins; module ElasticSearch
291
299
  retry unless @stopping.true?
292
300
  rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
293
301
  @bulk_request_metrics.increment(:failures)
294
- log_hash = {:code => e.response_code, :url => e.url.sanitized.to_s}
302
+ log_hash = {:code => e.response_code, :url => e.url.sanitized.to_s, :content_length => e.request_body.bytesize}
295
303
  log_hash[:body] = e.response_body if @logger.debug? # Generally this is too verbose
296
304
  message = "Encountered a retryable error. Will Retry with exponential backoff "
297
305
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-elasticsearch'
3
- s.version = '10.8.4'
3
+ s.version = '10.8.6'
4
4
 
5
5
  s.licenses = ['apache-2.0']
6
6
  s.summary = "Stores logs in Elasticsearch"
@@ -5,7 +5,7 @@ shared_examples_for 'an ILM enabled Logstash' do
5
5
  context 'with a policy with a maximum number of documents' do
6
6
  let (:policy) { small_max_doc_policy }
7
7
  let (:ilm_policy_name) { "logstash-policy-custom"}
8
- let (:settings) { super.merge("ilm_policy" => ilm_policy_name)}
8
+ let (:settings) { super().merge("ilm_policy" => ilm_policy_name)}
9
9
 
10
10
  it 'should rollover when the policy max docs is reached' do
11
11
  put_policy(@es, ilm_policy_name, policy)
@@ -54,7 +54,7 @@ shared_examples_for 'an ILM enabled Logstash' do
54
54
  context 'with a policy where the maximum number of documents is not reached' do
55
55
  let (:policy) { large_max_doc_policy }
56
56
  let (:ilm_policy_name) { "logstash-policy-custom-policy"}
57
- let (:settings) { super.merge("ilm_policy" => ilm_policy_name)}
57
+ let (:settings) { super().merge("ilm_policy" => ilm_policy_name)}
58
58
 
59
59
  it 'should ingest into a single index when max docs is not reached' do
60
60
  put_policy(@es,ilm_policy_name, policy)
@@ -119,7 +119,7 @@ shared_examples_for 'an ILM disabled Logstash' do
119
119
  context 'with an existing policy that will roll over' do
120
120
  let (:policy) { small_max_doc_policy }
121
121
  let (:ilm_policy_name) { "logstash-policy-3_docs"}
122
- let (:settings) { super.merge("ilm_policy" => ilm_policy_name)}
122
+ let (:settings) { super().merge("ilm_policy" => ilm_policy_name)}
123
123
 
124
124
  it 'should not roll over indices' do
125
125
  subject.register
@@ -155,7 +155,7 @@ shared_examples_for 'an ILM disabled Logstash' do
155
155
 
156
156
  context 'with a custom template name' do
157
157
  let (:template_name) { "logstash_custom_template_name" }
158
- let (:settings) { super.merge('template_name' => template_name)}
158
+ let (:settings) { super().merge('template_name' => template_name)}
159
159
 
160
160
  it 'should not write the ILM settings into the template' do
161
161
  subject.register
@@ -195,7 +195,7 @@ shared_examples_for 'an Elasticsearch instance that does not support index lifec
195
195
  subject { LogStash::Outputs::ElasticSearch.new(settings) }
196
196
 
197
197
  context 'when ilm is enabled in Logstash' do
198
- let (:settings) { super.merge!({ 'ilm_enabled' => true }) }
198
+ let (:settings) { super().merge!({ 'ilm_enabled' => true }) }
199
199
 
200
200
  it 'should raise a configuration error' do
201
201
  expect do
@@ -210,13 +210,13 @@ shared_examples_for 'an Elasticsearch instance that does not support index lifec
210
210
  end
211
211
 
212
212
  context 'when ilm is disabled in Logstash' do
213
- let (:settings) { super.merge!({ 'ilm_enabled' => false }) }
213
+ let (:settings) { super().merge!({ 'ilm_enabled' => false }) }
214
214
 
215
215
  it_behaves_like 'an ILM disabled Logstash'
216
216
  end
217
217
 
218
218
  context 'when ilm is set to auto in Logstash' do
219
- let (:settings) { super.merge!({ 'ilm_enabled' => 'auto' }) }
219
+ let (:settings) { super().merge!({ 'ilm_enabled' => 'auto' }) }
220
220
 
221
221
  it_behaves_like 'an ILM disabled Logstash'
222
222
  end
@@ -286,7 +286,7 @@ if ESHelper.es_version_satisfies?(">= 6.6")
286
286
 
287
287
  context 'when using the default policy' do
288
288
  context 'with a custom pattern' do
289
- let (:settings) { super.merge("ilm_pattern" => "000001")}
289
+ let (:settings) { super().merge("ilm_pattern" => "000001")}
290
290
  it 'should create a rollover alias' do
291
291
  expect(@es.indices.exists_alias(name: "logstash")).to be_falsey
292
292
  subject.register
@@ -346,7 +346,7 @@ if ESHelper.es_version_satisfies?(">= 6.6")
346
346
 
347
347
  context 'when not using the default policy' do
348
348
  let (:ilm_policy_name) {"logstash-policy-small"}
349
- let (:settings) { super.merge("ilm_policy" => ilm_policy_name)}
349
+ let (:settings) { super().merge("ilm_policy" => ilm_policy_name)}
350
350
  let (:policy) { small_max_doc_policy }
351
351
 
352
352
  before do
@@ -363,7 +363,7 @@ if ESHelper.es_version_satisfies?(">= 6.6")
363
363
 
364
364
  context 'when using a time based policy' do
365
365
  let (:ilm_policy_name) {"logstash-policy-time"}
366
- let (:settings) { super.merge("ilm_policy" => ilm_policy_name)}
366
+ let (:settings) { super().merge("ilm_policy" => ilm_policy_name)}
367
367
  let (:policy) { max_age_policy("1d") }
368
368
 
369
369
  before do
@@ -409,7 +409,7 @@ if ESHelper.es_version_satisfies?(">= 6.6")
409
409
  let (:template) { "spec/fixtures/template-with-policy-es6x.json" }
410
410
  end
411
411
 
412
- let (:settings) { super.merge("template" => template,
412
+ let (:settings) { super().merge("template" => template,
413
413
  "index" => "overwrite-4")}
414
414
 
415
415
  it 'should not overwrite the index patterns' do
@@ -426,7 +426,7 @@ if ESHelper.es_version_satisfies?(">= 6.6")
426
426
  let (:ilm_rollover_alias) { "logstash_the_cat_in_the_hat" }
427
427
  let (:index) { ilm_rollover_alias }
428
428
  let(:expected_index) { index }
429
- let (:settings) { super.merge("ilm_policy" => ilm_policy_name,
429
+ let (:settings) { super().merge("ilm_policy" => ilm_policy_name,
430
430
  "template" => template,
431
431
  "ilm_rollover_alias" => ilm_rollover_alias)}
432
432
 
@@ -480,7 +480,7 @@ if ESHelper.es_version_satisfies?(">= 6.6")
480
480
 
481
481
  context 'with a different template_name' do
482
482
  let (:template_name) { "logstash_custom_template_name" }
483
- let (:settings) { super.merge('template_name' => template_name)}
483
+ let (:settings) { super().merge('template_name' => template_name)}
484
484
 
485
485
  it_behaves_like 'an ILM enabled Logstash'
486
486
 
@@ -514,7 +514,7 @@ if ESHelper.es_version_satisfies?(">= 6.6")
514
514
  end
515
515
 
516
516
  context 'when ilm_enabled is the default' do
517
- let (:settings) { super.tap{|x|x.delete('ilm_enabled')}}
517
+ let (:settings) { super().tap{|x|x.delete('ilm_enabled')}}
518
518
 
519
519
  if ESHelper.es_version_satisfies?(">=7.0")
520
520
  context 'when Elasticsearch is version 7 or above' do
@@ -530,13 +530,13 @@ if ESHelper.es_version_satisfies?(">= 6.6")
530
530
  end
531
531
 
532
532
  context 'with ilm disabled' do
533
- let (:settings) { super.merge('ilm_enabled' => false )}
533
+ let (:settings) { super().merge('ilm_enabled' => false )}
534
534
 
535
535
  it_behaves_like 'an ILM disabled Logstash'
536
536
  end
537
537
 
538
538
  context 'with ilm disabled using a string' do
539
- let (:settings) { super.merge('ilm_enabled' => 'false' )}
539
+ let (:settings) { super().merge('ilm_enabled' => 'false' )}
540
540
 
541
541
  it_behaves_like 'an ILM disabled Logstash'
542
542
  end
@@ -40,10 +40,10 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
40
40
 
41
41
  context "when setting bulk_path" do
42
42
  let(:bulk_path) { "/meh" }
43
- let(:options) { super.merge("bulk_path" => bulk_path) }
43
+ let(:options) { super().merge("bulk_path" => bulk_path) }
44
44
 
45
45
  context "when using path" do
46
- let(:options) { super.merge("path" => "/path") }
46
+ let(:options) { super().merge("path" => "/path") }
47
47
  it "ignores the path setting" do
48
48
  expect(described_class).to receive(:create_http_client) do |options|
49
49
  expect(options[:bulk_path]).to eq(bulk_path)
@@ -66,7 +66,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
66
66
 
67
67
  context "when using path" do
68
68
  let(:path) { "/meh" }
69
- let(:options) { super.merge("path" => path) }
69
+ let(:options) { super().merge("path" => path) }
70
70
  it "sets bulk_path to path+_bulk" do
71
71
  expect(described_class).to receive(:create_http_client) do |options|
72
72
  expect(options[:bulk_path]).to eq("#{path}/_bulk")
@@ -88,10 +88,10 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
88
88
  describe "healthcheck_path" do
89
89
  context "when setting healthcheck_path" do
90
90
  let(:healthcheck_path) { "/meh" }
91
- let(:options) { super.merge("healthcheck_path" => healthcheck_path) }
91
+ let(:options) { super().merge("healthcheck_path" => healthcheck_path) }
92
92
 
93
93
  context "when using path" do
94
- let(:options) { super.merge("path" => "/path") }
94
+ let(:options) { super().merge("path" => "/path") }
95
95
  it "ignores the path setting" do
96
96
  expect(described_class).to receive(:create_http_client) do |options|
97
97
  expect(options[:healthcheck_path]).to eq(healthcheck_path)
@@ -114,7 +114,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
114
114
 
115
115
  context "when using path" do
116
116
  let(:path) { "/meh" }
117
- let(:options) { super.merge("path" => path) }
117
+ let(:options) { super().merge("path" => path) }
118
118
  it "sets healthcheck_path to path" do
119
119
  expect(described_class).to receive(:create_http_client) do |options|
120
120
  expect(options[:healthcheck_path]).to eq(path)
@@ -136,10 +136,10 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
136
136
  describe "sniffing_path" do
137
137
  context "when setting sniffing_path" do
138
138
  let(:sniffing_path) { "/meh" }
139
- let(:options) { super.merge("sniffing_path" => sniffing_path) }
139
+ let(:options) { super().merge("sniffing_path" => sniffing_path) }
140
140
 
141
141
  context "when using path" do
142
- let(:options) { super.merge("path" => "/path") }
142
+ let(:options) { super().merge("path" => "/path") }
143
143
  it "ignores the path setting" do
144
144
  expect(described_class).to receive(:create_http_client) do |options|
145
145
  expect(options[:sniffing_path]).to eq(sniffing_path)
@@ -162,7 +162,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
162
162
 
163
163
  context "when using path" do
164
164
  let(:path) { "/meh" }
165
- let(:options) { super.merge("path" => path) }
165
+ let(:options) { super().merge("path" => path) }
166
166
  it "sets sniffing_path to path+_nodes/http" do
167
167
  expect(described_class).to receive(:create_http_client) do |options|
168
168
  expect(options[:sniffing_path]).to eq("#{path}/_nodes/http")
@@ -68,7 +68,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
68
68
 
69
69
  context "and setting healthcheck_path" do
70
70
  let(:healthcheck_path) { "/my/health" }
71
- let(:options) { super.merge(:healthcheck_path => healthcheck_path) }
71
+ let(:options) { super().merge(:healthcheck_path => healthcheck_path) }
72
72
  it "performs the healthcheck to the healthcheck_path" do
73
73
  expect(adapter).to receive(:perform_request) do |url, method, req_path, _, _|
74
74
  expect(method).to eq(:head)
@@ -130,7 +130,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
130
130
  end
131
131
 
132
132
  context "when enabled" do
133
- let(:options) { super.merge(:sniffing => true)}
133
+ let(:options) { super().merge(:sniffing => true)}
134
134
 
135
135
  it "should start the sniffer" do
136
136
  expect(subject.sniffer_alive?).to eql(true)
@@ -247,7 +247,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
247
247
  end
248
248
 
249
249
  let(:options) do
250
- super.merge(:license_checker => license_checker)
250
+ super().merge(:license_checker => license_checker)
251
251
  end
252
252
 
253
253
  context 'when LicenseChecker#acceptable_license? returns false' do
@@ -48,7 +48,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
48
48
  describe "ssl" do
49
49
  context "when SSL is true" do
50
50
  let(:ssl) { true }
51
- let(:base_options) { super.merge(:hosts => [http_hostname_port]) }
51
+ let(:base_options) { super().merge(:hosts => [http_hostname_port]) }
52
52
 
53
53
  it "should refuse to handle an http url" do
54
54
  expect {
@@ -59,7 +59,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
59
59
 
60
60
  context "when SSL is false" do
61
61
  let(:ssl) { false }
62
- let(:base_options) { super.merge(:hosts => [https_hostname_port]) }
62
+ let(:base_options) { super().merge(:hosts => [https_hostname_port]) }
63
63
 
64
64
  it "should refuse to handle an https url" do
65
65
  expect {
@@ -69,7 +69,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
69
69
  end
70
70
 
71
71
  describe "ssl is nil" do
72
- let(:base_options) { super.merge(:hosts => [https_hostname_port]) }
72
+ let(:base_options) { super().merge(:hosts => [https_hostname_port]) }
73
73
  it "should handle an ssl url correctly when SSL is nil" do
74
74
  subject
75
75
  expect(subject.host_to_url(https_hostname_port).to_s).to eq(https_hostname_port.to_s + "/")
@@ -79,14 +79,14 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
79
79
 
80
80
  describe "path" do
81
81
  let(:url) { http_hostname_port_path }
82
- let(:base_options) { super.merge(:hosts => [url]) }
82
+ let(:base_options) { super().merge(:hosts => [url]) }
83
83
 
84
84
  it "should allow paths in a url" do
85
85
  expect(subject.host_to_url(url)).to eq(url)
86
86
  end
87
87
 
88
88
  context "with the path option set" do
89
- let(:base_options) { super.merge(:client_settings => {:path => "/otherpath"}) }
89
+ let(:base_options) { super().merge(:client_settings => {:path => "/otherpath"}) }
90
90
 
91
91
  it "should not allow paths in two places" do
92
92
  expect {
@@ -97,7 +97,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
97
97
 
98
98
  context "with a path missing a leading /" do
99
99
  let(:url) { http_hostname_port }
100
- let(:base_options) { super.merge(:client_settings => {:path => "otherpath"}) }
100
+ let(:base_options) { super().merge(:client_settings => {:path => "otherpath"}) }
101
101
 
102
102
 
103
103
  it "should automatically insert a / in front of path overlays" do
@@ -204,7 +204,7 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
204
204
  end
205
205
 
206
206
  describe "#bulk" do
207
- subject { described_class.new(base_options) }
207
+ subject(:http_client) { described_class.new(base_options) }
208
208
 
209
209
  require "json"
210
210
  let(:message) { "hey" }
@@ -212,42 +212,61 @@ describe LogStash::Outputs::ElasticSearch::HttpClient do
212
212
  ["index", {:_id=>nil, :_index=>"logstash"}, {"message"=> message}],
213
213
  ]}
214
214
 
215
- context "if a message is over TARGET_BULK_BYTES" do
216
- let(:target_bulk_bytes) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES }
217
- let(:message) { "a" * (target_bulk_bytes + 1) }
215
+ [true,false].each do |http_compression_enabled|
216
+ context "with `http_compression => #{http_compression_enabled}`" do
218
217
 
219
- it "should be handled properly" do
220
- allow(subject).to receive(:join_bulk_responses)
221
- expect(subject).to receive(:bulk_send).once do |data|
222
- expect(data.size).to be > target_bulk_bytes
218
+ let(:base_options) { super().merge(:client_settings => {:http_compression => http_compression_enabled}) }
219
+
220
+ before(:each) do
221
+ if http_compression_enabled
222
+ expect(http_client).to receive(:gzip_writer).at_least(:once).and_call_original
223
+ else
224
+ expect(http_client).to_not receive(:gzip_writer)
225
+ end
223
226
  end
224
- s = subject.send(:bulk, actions)
225
- end
226
- end
227
227
 
228
- context "with two messages" do
229
- let(:message1) { "hey" }
230
- let(:message2) { "you" }
231
- let(:actions) { [
232
- ["index", {:_id=>nil, :_index=>"logstash"}, {"message"=> message1}],
233
- ["index", {:_id=>nil, :_index=>"logstash"}, {"message"=> message2}],
234
- ]}
235
- it "executes one bulk_send operation" do
236
- allow(subject).to receive(:join_bulk_responses)
237
- expect(subject).to receive(:bulk_send).once
238
- s = subject.send(:bulk, actions)
239
- end
228
+ context "if a message is over TARGET_BULK_BYTES" do
229
+ let(:target_bulk_bytes) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES }
230
+ let(:message) { "a" * (target_bulk_bytes + 1) }
231
+
232
+ it "should be handled properly" do
233
+ allow(subject).to receive(:join_bulk_responses)
234
+ expect(subject).to receive(:bulk_send).once do |data|
235
+ if !http_compression_enabled
236
+ expect(data.size).to be > target_bulk_bytes
237
+ else
238
+ expect(Zlib::gunzip(data.string).size).to be > target_bulk_bytes
239
+ end
240
+ end
241
+ s = subject.send(:bulk, actions)
242
+ end
243
+ end
244
+
245
+ context "with two messages" do
246
+ let(:message1) { "hey" }
247
+ let(:message2) { "you" }
248
+ let(:actions) { [
249
+ ["index", {:_id=>nil, :_index=>"logstash"}, {"message"=> message1}],
250
+ ["index", {:_id=>nil, :_index=>"logstash"}, {"message"=> message2}],
251
+ ]}
252
+ it "executes one bulk_send operation" do
253
+ allow(subject).to receive(:join_bulk_responses)
254
+ expect(subject).to receive(:bulk_send).once
255
+ s = subject.send(:bulk, actions)
256
+ end
240
257
 
241
- context "if one exceeds TARGET_BULK_BYTES" do
242
- let(:target_bulk_bytes) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES }
243
- let(:message1) { "a" * (target_bulk_bytes + 1) }
244
- it "executes two bulk_send operations" do
245
- allow(subject).to receive(:join_bulk_responses)
246
- expect(subject).to receive(:bulk_send).twice
247
- s = subject.send(:bulk, actions)
258
+ context "if one exceeds TARGET_BULK_BYTES" do
259
+ let(:target_bulk_bytes) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES }
260
+ let(:message1) { "a" * (target_bulk_bytes + 1) }
261
+ it "executes two bulk_send operations" do
262
+ allow(subject).to receive(:join_bulk_responses)
263
+ expect(subject).to receive(:bulk_send).twice
264
+ s = subject.send(:bulk, actions)
265
+ end
266
+ end
248
267
  end
249
- end
250
- end
268
+ end
269
+ end
251
270
  end
252
271
 
253
272
  describe "sniffing" do
@@ -24,7 +24,7 @@ describe "Proxy option" do
24
24
 
25
25
  context "when specified as a URI" do
26
26
  shared_examples("hash conversion") do |hash|
27
- let(:settings) { super.merge("proxy" => proxy)}
27
+ let(:settings) { super().merge("proxy" => proxy)}
28
28
 
29
29
  it "should set the proxy to the correct hash value" do
30
30
  expect(::Manticore::Client).to have_received(:new) do |options|
@@ -71,7 +71,7 @@ describe "Proxy option" do
71
71
  end
72
72
 
73
73
  context "when specified as ''" do
74
- let(:settings) { super.merge("proxy" => "${A_MISSING_ENV_VARIABLE:}")}
74
+ let(:settings) { super().merge("proxy" => "${A_MISSING_ENV_VARIABLE:}")}
75
75
 
76
76
  it "should not send the proxy option to manticore" do
77
77
  expect { subject.register }.not_to raise_error
@@ -85,7 +85,7 @@ describe "Proxy option" do
85
85
  end
86
86
 
87
87
  context "when specified as invalid uri" do
88
- let(:settings) { super.merge("proxy" => ":")}
88
+ let(:settings) { super().merge("proxy" => ":")}
89
89
 
90
90
  it "should fail" do
91
91
  # SafeURI isn't doing the proper exception wrapping for us, we can not simply :
@@ -4,7 +4,7 @@ require "flores/random"
4
4
  require "logstash/outputs/elasticsearch"
5
5
 
6
6
  describe LogStash::Outputs::ElasticSearch do
7
- subject { described_class.new(options) }
7
+ subject(:elasticsearch_output_instance) { described_class.new(options) }
8
8
  let(:options) { {} }
9
9
  let(:maximum_seen_major_version) { [1,2,5,6,7,8].sample }
10
10
 
@@ -46,7 +46,7 @@ describe LogStash::Outputs::ElasticSearch do
46
46
 
47
47
  describe "getting a document type" do
48
48
  context "if document_type isn't set" do
49
- let(:options) { super.merge("document_type" => nil)}
49
+ let(:options) { super().merge("document_type" => nil)}
50
50
  context "for 7.x elasticsearch clusters" do
51
51
  let(:maximum_seen_major_version) { 7 }
52
52
  it "should return '_doc'" do
@@ -70,7 +70,7 @@ describe LogStash::Outputs::ElasticSearch do
70
70
  end
71
71
 
72
72
  context "with 'document type set'" do
73
- let(:options) { super.merge("document_type" => "bar")}
73
+ let(:options) { super().merge("document_type" => "bar")}
74
74
  it "should get the event type from the 'document_type' setting" do
75
75
  expect(subject.send(:get_event_type, LogStash::Event.new())).to eql("bar")
76
76
  end
@@ -87,7 +87,7 @@ describe LogStash::Outputs::ElasticSearch do
87
87
  end
88
88
 
89
89
  context "with 'document type set'" do
90
- let(:options) { super.merge("document_type" => "bar")}
90
+ let(:options) { super().merge("document_type" => "bar")}
91
91
  it "should get the event type from the 'document_type' setting" do
92
92
  action_tuple = subject.send(:event_action_tuple, LogStash::Event.new("type" => "foo"))
93
93
  action_params = action_tuple[1]
@@ -105,7 +105,7 @@ describe LogStash::Outputs::ElasticSearch do
105
105
  end
106
106
 
107
107
  context "with 'document type set'" do
108
- let(:options) { super.merge("document_type" => "bar")}
108
+ let(:options) { super().merge("document_type" => "bar")}
109
109
  it "should not include '_type'" do
110
110
  action_tuple = subject.send(:event_action_tuple, LogStash::Event.new("type" => "foo"))
111
111
  action_params = action_tuple[1]
@@ -127,7 +127,7 @@ describe LogStash::Outputs::ElasticSearch do
127
127
 
128
128
  context "as part of a URL" do
129
129
  let(:options) {
130
- super.merge("hosts" => ["http://#{user}:#{password.value}@localhost:9200"])
130
+ super().merge("hosts" => ["http://#{user}:#{password.value}@localhost:9200"])
131
131
  }
132
132
 
133
133
  include_examples("an authenticated config")
@@ -135,7 +135,7 @@ describe LogStash::Outputs::ElasticSearch do
135
135
 
136
136
  context "as a hash option" do
137
137
  let(:options) {
138
- super.merge!(
138
+ super().merge!(
139
139
  "user" => user,
140
140
  "password" => password
141
141
  )
@@ -175,7 +175,7 @@ describe LogStash::Outputs::ElasticSearch do
175
175
 
176
176
  context "with extra slashes" do
177
177
  let(:path) { "/slashed-path/ "}
178
- let(:options) { super.merge("path" => "/some-path/") }
178
+ let(:options) { super().merge("path" => "/some-path/") }
179
179
 
180
180
  it "should properly set the path on the HTTP client without adding slashes" do
181
181
  expect(manticore_url.path).to eql(options["path"])
@@ -234,13 +234,13 @@ describe LogStash::Outputs::ElasticSearch do
234
234
  end
235
235
 
236
236
  describe "without a port specified" do
237
- let(:options) { super.merge('hosts' => 'localhost') }
237
+ let(:options) { super().merge('hosts' => 'localhost') }
238
238
  it "should properly set the default port (9200) on the HTTP client" do
239
239
  expect(manticore_url.port).to eql(9200)
240
240
  end
241
241
  end
242
242
  describe "with a port other than 9200 specified" do
243
- let(:options) { super.merge('hosts' => 'localhost:9202') }
243
+ let(:options) { super().merge('hosts' => 'localhost:9202') }
244
244
  it "should properly set the specified port on the HTTP client" do
245
245
  expect(manticore_url.port).to eql(9202)
246
246
  end
@@ -265,12 +265,14 @@ describe LogStash::Outputs::ElasticSearch do
265
265
  let(:event) { ::LogStash::Event.new("foo" => "bar") }
266
266
  let(:error) do
267
267
  ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(
268
- 429, double("url").as_null_object, double("request body"), double("response body")
268
+ 429, double("url").as_null_object, request_body, double("response body")
269
269
  )
270
270
  end
271
271
  let(:logger) { double("logger").as_null_object }
272
272
  let(:response) { { :errors => [], :items => [] } }
273
273
 
274
+ let(:request_body) { double(:request_body, :bytesize => 1023) }
275
+
274
276
  before(:each) do
275
277
 
276
278
  i = 0
@@ -296,6 +298,95 @@ describe LogStash::Outputs::ElasticSearch do
296
298
  expect(subject.logger).to have_received(:debug).with(/Encountered a retryable error/i, anything)
297
299
  end
298
300
  end
301
+
302
+ context "unexpected bulk response" do
303
+ let(:options) do
304
+ { "hosts" => "127.0.0.1:9999", "index" => "%{foo}", "manage_template" => false }
305
+ end
306
+
307
+ let(:events) { [ ::LogStash::Event.new("foo" => "bar1"), ::LogStash::Event.new("foo" => "bar2") ] }
308
+
309
+ let(:bulk_response) do
310
+ # shouldn't really happen but we've seen this happen - here ES returns more items than were sent
311
+ { "took"=>1, "ingest_took"=>9, "errors"=>true,
312
+ "items"=>[{"index"=>{"_index"=>"bar1", "_type"=>"_doc", "_id"=>nil, "status"=>500,
313
+ "error"=>{"type" => "illegal_state_exception",
314
+ "reason" => "pipeline with id [test-ingest] could not be loaded, caused by [ElasticsearchParseException[Error updating pipeline with id [test-ingest]]; nested: ElasticsearchException[java.lang.IllegalArgumentException: no enrich index exists for policy with name [test-metadata1]]; nested: IllegalArgumentException[no enrich index exists for policy with name [test-metadata1]];; ElasticsearchException[java.lang.IllegalArgumentException: no enrich index exists for policy with name [test-metadata1]]; nested: IllegalArgumentException[no enrich index exists for policy with name [test-metadata1]];; java.lang.IllegalArgumentException: no enrich index exists for policy with name [test-metadata1]]"
315
+ }
316
+ }
317
+ },
318
+ # NOTE: this is an artificial success (usually everything fails with a 500) but even if some doc where
319
+ # to succeed due the unexpected reponse items we can not clearly identify which actions to retry ...
320
+ {"index"=>{"_index"=>"bar2", "_type"=>"_doc", "_id"=>nil, "status"=>201}},
321
+ {"index"=>{"_index"=>"bar2", "_type"=>"_doc", "_id"=>nil, "status"=>500,
322
+ "error"=>{"type" => "illegal_state_exception",
323
+ "reason" => "pipeline with id [test-ingest] could not be loaded, caused by [ElasticsearchParseException[Error updating pipeline with id [test-ingest]]; nested: ElasticsearchException[java.lang.IllegalArgumentException: no enrich index exists for policy with name [test-metadata1]];"
324
+ }
325
+ }
326
+ }]
327
+ }
328
+ end
329
+
330
+ before(:each) do
331
+ allow(subject.client).to receive(:bulk_send).with(instance_of(StringIO), instance_of(Array)) do |stream, actions|
332
+ expect( stream.string ).to include '"foo":"bar1"'
333
+ expect( stream.string ).to include '"foo":"bar2"'
334
+ end.and_return(bulk_response, {"errors"=>false}) # let's make it go away (second call) to not retry indefinitely
335
+ end
336
+
337
+ it "should retry submit" do
338
+ allow(subject.logger).to receive(:error).with(/Encountered an unexpected error/i, anything)
339
+ allow(subject.client).to receive(:bulk).and_call_original # track count
340
+
341
+ subject.multi_receive(events)
342
+
343
+ expect(subject.client).to have_received(:bulk).twice
344
+ end
345
+
346
+ it "should log specific error message" do
347
+ expect(subject.logger).to receive(:error).with(/Encountered an unexpected error/i,
348
+ hash_including(:error_message => 'Sent 2 documents but Elasticsearch returned 3 responses (likely a bug with _bulk endpoint)'))
349
+
350
+ subject.multi_receive(events)
351
+ end
352
+ end
353
+ end
354
+
355
+ context '413 errors' do
356
+ let(:payload_size) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES + 1024 }
357
+ let(:event) { ::LogStash::Event.new("message" => ("a" * payload_size ) ) }
358
+
359
+ let(:logger_stub) { double("logger").as_null_object }
360
+
361
+ before(:each) do
362
+ allow(elasticsearch_output_instance.client).to receive(:logger).and_return(logger_stub)
363
+
364
+ allow(elasticsearch_output_instance.client).to receive(:bulk).and_call_original
365
+
366
+ max_bytes = payload_size * 3 / 4 # ensure a failure first attempt
367
+ allow(elasticsearch_output_instance.client.pool).to receive(:post) do |path, params, body|
368
+ if body.length > max_bytes
369
+ max_bytes *= 2 # ensure a successful retry
370
+ double("Response", :code => 413, :body => "")
371
+ else
372
+ double("Response", :code => 200, :body => '{"errors":false,"items":[{"index":{"status":200,"result":"created"}}]}')
373
+ end
374
+ end
375
+ end
376
+
377
+ it 'retries the 413 until it goes away' do
378
+ elasticsearch_output_instance.multi_receive([event])
379
+
380
+ expect(elasticsearch_output_instance.client).to have_received(:bulk).twice
381
+ end
382
+
383
+ it 'logs about payload quantity and size' do
384
+ elasticsearch_output_instance.multi_receive([event])
385
+
386
+ expect(logger_stub).to have_received(:warn)
387
+ .with(a_string_matching(/413 Payload Too Large/),
388
+ hash_including(:action_count => 1, :content_length => a_value > 20_000_000))
389
+ end
299
390
  end
300
391
 
301
392
  context "with timeout set" do
@@ -410,7 +501,7 @@ describe LogStash::Outputs::ElasticSearch do
410
501
  let(:options) { { 'retry_on_conflict' => num_retries } }
411
502
 
412
503
  context "with a regular index" do
413
- let(:options) { super.merge("action" => "index") }
504
+ let(:options) { super().merge("action" => "index") }
414
505
 
415
506
  it "should not set the retry_on_conflict parameter when creating an event_action_tuple" do
416
507
  allow(subject.client).to receive(:maximum_seen_major_version).and_return(maximum_seen_major_version)
@@ -420,7 +511,7 @@ describe LogStash::Outputs::ElasticSearch do
420
511
  end
421
512
 
422
513
  context "using a plain update" do
423
- let(:options) { super.merge("action" => "update", "retry_on_conflict" => num_retries, "document_id" => 1) }
514
+ let(:options) { super().merge("action" => "update", "retry_on_conflict" => num_retries, "document_id" => 1) }
424
515
 
425
516
  it "should set the retry_on_conflict parameter when creating an event_action_tuple" do
426
517
  action, params, event_data = subject.event_action_tuple(event)
@@ -429,7 +520,7 @@ describe LogStash::Outputs::ElasticSearch do
429
520
  end
430
521
 
431
522
  context "with a sprintf action that resolves to update" do
432
- let(:options) { super.merge("action" => "%{myactionfield}", "retry_on_conflict" => num_retries, "document_id" => 1) }
523
+ let(:options) { super().merge("action" => "%{myactionfield}", "retry_on_conflict" => num_retries, "document_id" => 1) }
433
524
 
434
525
  it "should set the retry_on_conflict parameter when creating an event_action_tuple" do
435
526
  action, params, event_data = subject.event_action_tuple(event)
@@ -44,7 +44,7 @@ describe "whitelisting error types in expected behavior" do
44
44
  end
45
45
 
46
46
  describe "when failure logging is disabled for docuemnt exists error" do
47
- let(:settings) { super.merge("failure_type_logging_whitelist" => ["document_already_exists_exception"]) }
47
+ let(:settings) { super().merge("failure_type_logging_whitelist" => ["document_already_exists_exception"]) }
48
48
 
49
49
  it "should log a failure on the action" do
50
50
  expect(subject.logger).not_to have_received(:warn).with("Failed action.", anything)
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: 10.8.4
4
+ version: 10.8.6
5
5
  platform: java
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-24 00:00:00.000000000 Z
11
+ date: 2021-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement