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