Skip to content

Commit

Permalink
enable breadcrumbs and context-scoped calls for node
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Skinner committed Feb 17, 2023
1 parent a72509f commit 000c454
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 73 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

- (plugin-navigation-breadcrumbs) calling `pushState` or `replaceState` no longer triggers a new session when `autoTrackSessions` is enabled [#1820](https://github.com/bugsnag/bugsnag-js/pull/1820)
- (plugin-contextualize) reimplement without relying on the deprecated node Domain API [#1924](https://github.com/bugsnag/bugsnag-js/pull/1924)
- (node) enable breadcrumbs and context-scoped calls [#1927](https://github.com/bugsnag/bugsnag-js/pull/1927)

## TBD

Expand Down
21 changes: 8 additions & 13 deletions packages/node/src/notifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ const delivery = require('@bugsnag/delivery-node')
// extend the base config schema with some node-specific options
const schema = { ...require('@bugsnag/core/config').schema, ...require('./config') }

// remove enabledBreadcrumbTypes from the config schema
delete schema.enabledBreadcrumbTypes

const pluginApp = require('@bugsnag/plugin-app-duration')
const pluginSurroundingCode = require('@bugsnag/plugin-node-surrounding-code')
const pluginInProject = require('@bugsnag/plugin-node-in-project')
Expand Down Expand Up @@ -65,12 +62,6 @@ const Bugsnag = {

bugsnag._logger.debug('Loaded!')

bugsnag.leaveBreadcrumb = function () {
bugsnag._logger.warn('Breadcrumbs are not supported in Node.js yet')
}

bugsnag._config.enabledBreadcrumbTypes = []

return bugsnag
},
start: (opts) => {
Expand All @@ -89,10 +80,14 @@ const Bugsnag = {
Object.keys(Client.prototype).forEach((m) => {
if (/^_/.test(m)) return
Bugsnag[m] = function () {
if (!Bugsnag._client) return console.error(`Bugsnag.${m}() was called before Bugsnag.start()`)
Bugsnag._client._depth += 1
const ret = Bugsnag._client[m].apply(Bugsnag._client, arguments)
Bugsnag._client._depth -= 1
// if we are in an async context, use the client from that context
const client = clientContext.getStore() || Bugsnag._client

if (!client) return console.error(`Bugsnag.${m}() was called before Bugsnag.start()`)

client._depth += 1
const ret = client[m].apply(client, arguments)
client._depth -= 1
return ret
}
})
Expand Down
66 changes: 66 additions & 0 deletions test/node/features/contextualize.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
Feature: contextualize plugin

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"

Scenario: using contextualize to add context to an error
And I run the service "contextualize" with the command "node scenarios/contextualize"
And I wait to receive 2 errors

Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event "unhandled" is false
And the event "severity" equals "warning"
And the event "severityReason.type" equals "handledException"
And the exception "errorClass" equals "Error"
And the exception "message" equals "manual notify"
And the exception "type" equals "nodejs"
And the "file" of stack frame 0 equals "scenarios/contextualize.js"
And the "lineNumber" of stack frame 0 equals 15
And the event "metaData.subsystem.name" equals "manual notify"
And the event has a "manual" breadcrumb named "manual notify"

And I discard the oldest error

Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledException"
And the exception "errorClass" equals "Error"
And the exception "message" equals "ENOENT: no such file or directory, open 'does not exist'"
And the exception "type" equals "nodejs"
And the "file" of stack frame 0 equals "scenarios/contextualize.js"
And the "lineNumber" of stack frame 0 equals 20
And the event "metaData.subsystem.name" equals "fs reader"
And the event has a "manual" breadcrumb named "opening file"
And the event does not have a "manual" breadcrumb with message "manual notify"

@skip_before_node_16
Scenario: using contextualize with an unhandled rejection (with context added)
And I run the service "contextualize" with the command "node scenarios/contextualize-unhandled-rejection"
And I wait to receive an error
Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledPromiseRejection"
And the exception "errorClass" equals "Error"
And the exception "message" equals "unhandled rejection"
And the exception "type" equals "nodejs"
And the "file" of stack frame 0 equals "scenarios/contextualize-unhandled-rejection.js"
And the "lineNumber" of stack frame 0 equals 12
And the event "metaData.subsystem.name" equals "fs reader"
And the event "metaData.subsystem.widgetsAdded" equals "cat,dog,mouse"

Scenario: using contextualize with an unhandled rejection (no context added)
And I run the service "contextualize" with the command "node scenarios/contextualize-unhandled-rejection"
And I wait to receive an error
Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledPromiseRejection"
And the exception "errorClass" equals "Error"
And the exception "message" equals "unhandled rejection"
And the exception "type" equals "nodejs"
And the "file" of stack frame 0 equals "scenarios/contextualize-unhandled-rejection.js"
And the "lineNumber" of stack frame 0 equals 12
19 changes: 19 additions & 0 deletions test/node/features/express.feature
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,22 @@ Scenario: adding body to request metadata
And the "file" of stack frame 0 equals "scenarios/app.js"
And the event "request.body.data" equals "in_request_body"
And the event "request.httpMethod" equals "POST"

Scenario: Breadcrumbs from one request do not appear in another
When I open the URL "http://express/breadcrumbs_a"
And I wait to receive an error
Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event has a "manual" breadcrumb named "For the first URL"
And the event "request.url" equals "http://express/breadcrumbs_a"
And the event "request.httpMethod" equals "GET"
And the event "request.clientIp" is not null
And I discard the oldest error

And I open the URL "http://express/breadcrumbs_b"
And I wait to receive an error
Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event has a "manual" breadcrumb named "For the second URL"
And the event does not have a "manual" breadcrumb with message "For the first URL"
And the event "request.url" equals "http://express/breadcrumbs_b"
And the event "request.httpMethod" equals "GET"
And the event "request.clientIp" is not null
11 changes: 11 additions & 0 deletions test/node/features/fixtures/contextualize/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ARG NODE_VERSION=8
FROM node:$NODE_VERSION-alpine

WORKDIR /app

COPY package* ./
RUN npm install

COPY . ./

RUN npm install --no-package-lock --no-save bugsnag-node*.tgz
5 changes: 5 additions & 0 deletions test/node/features/fixtures/contextualize/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/node/features/fixtures/contextualize/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "bugsnag-test"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
var fs = require('fs')
var Bugsnag = require('@bugsnag/node')
Bugsnag.start({
apiKey: process.env.BUGSNAG_API_KEY,
endpoints: {
notify: process.env.BUGSNAG_NOTIFY_ENDPOINT,
sessions: process.env.BUGSNAG_SESSIONS_ENDPOINT
}
})

var contextualize = Bugsnag.getPlugin('contextualize')

contextualize(function () {
Bugsnag.leaveBreadcrumb('manual notify', { message: 'manual notify' })
Bugsnag.notify(new Error('manual notify'))
}, function (event) {
event.addMetadata('subsystem', { name: 'manual notify' })
})

contextualize(function () {
Bugsnag.leaveBreadcrumb('opening file', { message: 'opening file' })
setTimeout(function () {
fs.createReadStream('does not exist')
}, 100)
}, function (event) {
event.addMetadata('subsystem', { name: 'fs reader' })
})
15 changes: 15 additions & 0 deletions test/node/features/fixtures/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ services:
- express
restart: "no"

contextualize:
build:
context: contextualize
args:
- NODE_VERSION
environment:
- BUGSNAG_API_KEY
- BUGSNAG_NOTIFY_ENDPOINT
- BUGSNAG_SESSIONS_ENDPOINT
networks:
default:
aliases:
- contextualize
restart: "no"

express-disabled:
build:
context: express
Expand Down
10 changes: 10 additions & 0 deletions test/node/features/fixtures/express/scenarios/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,16 @@ app.post('/features/handled', bodyParser.urlencoded(), function (req, res, next)
res.end('OK')
})

app.get('/breadcrumbs_a', function (req, res) {
Bugsnag.leaveBreadcrumb('For the first URL', { message: 'For the first URL' })
throw new Error('Error in /breadcrumbs_a')
})

app.get('/breadcrumbs_b', function (req, res) {
Bugsnag.leaveBreadcrumb('For the second URL', { message: 'For the second URL' })
throw new Error('Error in /breadcrumbs_b')
})

app.use(middleware.errorHandler)

app.listen(80)
16 changes: 0 additions & 16 deletions test/node/features/fixtures/unhandled/scenarios/contextualize.js

This file was deleted.

44 changes: 0 additions & 44 deletions test/node/features/unhandled_errors.feature
Original file line number Diff line number Diff line change
Expand Up @@ -55,50 +55,6 @@ Scenario: not reporting unhandledRejections when autoDetectErrors is off
And I wait for 1 second
Then I should receive no requests

Scenario: using contextualize to add context to an error
And I run the service "unhandled" with the command "node scenarios/contextualize"
And I wait to receive an error
Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledException"
And the exception "errorClass" equals "Error"
And the exception "message" equals "ENOENT: no such file or directory, open 'does not exist'"
And the exception "type" equals "nodejs"
And the "file" of stack frame 0 equals "scenarios/contextualize.js"
And the "lineNumber" of stack frame 0 equals 12
And the event "metaData.subsystem.name" equals "fs reader"
And the event "metaData.subsystem.widgetsAdded" equals "cat,dog,mouse"

@skip_before_node_16
Scenario: using contextualize with an unhandled rejection (with context added)
And I run the service "unhandled" with the command "node scenarios/contextualize-unhandled-rejection"
And I wait to receive an error
Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledPromiseRejection"
And the exception "errorClass" equals "Error"
And the exception "message" equals "unhandled rejection"
And the exception "type" equals "nodejs"
And the "file" of stack frame 0 equals "scenarios/contextualize-unhandled-rejection.js"
And the "lineNumber" of stack frame 0 equals 12
And the event "metaData.subsystem.name" equals "fs reader"
And the event "metaData.subsystem.widgetsAdded" equals "cat,dog,mouse"

Scenario: using contextualize with an unhandled rejection (no context added)
And I run the service "unhandled" with the command "node scenarios/contextualize-unhandled-rejection"
And I wait to receive an error
Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledPromiseRejection"
And the exception "errorClass" equals "Error"
And the exception "message" equals "unhandled rejection"
And the exception "type" equals "nodejs"
And the "file" of stack frame 0 equals "scenarios/contextualize-unhandled-rejection.js"
And the "lineNumber" of stack frame 0 equals 12

Scenario: overridden handled state in a callback
And I run the service "unhandled" with the command "node scenarios/modify-unhandled-callback"
And I wait to receive an error
Expand Down

0 comments on commit 000c454

Please sign in to comment.