diff --git a/.buildkite/browser-pipeline.yml b/.buildkite/browser-pipeline.yml index 03597b21c5..da1ca846ff 100644 --- a/.buildkite/browser-pipeline.yml +++ b/.buildkite/browser-pipeline.yml @@ -107,23 +107,6 @@ steps: concurrency: 5 concurrency_group: "bitbar-web" - - label: ":safari: 16 Browser tests" - depends_on: "browser-maze-runner" - timeout_in_minutes: 30 - plugins: - docker-compose#v3.9.0: - pull: browser-maze-runner - run: browser-maze-runner - use-aliases: true - command: - - --farm=bb - - --browser=safari_16 - artifacts#v1.5.0: - upload: - - "./test/browser/maze_output/failed/**/*" - concurrency: 5 - concurrency_group: "bitbar-web" - # # BrowserStack tests # @@ -263,6 +246,23 @@ steps: concurrency: 2 concurrency_group: "browserstack" + - label: ":safari: 16 Browser tests" + depends_on: "browser-maze-runner" + timeout_in_minutes: 30 + plugins: + docker-compose#v3.9.0: + pull: browser-maze-runner + run: browser-maze-runner + use-aliases: true + command: + - --farm=bs + - --browser=safari_16 + artifacts#v1.5.0: + upload: + - "./test/browser/maze_output/failed/**/*" + concurrency: 2 + concurrency_group: "browserstack" + - label: ":iphone: iOS 10.3 Browser tests" depends_on: "browser-maze-runner" timeout_in_minutes: 30 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ed22e7fd3..ef609a31c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## TBD -### Enhancements +### Changed +- After trimming, attempt to send all event and session payloads, even if believed oversize [#1823](https://github.com/bugsnag/bugsnag-js/pull/1823) - (react-native) Update bugsnag-android from v5.28.1 to [v5.28.3](https://github.com/bugsnag/bugsnag-android/blob/master/CHANGELOG.md#5283-2022-11-16) ### Fixed diff --git a/packages/core/lib/json-payload.js b/packages/core/lib/json-payload.js index 63a6e30962..d543aed646 100644 --- a/packages/core/lib/json-payload.js +++ b/packages/core/lib/json-payload.js @@ -15,13 +15,11 @@ Serialized payload was ${payload.length / 10e5}MB (limit = 1MB) metadata was removed` } payload = jsonStringify(event, null, null, { redactedPaths: EVENT_REDACTION_PATHS, redactedKeys }) - if (payload.length > 10e5) throw new Error('payload exceeded 1MB limit') } return payload } -module.exports.session = (event, redactedKeys) => { - const payload = jsonStringify(event, null, null) - if (payload.length > 10e5) throw new Error('payload exceeded 1MB limit') +module.exports.session = (session, redactedKeys) => { + const payload = jsonStringify(session, null, null) return payload } diff --git a/packages/core/lib/test/json-payload.test.ts b/packages/core/lib/test/json-payload.test.ts new file mode 100644 index 0000000000..44cf86c760 --- /dev/null +++ b/packages/core/lib/test/json-payload.test.ts @@ -0,0 +1,102 @@ +import jsonPayload from '../json-payload' + +function makeBigObject () { + var big: Record = {} + var i = 0 + while (JSON.stringify(big).length < 2 * 10e5) { + big['entry' + i] = 'long repetitive string'.repeat(1000) + i++ + } + return big +} + +describe('jsonPayload.event', () => { + it('safe stringifies the payload and redacts values from certain paths of the supplied keys', () => { + expect(jsonPayload.event({ + api_key: 'd145b8e5afb56516423bc4d605e45442', + events: [ + { + errorMessage: 'Failed load tickets', + errorClass: 'CheckoutError', + user: { + name: 'Jim Bug', + email: 'jim@bugsnag.com' + }, + request: { + api_key: '245b39ebd3cd3992e85bffc81c045924' + } + } + ] + }, ['api_key'])).toBe('{"api_key":"d145b8e5afb56516423bc4d605e45442","events":[{"errorMessage":"Failed load tickets","errorClass":"CheckoutError","user":{"name":"Jim Bug","email":"jim@bugsnag.com"},"request":{"api_key":"[REDACTED]"}}]}') + }) + + it('strips the metaData of the first event if the payload is too large', () => { + const payload = { + api_key: 'd145b8e5afb56516423bc4d605e45442', + events: [ + { + errorMessage: 'Failed load tickets', + errorClass: 'CheckoutError', + user: { + name: 'Jim Bug', + email: 'jim@bugsnag.com' + }, + request: { + api_key: '245b39ebd3cd3992e85bffc81c045924' + }, + _metadata: {} + } + ] + } + + payload.events[0]._metadata = { 'big thing': makeBigObject() } + + expect(jsonPayload.event(payload)).toBe('{"api_key":"d145b8e5afb56516423bc4d605e45442","events":[{"errorMessage":"Failed load tickets","errorClass":"CheckoutError","user":{"name":"Jim Bug","email":"jim@bugsnag.com"},"request":{"api_key":"245b39ebd3cd3992e85bffc81c045924"},"_metadata":{"notifier":"WARNING!\\nSerialized payload was 2.003435MB (limit = 1MB)\\nmetadata was removed"}}]}') + }) + + it('does not attempt to strip any other data paths from the payload to reduce the size', () => { + const payload = { + api_key: 'd145b8e5afb56516423bc4d605e45442', + events: [ + { + errorMessage: 'Failed load tickets', + errorClass: 'CheckoutError', + user: { + name: 'Jim Bug', + email: 'jim@bugsnag.com' + }, + _metadata: {} + }, + { + errorMessage: 'Request failed', + errorClass: 'APIError', + _metadata: {} + } + ] + } + payload.events[1]._metadata = { 'big thing': makeBigObject() } + + expect(jsonPayload.event(payload).length).toBeGreaterThan(10e5) + }) +}) + +describe('jsonPayload.session', () => { + it('safe stringifies the payload', () => { + expect(jsonPayload.session({ + api_key: 'd145b8e5afb56516423bc4d605e45442', + events: [ + { + errorMessage: 'Failed load tickets', + errorClass: 'CheckoutError', + user: { + name: 'Jim Bug', + email: 'jim@bugsnag.com' + }, + request: { + api_key: '245b39ebd3cd3992e85bffc81c045924' + } + } + ] + }, ['api_key'])).toBe('{"api_key":"d145b8e5afb56516423bc4d605e45442","events":[{"errorMessage":"Failed load tickets","errorClass":"CheckoutError","user":{"name":"Jim Bug","email":"jim@bugsnag.com"},"request":{"api_key":"245b39ebd3cd3992e85bffc81c045924"}}]}') + }) +}) diff --git a/packages/delivery-electron/delivery.js b/packages/delivery-electron/delivery.js index 8c77d4040f..7216350692 100644 --- a/packages/delivery-electron/delivery.js +++ b/packages/delivery-electron/delivery.js @@ -18,6 +18,11 @@ const delivery = (client, filestore, net, app) => { } else { const err = new Error(`Bad status code from API: ${response.statusCode}`) err.isRetryable = isRetryable(response.statusCode) + // do not retry oversized payloads regardless of status code + if (body.length > 10e5) { + client._logger.warn(`Discarding over-sized event (${(body.length / 10e5).toFixed(2)} MB) after failed delivery`) + err.isRetryable = false + } cb(err) } }) diff --git a/packages/delivery-electron/test/delivery.test-main.ts b/packages/delivery-electron/test/delivery.test-main.ts index 2a8e4d575f..d7a47916d8 100644 --- a/packages/delivery-electron/test/delivery.test-main.ts +++ b/packages/delivery-electron/test/delivery.test-main.ts @@ -207,6 +207,42 @@ describe('delivery: electron', () => { }) }) + it('does not attempt to re-send oversized payloads', done => { + // A 401 is considered retryable but this will be override by the payload size check + const { requests, server } = mockServer(401) + server.listen(err => { + expect(err).toBeUndefined() + + const lotsOfEvents: any[] = [] + while (JSON.stringify(lotsOfEvents).length < 10e5) { + lotsOfEvents.push({ errors: [{ errorClass: 'Error', errorMessage: 'long repetitive string'.repeat(1000) }] }) + } + const payload = { + events: lotsOfEvents + } as unknown as EventDeliveryPayload + + const config = { + apiKey: 'aaaaaaaa', + endpoints: { notify: `http://localhost:${(server.address() as AddressInfo).port}/notify/` }, + redactedKeys: [] + } + + const logger = { error: jest.fn(), info: () => {}, warn: jest.fn() } + delivery(filestore, net, app)(makeClient(config, logger)).sendEvent(payload, (err: any) => { + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('event failed to send…'), + expect.stringContaining('Bad status code from API: 401') + ) + expect(logger.warn).toHaveBeenCalledWith('Discarding over-sized event (1.01 MB) after failed delivery') + expect(enqueueSpy).not.toHaveBeenCalled() + expect(err).toBeTruthy() + expect(requests.length).toBe(1) + server.close() + done() + }) + }) + }) + it('handles errors gracefully for sessions (ECONNREFUSED)', done => { const payload = { events: [{ errors: [{ errorClass: 'Error', errorMessage: 'foo is not a function' }] }] diff --git a/packages/delivery-node/delivery.js b/packages/delivery-node/delivery.js index 79d4faff8f..d7fffec79f 100644 --- a/packages/delivery-node/delivery.js +++ b/packages/delivery-node/delivery.js @@ -3,8 +3,13 @@ const request = require('./request') module.exports = (client) => ({ sendEvent: (event, cb = () => {}) => { + const body = payload.event(event, client._config.redactedKeys) + const _cb = err => { if (err) client._logger.error(`Event failed to send…\n${(err && err.stack) ? err.stack : err}`, err) + if (body.length > 10e5) { + client._logger.warn(`Event oversized (${(body.length / 10e5).toFixed(2)} MB)`) + } cb(err) } @@ -17,7 +22,7 @@ module.exports = (client) => ({ 'Bugsnag-Payload-Version': '4', 'Bugsnag-Sent-At': (new Date()).toISOString() }, - body: payload.event(event, client._config.redactedKeys), + body, agent: client._config.agent }, (err, body) => _cb(err)) } catch (e) { diff --git a/packages/delivery-node/test/delivery.test.ts b/packages/delivery-node/test/delivery.test.ts index fca1339222..96fb62a19d 100644 --- a/packages/delivery-node/test/delivery.test.ts +++ b/packages/delivery-node/test/delivery.test.ts @@ -61,6 +61,38 @@ describe('delivery:node', () => { }) }) + it('logs failures and large payloads', done => { + const { server } = mockServer(400) + server.listen((err: Error) => { + expect(err).toBeUndefined() + + const lotsOfEvents: any[] = [] + while (JSON.stringify(lotsOfEvents).length < 10e5) { + lotsOfEvents.push({ errors: [{ errorClass: 'Error', errorMessage: 'long repetitive string'.repeat(1000) }] }) + } + const payload = { + events: lotsOfEvents + } as unknown as EventDeliveryPayload + + const config = { + apiKey: 'aaaaaaaa', + endpoints: { notify: `http://0.0.0.0:${(server.address() as AddressInfo).port}/notify/` }, + redactedKeys: [] + } + + const logger = { error: jest.fn(), warn: jest.fn() } + + delivery({ _logger: logger, _config: config } as unknown as Client).sendEvent(payload, (err) => { + expect(err).toStrictEqual(new Error('Bad statusCode from API: 400\nOK')) + expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Event failed to send…'), expect.any(Error)) + expect(logger.warn).toHaveBeenCalledWith('Event oversized (1.01 MB)') + + server.close() + done() + }) + }) + }) + it('sends sessions successfully', done => { const { requests, server } = mockServer(202) server.listen((err: Error) => { diff --git a/packages/delivery-x-domain-request/delivery.js b/packages/delivery-x-domain-request/delivery.js index 2e097f77c0..3c25a1427f 100644 --- a/packages/delivery-x-domain-request/delivery.js +++ b/packages/delivery-x-domain-request/delivery.js @@ -3,14 +3,24 @@ const payload = require('@bugsnag/core/lib/json-payload') module.exports = (client, win = window) => ({ sendEvent: (event, cb = () => {}) => { const url = getApiUrl(client._config, 'notify', '4', win) + const body = payload.event(event, client._config.redactedKeys) + const req = new win.XDomainRequest() req.onload = function () { cb(null) } + req.onerror = function () { + const err = new Error('Event failed to send') + client._logger.error('Event failed to send…', err) + if (body.length > 10e5) { + client._logger.warn(`Event oversized (${(body.length / 10e5).toFixed(2)} MB)`) + } + cb(err) + } req.open('POST', url) setTimeout(() => { try { - req.send(payload.event(event, client._config.redactedKeys)) + req.send(body) } catch (e) { client._logger.error(e) cb(e) diff --git a/packages/delivery-x-domain-request/test/delivery.test.ts b/packages/delivery-x-domain-request/test/delivery.test.ts index a5f512aeb0..c23bb8b238 100644 --- a/packages/delivery-x-domain-request/test/delivery.test.ts +++ b/packages/delivery-x-domain-request/test/delivery.test.ts @@ -72,6 +72,42 @@ describe('delivery:XDomainRequest', () => { }) }) + it('logs failures and large payloads', done => { + // mock XDomainRequest class + function XDomainRequest () { + } + XDomainRequest.prototype.open = function (method: string, url: string) { + this.method = method + this.url = url + } + XDomainRequest.prototype.send = function (method: string, url: string) { + this.onerror() + } + const window = { XDomainRequest, location: { protocol: 'https://' } } as unknown as Window + + const lotsOfEvents: any[] = [] + while (JSON.stringify(lotsOfEvents).length < 10e5) { + lotsOfEvents.push({ errors: [{ errorClass: 'Error', errorMessage: 'long repetitive string'.repeat(1000) }] }) + } + const payload = { + events: lotsOfEvents + } as unknown as EventDeliveryPayload + + const config = { + apiKey: 'aaaaaaaa', + endpoints: { notify: '/echo/', sessions: '/sessions/' }, + redactedKeys: [] + } + const logger = { error: jest.fn(), warn: jest.fn() } + delivery({ _logger: logger, _config: config } as unknown as Client, window).sendEvent(payload, (err) => { + const expectedError = new Error('Event failed to send') + expect(err).toStrictEqual(expectedError) + expect(logger.error).toHaveBeenCalledWith('Event failed to send…', expectedError) + expect(logger.warn).toHaveBeenCalledWith('Event oversized (1.01 MB)') + done() + }) + }) + it('sends sessions successfully', done => { const requests: XDomainRequest[] = [] diff --git a/packages/delivery-xml-http-request/delivery.js b/packages/delivery-xml-http-request/delivery.js index 2c74e61549..19480d7d76 100644 --- a/packages/delivery-xml-http-request/delivery.js +++ b/packages/delivery-xml-http-request/delivery.js @@ -5,15 +5,26 @@ module.exports = (client, win = window) => ({ try { const url = client._config.endpoints.notify const req = new win.XMLHttpRequest() + const body = payload.event(event, client._config.redactedKeys) + req.onreadystatechange = function () { - if (req.readyState === win.XMLHttpRequest.DONE) cb(null) + if (req.readyState === win.XMLHttpRequest.DONE) { + const status = req.status + if (status === 0 || status >= 400) { + client._logger.error('Event failed to send…') + if (body.length > 10e5) { + client._logger.warn(`Event oversized (${(body.length / 10e5).toFixed(2)} MB)`) + } + } + cb(null) + } } req.open('POST', url) req.setRequestHeader('Content-Type', 'application/json') req.setRequestHeader('Bugsnag-Api-Key', event.apiKey || client._config.apiKey) req.setRequestHeader('Bugsnag-Payload-Version', '4') req.setRequestHeader('Bugsnag-Sent-At', (new Date()).toISOString()) - req.send(payload.event(event, client._config.redactedKeys)) + req.send(body) } catch (e) { client._logger.error(e) } diff --git a/packages/delivery-xml-http-request/test/delivery.test.ts b/packages/delivery-xml-http-request/test/delivery.test.ts index 17d2fafafa..e5300482df 100644 --- a/packages/delivery-xml-http-request/test/delivery.test.ts +++ b/packages/delivery-xml-http-request/test/delivery.test.ts @@ -8,6 +8,7 @@ interface MockXMLHttpRequest { data: string | null headers: { [key: string]: string } readyState: string | null + status: number } describe('delivery:XMLHttpRequest', () => { @@ -43,7 +44,7 @@ describe('delivery:XMLHttpRequest', () => { endpoints: { notify: '/echo/' }, redactedKeys: [] } - delivery({ logger: {}, _config: config } as unknown as Client, { XMLHttpRequest } as unknown as Window).sendEvent(payload, (err: any) => { + delivery({ _logger: {}, _config: config } as unknown as Client, { XMLHttpRequest } as unknown as Window).sendEvent(payload, (err: any) => { expect(err).toBe(null) expect(requests.length).toBe(1) expect(requests[0].method).toBe('POST') @@ -57,6 +58,56 @@ describe('delivery:XMLHttpRequest', () => { }) }) + it('logs failures and large payloads', done => { + const requests: MockXMLHttpRequest[] = [] + + // mock XMLHttpRequest class + function XMLHttpRequest (this: MockXMLHttpRequest) { + this.method = null + this.url = null + this.data = null + this.headers = {} + this.readyState = null + this.status = 0 + requests.push(this) + } + XMLHttpRequest.DONE = 4 + XMLHttpRequest.prototype.open = function (method: string, url: string) { + this.method = method + this.url = url + } + XMLHttpRequest.prototype.setRequestHeader = function (key: string, val: string) { + this.headers[key] = val + } + XMLHttpRequest.prototype.send = function (data: string) { + this.data = data + this.readyState = XMLHttpRequest.DONE + this.status = 400 + this.onreadystatechange() + } + + const lotsOfEvents: any[] = [] + while (JSON.stringify(lotsOfEvents).length < 10e5) { + lotsOfEvents.push({ errors: [{ errorClass: 'Error', errorMessage: 'long repetitive string'.repeat(1000) }] }) + } + const payload = { + events: lotsOfEvents + } as unknown as EventDeliveryPayload + const config = { + apiKey: 'aaaaaaaa', + endpoints: { notify: '/echo/' }, + redactedKeys: [] + } + const logger = { error: jest.fn(), warn: jest.fn() } + + delivery({ _logger: logger, _config: config } as unknown as Client, { XMLHttpRequest } as unknown as Window).sendEvent(payload, (err: any) => { + expect(err).toBe(null) + expect(logger.error).toHaveBeenCalledWith('Event failed to send…') + expect(logger.warn).toHaveBeenCalledWith('Event oversized (1.01 MB)') + done() + }) + }) + it('sends sessions successfully', done => { const requests: MockXMLHttpRequest[] = [] @@ -89,7 +140,7 @@ describe('delivery:XMLHttpRequest', () => { endpoints: { notify: '/', sessions: '/echo/' }, redactedKeys: [] } - delivery({ _config: config, logger: {} } as unknown as Client, { XMLHttpRequest } as unknown as Window).sendSession(payload, (err) => { + delivery({ _config: config, _logger: {} } as unknown as Client, { XMLHttpRequest } as unknown as Window).sendSession(payload, (err) => { expect(err).toBe(null) expect(requests.length).toBe(1) expect(requests[0].method).toBe('POST') diff --git a/test/browser/features/delivery.feature b/test/browser/features/delivery.feature new file mode 100644 index 0000000000..a452d2fafa --- /dev/null +++ b/test/browser/features/delivery.feature @@ -0,0 +1,13 @@ +Feature: Delivery of errors + + @skip_ie_10 + Scenario: Delivery is attempted oversized payloads + When I set the HTTP status code for the next "POST" request to 400 + And I navigate to the test URL "/delivery/script/a.html" + And I wait for 5 seconds + Then I wait to receive an error + + # Check that Bugsnag is discarding the event + And I wait to receive 2 logs + Then I discard the oldest log + And the log payload field "message" equals "Event oversized (2.00 MB)" diff --git a/test/browser/features/fixtures/delivery/script/a.html b/test/browser/features/fixtures/delivery/script/a.html new file mode 100644 index 0000000000..56437ddf0f --- /dev/null +++ b/test/browser/features/fixtures/delivery/script/a.html @@ -0,0 +1,83 @@ + + + + + + + + + + + diff --git a/test/browser/features/fixtures/delivery/script/package.json b/test/browser/features/fixtures/delivery/script/package.json new file mode 100644 index 0000000000..dfd1a94a76 --- /dev/null +++ b/test/browser/features/fixtures/delivery/script/package.json @@ -0,0 +1,8 @@ +{ + "name": "bugsnag-js-fixtures-delivery", + "private": true, + "scripts": { + "build": "echo 'Done'" + }, + "dependencies": {} +} diff --git a/test/browser/features/support/env.rb b/test/browser/features/support/env.rb index 19c064ee3a..2c184324cd 100644 --- a/test/browser/features/support/env.rb +++ b/test/browser/features/support/env.rb @@ -4,7 +4,8 @@ def get_test_url(path) host = ENV['HOST'] notify = "http://#{ENV['API_HOST']}:#{Maze.config.port}/notify" sessions = "http://#{ENV['API_HOST']}:#{Maze.config.port}/sessions" - config_query_string = "NOTIFY=#{notify}&SESSIONS=#{sessions}&API_KEY=#{$api_key}" + logs = "http://#{ENV['API_HOST']}:#{Maze.config.port}/logs" + config_query_string = "NOTIFY=#{notify}&SESSIONS=#{sessions}&API_KEY=#{$api_key}&LOGS=#{logs}" uri = URI("http://#{host}:#{FIXTURES_SERVER_PORT}#{path}") diff --git a/test/node/features/delivery.feature b/test/node/features/delivery.feature new file mode 100644 index 0000000000..7cdbea1f9f --- /dev/null +++ b/test/node/features/delivery.feature @@ -0,0 +1,28 @@ +Feature: Delivery of errors + +Background: + Given I store the api key in the environment variable "BUGSNAG_API_KEY" + And I store the notify endpoint in the environment variable "BUGSNAG_NOTIFY_ENDPOINT" + And I store the sessions endpoint in the environment variable "BUGSNAG_SESSIONS_ENDPOINT" + And I store the logs endpoint in the environment variable "BUGSNAG_LOGS_ENDPOINT" + +Scenario: Delivery for an oversized error is not retried + Given I start the service "express" + And I wait for the host "express" to open port "80" + And I set the HTTP status code for the next "POST" request to 400 + When I open the URL "http://express/oversized" + And I wait for 5 seconds + Then I wait to receive an error + + # Check that Bugsnag is discarding the event based on the log output + And I wait to receive 3 logs + Then I discard the oldest log + Then I discard the oldest log + And the log payload field "message" equals "Event oversized (2.01 MB)" + + # Check that resend is not attempted next load (e.g. for when persistence/retry is supported in node) + Then I stop all docker services + And I discard the oldest error + And I start the service "express" + And I wait for the host "express" to open port "80" + Then I should receive no errors \ No newline at end of file diff --git a/test/node/features/express.feature b/test/node/features/express.feature index d8587dac20..c0f6a9aa55 100644 --- a/test/node/features/express.feature +++ b/test/node/features/express.feature @@ -4,6 +4,7 @@ Background: Given I store the api key in the environment variable "BUGSNAG_API_KEY" And I store the notify endpoint in the environment variable "BUGSNAG_NOTIFY_ENDPOINT" And I store the sessions endpoint in the environment variable "BUGSNAG_SESSIONS_ENDPOINT" + And I store the logs endpoint in the environment variable "BUGSNAG_LOGS_ENDPOINT" And I start the service "express" And I wait for the host "express" to open port "80" diff --git a/test/node/features/feature_flags.feature b/test/node/features/feature_flags.feature index 418281ad96..d865ac3199 100644 --- a/test/node/features/feature_flags.feature +++ b/test/node/features/feature_flags.feature @@ -4,6 +4,7 @@ Background: Given I store the api key in the environment variable "BUGSNAG_API_KEY" And I store the notify endpoint in the environment variable "BUGSNAG_NOTIFY_ENDPOINT" And I store the sessions endpoint in the environment variable "BUGSNAG_SESSIONS_ENDPOINT" + And I store the logs endpoint in the environment variable "BUGSNAG_LOGS_ENDPOINT" Scenario: adding feature flags for an unhandled error Given I start the service "express" diff --git a/test/node/features/fixtures/docker-compose.yml b/test/node/features/fixtures/docker-compose.yml index c20b3be735..7cf0ceb10e 100644 --- a/test/node/features/fixtures/docker-compose.yml +++ b/test/node/features/fixtures/docker-compose.yml @@ -99,6 +99,7 @@ services: environment: - BUGSNAG_API_KEY - BUGSNAG_NOTIFY_ENDPOINT + - BUGSNAG_LOGS_ENDPOINT - BUGSNAG_SESSIONS_ENDPOINT networks: default: diff --git a/test/node/features/fixtures/express/scenarios/app.js b/test/node/features/fixtures/express/scenarios/app.js index e733b34e29..c35f6bbcea 100644 --- a/test/node/features/fixtures/express/scenarios/app.js +++ b/test/node/features/fixtures/express/scenarios/app.js @@ -3,6 +3,39 @@ var bugsnagExpress = require('@bugsnag/plugin-express') var express = require('express') var bodyParser = require('body-parser') +const nodeVersion = process.version.match(/^v(\d+\.\d+)/)[1] + +const http = parseFloat(nodeVersion) > 14 ? require('node:http') : require('http') + +const logUrl = parseFloat(nodeVersion) > 12 + ? new URL(process.env.BUGSNAG_LOGS_ENDPOINT) + : require('url').parse(process.env.BUGSNAG_LOGS_ENDPOINT) + +function sendLog(level, message) { + const postData = JSON.stringify({ level, message }) + + const options = { + hostname: logUrl.hostname, + path: logUrl.pathname, + port: logUrl.port, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + } + + const req = http.request(options) + req.write(postData) + req.end() +} + +const remoteLogger = { + debug: (message) => sendLog('debug', message), + info: (message) => sendLog('info', message), + warn: (message) => sendLog('warn', message), + error: (message) => sendLog('error', message) +} + Bugsnag.start({ apiKey: process.env.BUGSNAG_API_KEY, endpoints: { @@ -15,7 +48,8 @@ Bugsnag.start({ { name: 'from config 3', variant: 'SHOULD BE REMOVED' } ], autoTrackSessions: false, - plugins: [bugsnagExpress] + plugins: [bugsnagExpress], + logger: remoteLogger, }) var middleware = Bugsnag.getPlugin('express') @@ -70,6 +104,26 @@ app.get('/throw-non-error', function (req, res, next) { throw 1 // eslint-disable-line }) +app.get('/oversized', function (req, res, next) { + function repeat(s, n){ + var a = []; + while(a.length < n){ + a.push(s); + } + return a.join(''); + } + + var big = {}; + var i = 0; + while (JSON.stringify(big).length < 2*10e5) { + big['entry'+i] = repeat('long repetitive string', 1000); + i++; + } + req.bugsnag.leaveBreadcrumb('big thing', big); + req.bugsnag.notify(new Error('oversized')); + res.end('OK') +}) + app.get('/handled', function (req, res, next) { req.bugsnag.notify(new Error('handled')) res.end('OK') diff --git a/test/node/features/steps/server_fixture_request_steps.rb b/test/node/features/steps/server_fixture_request_steps.rb index a1da9e1d9e..c0a94df156 100644 --- a/test/node/features/steps/server_fixture_request_steps.rb +++ b/test/node/features/steps/server_fixture_request_steps.rb @@ -33,3 +33,7 @@ TEXT ) end + +When('I stop all docker services') do + Maze::Docker.down_all_services +end \ No newline at end of file