Skip to content

Commit

Permalink
use domain to catch errors in koa
Browse files Browse the repository at this point in the history
  • Loading branch information
djskinner committed Nov 17, 2022
1 parent 7c6f8fe commit 2c1bbb9
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 16 deletions.
18 changes: 18 additions & 0 deletions packages/plugin-koa/src/koa.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint node/no-deprecated-api: [error, {ignoreModuleItems: ["domain"]}] */
const domain = require('domain')
const clone = require('@bugsnag/core/lib/clone-client')
const extractRequestInfo = require('./request-info')

Expand All @@ -14,6 +16,8 @@ module.exports = {
name: 'koa',
load: client => {
const requestHandler = async (ctx, next) => {
const dom = domain.create()

// Get a client to be scoped to this request. If sessions are enabled, use the
// resumeSession() call to get a session client, otherwise, clone the existing client.
const requestClient = client._config.autoTrackSessions ? client.resumeSession() : clone(client)
Expand All @@ -27,7 +31,21 @@ module.exports = {
event.addMetadata('request', metadata)
}, true)

// unhandled errors caused by this request
dom.on('error', (err) => {
const event = client.Event.create(err, false, handledState, 'koa middleware', 1)
ctx.bugsnag._notify(event, () => {}, (e, event) => {
if (e) client._logger.error('Failed to send event to Bugsnag')
ctx.bugsnag._config.onUncaughtException(err, event, client._logger)
})
if (!ctx.response.headersSent) {
ctx.response.status = 500
}
})

dom.enter()
await next()
dom.exit()
}

requestHandler.v1 = function * (next) {
Expand Down
5 changes: 0 additions & 5 deletions test/node/features/fixtures/koa/scenarios/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ app.use(async (ctx, next) => {
throw new Error('error in async callback')
}, 100)
ctx.body = 'ok'
} else if (ctx.path === '/reject-async-callback') {
setTimeout(function () {
Promise.reject(new Error('reject in async callback')).catch(next)
}, 100)
ctx.body = 'ok'
} else if (ctx.path === '/unhandled-reject-async-callback') {
setTimeout(function () {
Promise.reject(new Error('unhandled reject in async callback'))
Expand Down
13 changes: 2 additions & 11 deletions test/node/features/koa.feature
Original file line number Diff line number Diff line change
Expand Up @@ -127,26 +127,17 @@ Scenario: a thrown error in an async callback
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 event "severityReason.type" equals "unhandledErrorMiddleware"
And the exception "errorClass" equals "Error"
And the exception "message" equals "error in async callback"
And the event "request.url" equals "http://koa/throw-async-callback"
And the event "request.httpMethod" equals "GET"

Scenario: a handled promise rejection in an async callback
Then I open the URL "http://koa/reject-async-callback" and get a 200 response
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"

Scenario: an unhandled promise rejection in an async callback
Then I open the URL "http://koa/unhandled-reject-async-callback" and get a 200 response
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 event "severityReason.type" equals "unhandledPromiseRejection"
And the exception "errorClass" equals "Error"

0 comments on commit 2c1bbb9

Please sign in to comment.