logstash-output-newrelic 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,6 +4,7 @@ require "logstash/outputs/newrelic"
4
4
  require "logstash/outputs/newrelic_version/version"
5
5
  require "logstash/codecs/plain"
6
6
  require "logstash/event"
7
+ require "thread"
7
8
  require "webmock/rspec"
8
9
  require "zlib"
9
10
 
@@ -264,7 +265,7 @@ describe LogStash::Outputs::NewRelic do
264
265
  end
265
266
  end
266
267
 
267
- context "error handling" do
268
+ context "error handling and retry logic" do
268
269
  it "continues through errors, future calls should still succeed" do
269
270
  stub_request(:any, base_uri)
270
271
  .to_raise(StandardError.new("from test"))
@@ -280,33 +281,41 @@ describe LogStash::Outputs::NewRelic do
280
281
  .to have_been_made
281
282
  end
282
283
 
283
- it "retry when receive retryable http error code" do
284
- stub_request(:any, base_uri)
285
- .to_return(status: 500)
286
- .to_return(status: 200)
287
-
288
- event1 = LogStash::Event.new({ "message" => "Test message 1" })
289
- @newrelic_output.multi_receive([event1])
290
-
291
- wait_for(a_request(:post, base_uri)
292
- .with { |request| single_gzipped_message(request.body)['message'] == 'Test message 1' })
293
- .to have_been_made.times(2)
284
+ [
285
+ { "returned_status_code" => 200, "expected_to_retry" => false },
286
+ { "returned_status_code" => 202, "expected_to_retry" => false },
287
+ { "returned_status_code" => 400, "expected_to_retry" => false },
288
+ { "returned_status_code" => 404, "expected_to_retry" => false },
289
+ { "returned_status_code" => 408, "expected_to_retry" => true },
290
+ { "returned_status_code" => 429, "expected_to_retry" => true },
291
+ { "returned_status_code" => 500, "expected_to_retry" => true },
292
+ { "returned_status_code" => 502, "expected_to_retry" => true },
293
+ { "returned_status_code" => 503, "expected_to_retry" => true },
294
+ { "returned_status_code" => 504, "expected_to_retry" => true },
295
+ { "returned_status_code" => 599, "expected_to_retry" => true }
296
+ ].each do |test_case|
297
+ returned_status_code = test_case["returned_status_code"]
298
+ expected_to_retry = test_case["expected_to_retry"]
299
+
300
+ it "should #{expected_to_retry ? "" : "not"} retry on status code #{returned_status_code}" do
301
+ stub_request(:any, base_uri)
302
+ .to_return(status: returned_status_code)
303
+ .to_return(status: 200)
304
+
305
+ logstash_event = LogStash::Event.new({ "message" => "Test message" })
306
+ @newrelic_output.multi_receive([logstash_event])
307
+
308
+ expected_retries = expected_to_retry ? 2 : 1
309
+ wait_for(a_request(:post, base_uri)
310
+ .with { |request| single_gzipped_message(request.body)['message'] == 'Test message' })
311
+ .to have_been_made.at_least_times(expected_retries)
312
+ wait_for(a_request(:post, base_uri)
313
+ .with { |request| single_gzipped_message(request.body)['message'] == 'Test message' })
314
+ .to have_been_made.at_most_times(expected_retries)
315
+ end
294
316
  end
295
317
 
296
- it "not retry when receive a non retryable http error code" do
297
- stub_request(:any, base_uri)
298
- .to_return(status: 401)
299
-
300
- event1 = LogStash::Event.new({ "message" => "Test message 1" })
301
- @newrelic_output.multi_receive([event1])
302
- # Due the async behavior we need to wait to be sure that the method was not called more than 1 time
303
- sleep(2)
304
- wait_for(a_request(:post, base_uri)
305
- .with { |request| single_gzipped_message(request.body)['message'] == 'Test message 1' })
306
- .to have_been_made.times(1)
307
- end
308
-
309
- it "not retries when retry is disabled" do
318
+ it "does not retry when max_retries is set to 0" do
310
319
  @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(
311
320
  { "base_uri" => base_uri, "license_key" => api_key, "max_retries" => '0' }
312
321
  )
@@ -323,7 +332,7 @@ describe LogStash::Outputs::NewRelic do
323
332
  .to have_been_made.times(1)
324
333
  end
325
334
 
326
- it "retry when receive a not expected exception" do
335
+ it "retries when receive a not expected exception" do
327
336
  stub_request(:any, base_uri)
328
337
  .to_raise(StandardError.new("from test"))
329
338
  .to_return(status: 200)
@@ -379,4 +388,97 @@ describe LogStash::Outputs::NewRelic do
379
388
  ).to have_been_made
