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.
- checksums.yaml +4 -4
- data/DEVELOPER.md +13 -2
- data/Gemfile +12 -0
- data/lib/logstash/outputs/newrelic.rb +57 -37
- data/lib/logstash/outputs/newrelic_version/version.rb +1 -1
- data/spec/outputs/input_17997_messages_resulting_in_2680KB_compressed_payload.json +17997 -0
- data/spec/outputs/input_5000_messages_resulting_in_740KB_compressed_payload.json +5000 -0
- data/spec/outputs/newrelic_spec.rb +129 -27
- data/spec/outputs/single_input_message_exceeeding_1MB_once_compressed.json +1 -0
- metadata +7 -1
@@ -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
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
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
|
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 "
|
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
|