logstash-output-newrelic 1.3.0 → 1.5.0

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