logstash-output-newrelic 2.0.0.pre.beta-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,603 @@
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
+ puts "DEBUG: Captured body class: #{captured_body.class}"
272
+ puts "DEBUG: Captured body length: #{captured_body.length rescue 'N/A'}"
273
+ puts "DEBUG: First 20 bytes: #{captured_body.to_s[0..19].bytes.map{|b| "\\x%02X" % b}.join rescue 'N/A'}"
274
+
275
+ message = single_gzipped_message(captured_body)
276
+ expect(message['message']).to eq('Test message')
277
+ expect(message['attributes']['other']).to eq('Other value')
278
+ end
279
+
280
+ it "JSON object 'message' field is not parsed" do
281
+ stub_request(:any, base_uri).to_return(status: 200)
282
+
283
+ message_json = '{ "in-json-1": "1", "in-json-2": "2", "sub-object": {"in-json-3": "3"} }'
284
+ event = LogStash::Event.new({ :message => message_json, :other => "Other value" })
285
+ @newrelic_output.multi_receive([event])
286
+
287
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
288
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
289
+ expect(message['message']).to eq(message_json)
290
+ expect(message['attributes']['other']).to eq('Other value')
291
+ end
292
+
293
+ it "JSON array 'message' field is not parsed, left as is" do
294
+ stub_request(:any, base_uri).to_return(status: 200)
295
+
296
+ message_json_array = '[{ "in-json-1": "1", "in-json-2": "2", "sub-object": {"in-json-3": "3"} }]'
297
+ event = LogStash::Event.new({ :message => message_json_array, :other => "Other value" })
298
+ @newrelic_output.multi_receive([event])
299
+
300
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
301
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
302
+ expect(message['message']).to eq(message_json_array)
303
+ expect(message['attributes']['other']).to eq('Other value')
304
+ end
305
+
306
+ it "JSON string 'message' field is not parsed, left as is" do
307
+ stub_request(:any, base_uri).to_return(status: 200)
308
+
309
+ message_json_string = '"I can be parsed as JSON"'
310
+ event = LogStash::Event.new({ :message => message_json_string, :other => "Other value" })
311
+ @newrelic_output.multi_receive([event])
312
+
313
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
314
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
315
+ expect(message['message']).to eq(message_json_string)
316
+ expect(message['attributes']['other']).to eq('Other value')
317
+ end
318
+
319
+ it "other JSON fields are not parsed" do
320
+ stub_request(:any, base_uri).to_return(status: 200)
321
+
322
+ other_json = '{ "key": "value" }'
323
+ event = LogStash::Event.new({ :message => "Test message", :other => other_json })
324
+ @newrelic_output.multi_receive([event])
325
+
326
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
327
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
328
+ expect(message['message']).to eq('Test message')
329
+ expect(message['attributes']['other']).to eq(other_json)
330
+ end
331
+
332
+ it "handles messages without a 'message' field" do
333
+ stub_request(:any, base_uri).to_return(status: 200)
334
+
335
+ event = LogStash::Event.new({ :other => 'Other value' })
336
+ @newrelic_output.multi_receive([event])
337
+
338
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
339
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
340
+ expect(message['attributes']['other']).to eq('Other value')
341
+ end
342
+
343
+ it "zero events should not cause an HTTP call" do
344
+ stub_request(:any, base_uri).to_return(status: 200)
345
+
346
+ @newrelic_output.multi_receive([])
347
+
348
+ # Shut down the plugin so that it has the chance to send a request
349
+ # (since we're verifying that nothing is sent)
350
+ @newrelic_output.shutdown
351
+
352
+ expect(a_request(:post, base_uri))
353
+ .not_to have_been_made
354
+ end
355
+
356
+ it "multiple events" do
357
+ stub_request(:any, base_uri).to_return(status: 200)
358
+
359
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
360
+ event2 = LogStash::Event.new({ "message" => "Test message 2" })
361
+ @newrelic_output.multi_receive([event1, event2])
362
+
363
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
364
+ messages = multiple_gzipped_messages(ManticoreRequestCapture.last_body)[0]['logs']
365
+ expect(messages.length).to eq(2)
366
+ expect(messages[0]['message']).to eq('Test message 1')
367
+ expect(messages[1]['message']).to eq('Test message 2')
368
+ end
369
+ end
370
+
371
+ context "error handling and retry logic" do
372
+ it "continues through errors, future calls should still succeed" do
373
+ stub_request(:any, base_uri)
374
+ .to_raise(StandardError.new("from test"))
375
+ .to_return(status: 200)
376
+
377
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
378
+ event2 = LogStash::Event.new({ "message" => "Test message 2" })
379
+ @newrelic_output.multi_receive([event1])
380
+ @newrelic_output.multi_receive([event2])
381
+
382
+ wait_for { ManticoreRequestCapture.captured_bodies.length }.to be >= 2
383
+ message = single_gzipped_message(ManticoreRequestCapture.captured_bodies.last)
384
+ expect(message['message']).to eq('Test message 2')
385
+ end
386
+
387
+ [
388
+ { "returned_status_code" => 200, "expected_to_retry" => false },
389
+ { "returned_status_code" => 202, "expected_to_retry" => false },
390
+ { "returned_status_code" => 400, "expected_to_retry" => false },
391
+ { "returned_status_code" => 404, "expected_to_retry" => false },
392
+ { "returned_status_code" => 408, "expected_to_retry" => true },
393
+ { "returned_status_code" => 429, "expected_to_retry" => true },
394
+ { "returned_status_code" => 500, "expected_to_retry" => true },
395
+ { "returned_status_code" => 502, "expected_to_retry" => true },
396
+ { "returned_status_code" => 503, "expected_to_retry" => true },
397
+ { "returned_status_code" => 504, "expected_to_retry" => true },
398
+ { "returned_status_code" => 599, "expected_to_retry" => true }
399
+ ].each do |test_case|
400
+ returned_status_code = test_case["returned_status_code"]
401
+ expected_to_retry = test_case["expected_to_retry"]
402
+
403
+ it "should #{expected_to_retry ? "" : "not"} retry on status code #{returned_status_code}" do
404
+ request_count = 0
405
+ stub_request(:any, base_uri)
406
+ .to_return do |request|
407
+ request_count += 1
408
+ { status: request_count == 1 ? returned_status_code : 200 }
409
+ end
410
+
411
+ logstash_event = LogStash::Event.new({ "message" => "Test message" })
412
+ @newrelic_output.multi_receive([logstash_event])
413
+
414
+ expected_retries = expected_to_retry ? 2 : 1
415
+ wait_for { request_count }.to eq(expected_retries)
416
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
417
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
418
+ expect(message['message']).to eq('Test message')
419
+ end
420
+ end
421
+
422
+ it "does not retry when max_retries is set to 0" do
423
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(
424
+ { "base_uri" => base_uri, "license_key" => api_key, "max_retries" => '0' }
425
+ )
426
+ @newrelic_output.register
427
+ request_count = 0
428
+ stub_request(:any, base_uri).to_return do |request|
429
+ request_count += 1
430
+ { status: 500 }
431
+ end
432
+
433
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
434
+ @newrelic_output.multi_receive([event1])
435
+ # Due the async behavior we need to wait to be sure that the method was not called more than 1 time
436
+ sleep(2)
437
+ expect(request_count).to eq(1)
438
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
439
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
440
+ expect(message['message']).to eq('Test message 1')
441
+ end
442
+
443
+ it "retries when receive a not expected exception" do
444
+ request_count = 0
445
+ stub_request(:any, base_uri).to_return do |request|
446
+ request_count += 1
447
+ raise StandardError.new("from test") if request_count == 1
448
+ { status: 200 }
449
+ end
450
+
451
+ event1 = LogStash::Event.new({ "message" => "Test message 1" })
452
+ @newrelic_output.multi_receive([event1])
453
+ wait_for { request_count }.to eq(2)
454
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
455
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
456
+ expect(message['message']).to eq('Test message 1')
457
+ end
458
+
459
+ it "performs the configured amount of retries, no more, no less" do
460
+ @newrelic_output = LogStash::Plugin.lookup("output", "newrelic").new(
461
+ { "base_uri" => base_uri, "license_key" => api_key, "max_retries" => '3' }
462
+ )
463
+ @newrelic_output.register
464
+ request_count = 0
465
+ stub_request(:any, base_uri).to_return do |request|
466
+ request_count += 1
467
+ { status: request_count <= 3 ? 500 : 200 }
468
+ end
469
+
470
+ event1 = LogStash::Event.new({ "message" => "Test message" })
471
+ @newrelic_output.multi_receive([event1])
472
+
473
+ wait_for { request_count }.to eq(3)
474
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
475
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
476
+ expect(message['message']).to eq('Test message')
477
+ end
478
+ end
479
+
480
+ context "JSON serialization" do
481
+ it "serializes floating point numbers as floating point numbers" do
482
+ stub_request(:any, base_uri).to_return(status: 200)
483
+
484
+ event = LogStash::Event.new({ "floatingpoint" => 0.12345 })
485
+ @newrelic_output.multi_receive([event])
486
+
487
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
488
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
489
+ expect(message['attributes']['floatingpoint']).to eq(0.12345)
490
+ end
491
+
492
+ it "serializes BigDecimals as floating point numbers" do
493
+ stub_request(:any, base_uri).to_return(status: 200)
494
+
495
+ event = LogStash::Event.new({ "bigdecimal" => BigDecimal('0.12345') })
496
+ @newrelic_output.multi_receive([event])
497
+
498
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
499
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
500
+ expect(message['attributes']['bigdecimal']).to eq(0.12345)
501
+ end
502
+
503
+ it "serializes NaN as null" do
504
+ stub_request(:any, base_uri).to_return(status: 200)
505
+
506
+ event = LogStash::Event.new({ "nan" => BigDecimal('NaN') })
507
+ @newrelic_output.multi_receive([event])
508
+
509
+ wait_for { ManticoreRequestCapture.last_body }.not_to be_nil
510
+ message = single_gzipped_message(ManticoreRequestCapture.last_body)
511
+ expect(message['attributes']['nan']).to be_nil
512
+ end
513
+ end
514
+
515
+ context "payload splitting" do
516
+
517
+ def collect_msg_ids_from_captured_bodies
518
+ ManticoreRequestCapture.captured_bodies.flat_map do |body|
519
+ extract_msg_ids(body)
520
+ end
521
+ end
522
+
523
+ # Tests using this method expect log messages to contain a field "msgId" in their logs
524
+ def extract_msg_ids(body)
525
+ JSON.parse(gunzip(body))[0]['logs'].map do |log|
526
+ log['attributes']['msgId']
527
+ end
528
+ end
529
+
530
+ def expect_msg_ids(captured_msg_ids, msgIdsCount)
531
+ wait_for { captured_msg_ids.length }.to eq(msgIdsCount), "Expected a total of #{msgIdsCount} logs, but found #{captured_msg_ids.length}"
532
+ sorted_captured_msg_ids = captured_msg_ids.sort
533
+ for i in 0...msgIdsCount do
534
+ expect(sorted_captured_msg_ids[i]).to eq(i), "msgId #{i} not found"
535
+ end
536
+ end
537
+
538
+ it "splits logs into up to 1MB payloads" do
539
+ stub_request(:any, base_uri).to_return(status: 200)
540
+
541
+ # This file contains 17997 random log record messages that upon compressed ends up being about 2.68MB
542
+ # Each log record is pretty small (no single log exceeds 1MB alone), so, we expect it to perform 4 requests to the API
543
+ # with
544
+ file_path = 'spec/outputs/input_17997_messages_resulting_in_2680KB_compressed_payload.json'
545
+
546
+ logstash_events = File.readlines(file_path).map do |line|
547
+ LogStash::Event.new(JSON.parse(line))
548
+ end
549
+
550
+ @newrelic_output.multi_receive(logstash_events)
551
+
552
+ # Verify number of requests matches exactly 4. Note that .times() unexpectedly behaves as .at_least_times(), so we
553
+ # are forced to do this double verification to check the exact number of calls.
554
+ wait_for(a_request(:post, base_uri)).to have_been_made.at_least_times(4)
555
+ wait_for(a_request(:post, base_uri)).to have_been_made.at_most_times(4)
556
+
557
+ # Verify all expected msgIds were received
558
+ captured_msg_ids = collect_msg_ids_from_captured_bodies
559
+ expect_msg_ids(captured_msg_ids, 17997)
560
+ end
561
+
562
+ it "does not split a log and does not perform any request if it exceeds 1MB once compressed" do
563
+ stub_request(:any, base_uri).to_return(status: 200)
564
+
565
+ # This file contains a SINGLE random log record that upon compressed ends up being about 1.8MB
566
+ # This payload cannot be further split, so we expect no call being made to the Logs API
567
+ file_path = 'spec/outputs/single_input_message_exceeeding_1MB_once_compressed.json'
568
+
569
+ logstash_events = []
570
+ File.foreach(file_path) do |line|
571
+ logstash_events << LogStash::Event.new(JSON.parse(line))
572
+ end
573
+
574
+ @newrelic_output.multi_receive(logstash_events)
575
+
576
+ wait_for(a_request(:post, base_uri)).not_to have_been_made
577
+ end
578
+
579
+ it "does a single request when the payload is below 1MB" do
580
+ stub_request(:any, base_uri).to_return(status: 200)
581
+
582
+ # This file contains 5000 random log record messages that upon compressed ends up being about 0.74MB
583
+ # Given that this is below the 1MB allowed by the Logs API, a single request will be made
584
+ file_path = 'spec/outputs/input_5000_messages_resulting_in_740KB_compressed_payload.json'
585
+
586
+ logstash_events = []
587
+ File.foreach(file_path) do |line|
588
+ logstash_events << LogStash::Event.new(JSON.parse(line))
589
+ end
590
+
591
+ @newrelic_output.multi_receive(logstash_events)
592
+
593
+ # Verify number of requests matches exactly 1. Note that .times() unexpectedly behaves as .at_least_times(), so we
594
+ # are forced to do this double verification to check the exact number of calls.
595
+ wait_for(a_request(:post, base_uri)).to have_been_made.at_least_times(1)
596
+ wait_for(a_request(:post, base_uri)).to have_been_made.at_most_times(1)
597
+
598
+ # Verify all expected msgIds were received
599
+ captured_msg_ids = collect_msg_ids_from_captured_bodies
600
+ expect_msg_ids(captured_msg_ids, 5000)
601
+ end
602
+ end
603
+ end