logstash-output-newrelic 2.0.0-java

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.
@@ -0,0 +1,599 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/outputs/newrelic"
4
+ require "logstash/outputs/newrelic_version/version"
5
+ require "logstash/codecs/plain"
6
+ require "logstash/event"
7
+ require "thread"
8
+ require "manticore"
9
+ require "webmock/rspec"
10
+ require "zlib"
11
+ require "rspec/wait"
12
+
13
+ # Configure WebMock to work with Manticore
14
+ WebMock.disable_net_connect!(allow_localhost: true)
15
+
16
+ # Monkey-patch Manticore to capture request bodies for testing
17
+ module ManticoreRequestCapture
18
+ @@captured_bodies = []
19
+
20
+ def self.captured_bodies
21
+ @@captured_bodies
22
+ end
23
+
24
+ def self.clear
25
+ @@captured_bodies.clear
26
+ end
27
+
28
+ def self.last_body
29
+ @@captured_bodies.last
30
+ end
31
+ end
32
+
33
+ # Patch Manticore::Client to capture bodies before WebMock intercepts
34
+ if defined?(::Manticore)
35
+ Manticore::Client.class_eval do
36
+ alias_method :original_post, :post
37
+
38
+ def post(url, options = {})
39
+ # Capture the body before the request, preserving binary encoding
40
+ if options[:body]
41
+ body = options[:body].dup
42
+ # Force binary encoding to prevent corruption of gzipped data
43
+ body.force_encoding('BINARY') if body.respond_to?(:force_encoding)
44
+ ManticoreRequestCapture.captured_bodies << body
45
+ end
46
+ original_post(url, options)
47
+ end
48
+ end
49
+ end
50
+
51
+ describe LogStash::Outputs::NewRelic do
52
+ let (:base_uri) { "https://testing-example-collector.com" }
53
+ let (:retry_seconds) { 0 }
54
+ # Don't sleep in tests, to keep tests fast. We have a test for the method that produces the sleep duration between retries.
55
+ let (:max_delay) { 0 }
56
+ let (:retries) { 3 }
57
+ let (:license_key) { 'cool-guy' }
58
+ let (:simple_config) {
59
+ {
60
+ "base_uri" => base_uri,
61
+ "license_key" => license_key
62
+ }
63
+ }
64
+
65
+ before(:each) do
66
+ ManticoreRequestCapture.clear
67
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(simple_config)
68
+ @newrelic_output.register
69
+ end
70
+
71
+ after(:each) do
72
+ if @newrelic_output
73
+ @newrelic_output.shutdown
74
+ end
75
+ end
76
+
77
+ context "license key tests" do
78
+ it "sets license key when given in the header" do
79
+ stub_request(:any, base_uri).to_return(status: 200)
80
+
81
+ event = LogStash::Event.new({:message => "Test message" })
82
+ @newrelic_output.multi_receive([event])
83
+
84
+ wait_for { a_request(:post, base_uri)
85
+ .with(headers: {
86
+ "X-License-Key" => license_key,
87
+ "X-Event-Source" => "logs",
88
+ "Content-Encoding" => "gzip",
89
+ }) }.to have_been_made
90
+ end
91
+ end
92
+
93
+ context "check https connection scheme" do
94
+ it "uses https by default" do
95
+ stub_request(:any, base_uri).to_return(status: 200)
96
+
97
+ event = LogStash::Event.new({:message => "Test message" })
98
+ @newrelic_output.multi_receive([event])
99
+
100
+ wait_for { a_request(:post, base_uri)
101
+ .with(headers: {
102
+ "X-License-Key" => license_key,
103
+ "X-Event-Source" => "logs",
104
+ "Content-Encoding" => "gzip",
105
+ }) }.to have_been_made
106
+
107
+ # Check if the requests were made using HTTPS
108
+ expect(WebMock).to have_requested(:post, base_uri).with { |req| req.uri.scheme == 'https' }
109
+ expect(WebMock).to have_requested(:post, base_uri).with { |req| req.uri.port == 443 }
110
+ end
111
+ end
112
+
113
+ context "check http connection scheme" do
114
+ it "uses http when http config is set" do
115
+ stub_request(:any, "http://localhost:5000/").to_return(status: 200)
116
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new({
117
+ "base_uri" => "http://localhost:5000/",
118
+ "license_key" => license_key
119
+ })
120
+ @newrelic_output.register
121
+ event = LogStash::Event.new({:message => "Test message" })
122
+ @newrelic_output.multi_receive([event])
123
+
124
+ wait_for { a_request(:post, "http://localhost:5000/")
125
+ .with(headers: {
126
+ "X-License-Key" => license_key,
127
+ "X-Event-Source" => "logs",
128
+ "Content-Encoding" => "gzip",
129
+ }) }.to have_been_made
130
+
131
+ # Check if the requests were made using HTTP to this endpoint
132
+ expect(WebMock).to have_requested(:post, "http://localhost:5000/").with { |req| req.uri.scheme == 'http' }
133
+ expect(WebMock).to have_requested(:post, "http://localhost:5000/").with { |req| req.uri.port == 5000 }
134
+ end
135
+ end
136
+ end
137
+
138
+ describe LogStash::Outputs::NewRelic do
139
+ let (:api_key) { "someAccountKey" }
140
+ let (:base_uri) { "https://testing-example-collector.com" }
141
+ let (:retry_seconds) { 0 }
142
+ # Don't sleep in tests, to keep tests fast. We have a test for the method that produces the sleep duration between retries.
143
+ let (:max_delay) { 0 }
144
+ let (:retries) { 3 }
145
+ let (:simple_config) {
146
+ {
147
+ "api_key" => api_key,
148
+ "base_uri" => base_uri,
149
+ }
150
+ }
151
+
152
+ # An arbitrary time to use in these tests, with different representations
153
+ class FixedTime
154
+ MILLISECONDS = 1562888528123
155
+ ISO_8601_STRING_TIME = '2019-07-11T23:42:08.123Z'
156
+ LOGSTASH_TIMESTAMP = LogStash::Timestamp.coerce(ISO_8601_STRING_TIME)
157
+ end
158
+
159
+ def gunzip(bytes)
160
+ return bytes if bytes.nil? || bytes.empty?
161
+
162
+ # For Manticore, the body might come as a Java byte array or InputStream
163
+ # Convert to string if needed
164
+ if bytes.respond_to?(:java_class)
165
+ # Handle Java types (byte array, InputStream, etc)
166
+ bytes = String.from_java_bytes(bytes) if bytes.respond_to?(:to_a)
167
+ end
168
+
169
+ bytes = bytes.to_s unless bytes.is_a?(String)
170
+
171
+ # Ensure binary encoding
172
+ bytes.force_encoding('BINARY')
173
+
174
+ byte0 = bytes.getbyte(0)
175
+ byte1 = bytes.getbyte(1)
176
+
177
+ # Check for gzip magic bytes (0x1f 0x8b)
178
+ if bytes.length >= 2 && byte0 == 0x1f && byte1 == 0x8b
179
+ sio = StringIO.new(bytes)
180
+ gz = Zlib::GzipReader.new(sio)
181
+ result = gz.read
182
+ gz.close
183
+ result
184
+ else
185
+ # Not gzipped, return as-is
186
+ bytes
187
+ end
188
+ end
189
+
190
+ def single_gzipped_message(body)
191
+ decompressed = gunzip(body)
192
+ message = JSON.parse(decompressed)[0]['logs']
193
+ expect(message.length).to equal(1)
194
+ message[0]
195
+ end
196
+
197
+ def multiple_gzipped_messages(body)
198
+ JSON.parse(gunzip(body))
199
+ end
200
+
201
+ def now_in_milliseconds()
202
+ (Time.now.to_f * 1000).to_i # to_f gives seconds with a fractional portion
203
+ end
204
+
205
+ def within_five_seconds_of(time_in_millis, expected_in_millis)
206
+ five_seconds_in_millis = 5 * 1000
207
+ (time_in_millis - expected_in_millis).abs < five_seconds_in_millis
208
+ end
209
+
210
+
211
+ before(:each) do
212
+ ManticoreRequestCapture.clear
213
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(simple_config)
214
+ @newrelic_output.register
215
+ end
216
+
217
+ after(:each) do
218
+ if @newrelic_output
219
+ @newrelic_output.shutdown
220
+ end
221
+ end
222
+
223
+ context "validation of config" do
224
+ it "requires api_key" do
225
+ no_api_key_config = {
226
+ }
227
+ output = LogStash::Plugin.lookup("output", "newrelic").new(no_api_key_config)
228
+ expect { output.register }.to raise_error LogStash::ConfigurationError
229
+ end
230
+ end
231
+
232
+ context "request headers" do
233
+ it "all present" do
234
+ stub_request(:any, base_uri).to_return(status: 200)
235
+
236
+ event = LogStash::Event.new({:message => "Test message" })
237
+ @newrelic_output.multi_receive([event])
238
+
239
+ wait_for { a_request(:post, base_uri)
240
+ .with(headers: {
241
+ "X-Insert-Key" => api_key,
242
+ "X-Event-Source" => "logs",
243
+ "Content-Encoding" => "gzip",
244
+ }) }.to have_been_made
245
+ end
246
+ end
247
+
248
+ context "request body" do
249
+
250
+ it "message contains plugin information" do
251
+ stub_request(:any, base_uri).to_return(status: 200)
252
+
253
+ event = LogStash::Event.new({ :message => "Test message" })
254
+ @newrelic_output.multi_receive([event])
255
+
256
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
257
+ data = multiple_gzipped_messages(ManticoreRequestCapture.last_body)[0]
258
+ expect(data['common']['attributes']['plugin']['type']).to eq('logstash')
259
+ expect(data['common']['attributes']['plugin']['version']).to eq(LogStash::Outputs::NewRelicVersion::VERSION)
260
+ end
261
+
262
+ it "all other fields passed through as is" do
263
+ stub_request(:any, base_uri).to_return(status: 200)
264
+
265
+ event = LogStash::Event.new({ :message => "Test message", :other => "Other value" })
266
+ @newrelic_output.multi_receive([event])
267
+
268
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
269
+ captured_body = ManticoreRequestCapture.last_body
270
+
271
+ message = single_gzipped_message(captured_body)
272
+ expect(message['message']).to eq('Test message')
273
+ expect(message['attributes']['other']).to eq('Other value')
274
+ end
275
+
276
+ it "JSON object 'message' field is not parsed" do
277
+ stub_request(:any, base_uri).to_return(status: 200)
278
+
279
+ message_json = '{ "in-json-1": "1", "in-json-2": "2", "sub-object": {"in-json-3": "3"} }'
280
+ event = LogStash::Event.new({ :message => message_json, :other => "Other value" })
281
+ @newrelic_output.multi_receive([event])
282
+
283
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
284
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
285
+ expect(message['message']).to eq(message_json)
286
+ expect(message['attributes']['other']).to eq('Other value')
287
+ end
288
+
289
+ it "JSON array 'message' field is not parsed, left as is" do
290
+ stub_request(:any, base_uri).to_return(status: 200)
291
+
292
+ message_json_array = '[{ "in-json-1": "1", "in-json-2": "2", "sub-object": {"in-json-3": "3"} }]'
293
+ event = LogStash::Event.new({ :message => message_json_array, :other => "Other value" })
294
+ @newrelic_output.multi_receive([event])
295
+
296
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
297
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
298
+ expect(message['message']).to eq(message_json_array)
299
+ expect(message['attributes']['other']).to eq('Other value')
300
+ end
301
+
302
+ it "JSON string 'message' field is not parsed, left as is" do
303
+ stub_request(:any, base_uri).to_return(status: 200)
304
+
305
+ message_json_string = '"I can be parsed as JSON"'
306
+ event = LogStash::Event.new({ :message => message_json_string, :other => "Other value" })
307
+ @newrelic_output.multi_receive([event])
308
+
309
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
310
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
311
+ expect(message['message']).to eq(message_json_string)
312
+ expect(message['attributes']['other']).to eq('Other value')
313
+ end
314
+
315
+ it "other JSON fields are not parsed" do
316
+ stub_request(:any, base_uri).to_return(status: 200)
317
+
318
+ other_json = '{ "key": "value" }'
319
+ event = LogStash::Event.new({ :message => "Test message", :other => other_json })
320
+ @newrelic_output.multi_receive([event])
321
+
322
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
323
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
324
+ expect(message['message']).to eq('Test message')
325
+ expect(message['attributes']['other']).to eq(other_json)
326
+ end
327
+
328
+ it "handles messages without a 'message' field" do
329
+ stub_request(:any, base_uri).to_return(status: 200)
330
+
331
+ event = LogStash::Event.new({ :other => 'Other value' })
332
+ @newrelic_output.multi_receive([event])
333
+
334
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
335
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
336
+ expect(message['attributes']['other']).to eq('Other value')
337
+ end
338
+
339
+ it "zero events should not cause an HTTP call" do
340
+ stub_request(:any, base_uri).to_return(status: 200)
341
+
342
+ @newrelic_output.multi_receive([])
343
+
344
+ # Shut down the plugin so that it has the chance to send a request
345
+ # (since we're verifying that nothing is sent)
346
+ @newrelic_output.shutdown
347
+
348
+ expect(a_request(:post, base_uri))
349
+ .not_to have_been_made
350
+ end
351
+
352
+ it "multiple events" do
353
+ stub_request(:any, base_uri).to_return(status: 200)
354
+
355
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
356
+ event2 = LogStash::Event.new({ "message" => "Test message 2" })
357
+ @newrelic_output.multi_receive([event1, event2])
358
+
359
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
360
+ messages = multiple_gzipped_messages(ManticoreRequestCapture.last_body)[0]['logs']
361
+ expect(messages.length).to eq(2)
362
+ expect(messages[0]['message']).to eq('Test message 1')
363
+ expect(messages[1]['message']).to eq('Test message 2')
364
+ end
365
+ end
366
+
367
+ context "error handling and retry logic" do
368
+ it "continues through errors, future calls should still succeed" do
369
+ stub_request(:any, base_uri)
370
+ .to_raise(StandardError.new("from test"))
371
+ .to_return(status: 200)
372
+
373
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
374
+ event2 = LogStash::Event.new({ "message" => "Test message 2" })
375
+ @newrelic_output.multi_receive([event1])
376
+ @newrelic_output.multi_receive([event2])
377
+
378
+ wait_for { ManticoreRequestCapture.captured_bodies.length }.to be >= 2
379
+ message = single_gzipped_message(ManticoreRequestCapture.captured_bodies.last)
380
+ expect(message['message']).to eq('Test message 2')
381
+ end
382
+
383
+ [
384
+ { "returned_status_code" => 200, "expected_to_retry" => false },
385
+ { "returned_status_code" => 202, "expected_to_retry" => false },
386
+ { "returned_status_code" => 400, "expected_to_retry" => false },
387
+ { "returned_status_code" => 404, "expected_to_retry" => false },
388
+ { "returned_status_code" => 408, "expected_to_retry" => true },
389
+ { "returned_status_code" => 429, "expected_to_retry" => true },
390
+ { "returned_status_code" => 500, "expected_to_retry" => true },
391
+ { "returned_status_code" => 502, "expected_to_retry" => true },
392
+ { "returned_status_code" => 503, "expected_to_retry" => true },
393
+ { "returned_status_code" => 504, "expected_to_retry" => true },
394
+ { "returned_status_code" => 599, "expected_to_retry" => true }
395
+ ].each do |test_case|
396
+ returned_status_code = test_case["returned_status_code"]
397
+ expected_to_retry = test_case["expected_to_retry"]
398
+
399
+ it "should #{expected_to_retry ? "" : "not"} retry on status code #{returned_status_code}" do
400
+ request_count = 0
401
+ stub_request(:any, base_uri)
402
+ .to_return do |request|
403
+ request_count += 1
404
+ { status: request_count == 1 ? returned_status_code : 200 }
405
+ end
406
+
407
+ logstash_event = LogStash::Event.new({ "message" => "Test message" })
408
+ @newrelic_output.multi_receive([logstash_event])
409
+
410
+ expected_retries = expected_to_retry ? 2 : 1
411
+ wait_for { request_count }.to eq(expected_retries)
412
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
413
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
414
+ expect(message['message']).to eq('Test message')
415
+ end
416
+ end
417
+
418
+ it "does not retry when max_retries is set to 0" do
419
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(
420
+ { "base_uri" => base_uri, "license_key" => api_key, "max_retries" => '0' }
421
+ )
422
+ @newrelic_output.register
423
+ request_count = 0
424
+ stub_request(:any, base_uri).to_return do |request|
425
+ request_count += 1
426
+ { status: 500 }
427
+ end
428
+
429
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
430
+ @newrelic_output.multi_receive([event1])
431
+ # Due the async behavior we need to wait to be sure that the method was not called more than 1 time
432
+ sleep(2)
433
+ expect(request_count).to eq(1)
434
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
435
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
436
+ expect(message['message']).to eq('Test message 1')
437
+ end
438
+
439
+ it "retries when receive a not expected exception" do
440
+ request_count = 0
441
+ stub_request(:any, base_uri).to_return do |request|
442
+ request_count += 1
443
+ raise StandardError.new("from test") if request_count == 1
444
+ { status: 200 }
445
+ end
446
+
447
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
448
+ @newrelic_output.multi_receive([event1])
449
+ wait_for { request_count }.to eq(2)
450
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
451
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
452
+ expect(message['message']).to eq('Test message 1')
453
+ end
454
+
455
+ it "performs the configured amount of retries, no more, no less" do
456
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(
457
+ { "base_uri" => base_uri, "license_key" => api_key, "max_retries" => '3' }
458
+ )
459
+ @newrelic_output.register
460
+ request_count = 0
461
+ stub_request(:any, base_uri).to_return do |request|
462
+ request_count += 1
463
+ { status: request_count <= 3 ? 500 : 200 }
464
+ end
465
+
466
+ event1 = LogStash::Event.new({ "message" => "Test message" })
467
+ @newrelic_output.multi_receive([event1])
468
+
469
+ wait_for { request_count }.to eq(3)
470
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
471
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
472
+ expect(message['message']).to eq('Test message')
473
+ end
474
+ end
475
+
476
+ context "JSON serialization" do
477
+ it "serializes floating point numbers as floating point numbers" do
478
+ stub_request(:any, base_uri).to_return(status: 200)
479
+
480
+ event = LogStash::Event.new({ "floatingpoint" => 0.12345 })
481
+ @newrelic_output.multi_receive([event])
482
+
483
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
484
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
485
+ expect(message['attributes']['floatingpoint']).to eq(0.12345)
486
+ end
487
+
488
+ it "serializes BigDecimals as floating point numbers" do
489
+ stub_request(:any, base_uri).to_return(status: 200)
490
+
491
+ event = LogStash::Event.new({ "bigdecimal" => BigDecimal('0.12345') })
492
+ @newrelic_output.multi_receive([event])
493
+
494
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
495
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
496
+ expect(message['attributes']['bigdecimal']).to eq(0.12345)
497
+ end
498
+
499
+ it "serializes NaN as null" do
500
+ stub_request(:any, base_uri).to_return(status: 200)
501
+
502
+ event = LogStash::Event.new({ "nan" => BigDecimal('NaN') })
503
+ @newrelic_output.multi_receive([event])
504
+
505
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
506
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
507
+ expect(message['attributes']['nan']).to be_nil
508
+ end
509
+ end
510
+
511
+ context "payload splitting" do
512
+
513
+ def collect_msg_ids_from_captured_bodies
514
+ ManticoreRequestCapture.captured_bodies.flat_map do |body|
515
+ extract_msg_ids(body)
516
+ end
517
+ end
518
+
519
+ # Tests using this method expect log messages to contain a field "msgId" in their logs
520
+ def extract_msg_ids(body)
521
+ JSON.parse(gunzip(body))[0]['logs'].map do |log|
522
+ log['attributes']['msgId']
523
+ end
524
+ end
525
+
526
+ def expect_msg_ids(captured_msg_ids, msgIdsCount)
527
+ wait_for { captured_msg_ids.length }.to eq(msgIdsCount), "Expected a total of #{msgIdsCount} logs, but found #{captured_msg_ids.length}"
528
+ sorted_captured_msg_ids = captured_msg_ids.sort
529
+ for i in 0...msgIdsCount do
530
+ expect(sorted_captured_msg_ids[i]).to eq(i), "msgId #{i} not found"
531
+ end
532
+ end
533
+
534
+ it "splits logs into up to 1MB payloads" do
535
+ stub_request(:any, base_uri).to_return(status: 200)
536
+
537
+ # This file contains 17997 random log record messages that upon compressed ends up being about 2.68MB
538
+ # Each log record is pretty small (no single log exceeds 1MB alone), so, we expect it to perform 4 requests to the API
539
+ # with
540
+ file_path = 'spec/outputs/input_17997_messages_resulting_in_2680KB_compressed_payload.json'
541
+
542
+ logstash_events = File.readlines(file_path).map do |line|
543
+ LogStash::Event.new(JSON.parse(line))
544
+ end
545
+
546
+ @newrelic_output.multi_receive(logstash_events)
547
+
548
+ # Verify number of requests matches exactly 4. Note that .times() unexpectedly behaves as .at_least_times(), so we
549
+ # are forced to do this double verification to check the exact number of calls.
550
+ wait_for { a_request(:post, base_uri) }.to have_been_made.at_least_times(4)
551
+ wait_for { a_request(:post, base_uri) }.to have_been_made.at_most_times(4)
552
+
553
+ # Verify all expected msgIds were received
554
+ captured_msg_ids = collect_msg_ids_from_captured_bodies
555
+ expect_msg_ids(captured_msg_ids, 17997)
556
+ end
557
+
558
+ it "does not split a log and does not perform any request if it exceeds 1MB once compressed" do
559
+ stub_request(:any, base_uri).to_return(status: 200)
560
+
561
+ # This file contains a SINGLE random log record that upon compressed ends up being about 1.8MB
562
+ # This payload cannot be further split, so we expect no call being made to the Logs API
563
+ file_path = 'spec/outputs/single_input_message_exceeeding_1MB_once_compressed.json'
564
+
565
+ logstash_events = []
566
+ File.foreach(file_path) do |line|
567
+ logstash_events << LogStash::Event.new(JSON.parse(line))
568
+ end
569
+
570
+ @newrelic_output.multi_receive(logstash_events)
571
+
572
+ wait_for { a_request(:post, base_uri) }.not_to have_been_made
573
+ end
574
+
575
+ it "does a single request when the payload is below 1MB" do
576
+ stub_request(:any, base_uri).to_return(status: 200)
577
+
578
+ # This file contains 5000 random log record messages that upon compressed ends up being about 0.74MB
579
+ # Given that this is below the 1MB allowed by the Logs API, a single request will be made
580
+ file_path = 'spec/outputs/input_5000_messages_resulting_in_740KB_compressed_payload.json'
581
+
582
+ logstash_events = []
583
+ File.foreach(file_path) do |line|
584
+ logstash_events << LogStash::Event.new(JSON.parse(line))
585
+ end
586
+
587
+ @newrelic_output.multi_receive(logstash_events)
588
+
589
+ # Verify number of requests matches exactly 1. Note that .times() unexpectedly behaves as .at_least_times(), so we
590
+ # are forced to do this double verification to check the exact number of calls.
591
+ wait_for { a_request(:post, base_uri) }.to have_been_made.at_least_times(1)
592
+ wait_for { a_request(:post, base_uri) }.to have_been_made.at_most_times(1)
593
+
594
+ # Verify all expected msgIds were received
595
+ captured_msg_ids = collect_msg_ids_from_captured_bodies
596
+ expect_msg_ids(captured_msg_ids, 5000)
597
+ end
598
+ end
599
+ end