380
389
  end
381
390
  end
391
+
392
+ context "payload splitting" do
393
+
394
+ def stub_requests_and_capture_msg_ids(captured_msg_ids_accumulator)
395
+ mutex = Mutex.new
396
+ stub_request(:any, base_uri).to_return do |request|
397
+ mutex.synchronize do
398
+ captured_msg_ids_accumulator.concat(extract_msg_ids(request.body))
399
+ end
400
+ { status: 200 }
401
+ end
402
+ end
403
+
404
+ # Tests using this method expect log messages to contain a field "msgId" in their logs
405
+ def extract_msg_ids(body)
406
+ JSON.parse(gunzip(body))[0]['logs'].map do |log|
407
+ log['attributes']['msgId']
408
+ end
409
+ end
410
+
411
+ def expect_msg_ids(captured_msg_ids, msgIdsCount)
412
+ wait_for { captured_msg_ids.length }.to eq(msgIdsCount), "Expected a total of #{msgIdsCount} logs, but found #{captured_msg_ids.length}"
413
+ sorted_captured_msg_ids = captured_msg_ids.sort
414
+ for i in 0...msgIdsCount do
415
+ expect(sorted_captured_msg_ids[i]).to eq(i), "msgId #{i} not found"
416
+ end
417
+ end
418
+
419
+ it "splits logs into up to 1MB payloads" do
420
+ captured_msg_ids = []
421
+ stub_requests_and_capture_msg_ids(captured_msg_ids)
422
+
423
+ # This file contains 17997 random log record messages that upon compressed ends up being about 2.68MB
424
+ # Each log record is pretty small (no single log exceeds 1MB alone), so, we expect it to perform 4 requests to the API
425
+ # with
426
+ file_path = 'spec/outputs/input_17997_messages_resulting_in_2680KB_compressed_payload.json'
427
+
428
+ logstash_events = File.readlines(file_path).map do |line|
429
+ LogStash::Event.new(JSON.parse(line))
430
+ end
431
+
432
+ @newrelic_output.multi_receive(logstash_events)
433
+
434
+ # Verify number of requests matches exactly 4. Note that .times() unexpectedly behaves as .at_least_times(), so we
435
+ # are forced to do this double verification to check the exact number of calls.
436
+ wait_for(a_request(:post, base_uri)).to have_been_made.at_least_times(4)
437
+ wait_for(a_request(:post, base_uri)).to have_been_made.at_most_times(4)
438
+
439
+ # Verify all expected msgIds were received
440
+ expect_msg_ids(captured_msg_ids, 17997)
441
+ end
442
+
443
+ it "does not split a log and does not perform any request if it exceeds 1MB once compressed" do
444
+ stub_request(:any, base_uri).to_return(status: 200)
445
+
446
+ # This file contains a SINGLE random log record that upon compressed ends up being about 1.8MB
447
+ # This payload cannot be further split, so we expect no call being made to the Logs API
448
+ file_path = 'spec/outputs/single_input_message_exceeeding_1MB_once_compressed.json'
449
+
450
+ logstash_events = []
451
+ File.foreach(file_path) do |line|
452
+ logstash_events << LogStash::Event.new(JSON.parse(line))
453
+ end
454
+
455
+ @newrelic_output.multi_receive(logstash_events)
456
+
457
+ wait_for(a_request(:post, base_uri)).not_to have_been_made
458
+ end
459
+
460
+ it "does a single request when the payload is below 1MB" do
461
+ captured_msg_ids = []
462
+ stub_requests_and_capture_msg_ids(captured_msg_ids)
463
+
464
+ # This file contains 5000 random log record messages that upon compressed ends up being about 0.74MB
465
+ # Given that this is below the 1MB allowed by the Logs API, a single request will be made
466
+ file_path = 'spec/outputs/input_5000_messages_resulting_in_740KB_compressed_payload.json'
467
+
468
+ logstash_events = []
469
+ File.foreach(file_path) do |line|
470
+ logstash_events << LogStash::Event.new(JSON.parse(line))
471
+ end
472
+
473
+ @newrelic_output.multi_receive(logstash_events)
474
+
475
+ # Verify number of requests matches exactly 1. Note that .times() unexpectedly behaves as .at_least_times(), so we
476
+ # are forced to do this double verification to check the exact number of calls.
477
+ wait_for(a_request(:post, base_uri)).to have_been_made.at_least_times(1)
478
+ wait_for(a_request(:post, base_uri)).to have_been_made.at_most_times(1)
479
+
480
+ # Verify all expected msgIds were received
481
+ expect_msg_ids(captured_msg_ids, 5000)
482
+ end
483
+ end
382
484
  end