Skip to content

Commit 3e579b0

Browse files
committed
feat(errors): adding koa and express error handler support
1 parent 67b6d13 commit 3e579b0

6 files changed

Lines changed: 164 additions & 1 deletion

File tree

lib/agent/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,10 @@ Agent.prototype.reportError = function (errorName, error) {
251251
type: 'us',
252252
data: {
253253
name: errorName,
254-
payload: [error]
254+
payload: [{
255+
stack: error.stack,
256+
message: error.message
257+
}]
255258
}
256259
}
257260

lib/instrumentations/express.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
var debug = require('debug')('risingstack/trace')
2+
var Shimmer = require('../utils/shimmer')
3+
4+
module.exports = function (express, agent) {
5+
var isVersion4 = !!express &&
6+
express.Router &&
7+
express.Router.process_params &&
8+
express.application &&
9+
express.application.del
10+
11+
// for now support only express@4
12+
if (!isVersion4) {
13+
debug('express version is not supported, not wrapping error handler')
14+
return express
15+
}
16+
17+
var errorHandlerLayer
18+
function expressErrorHandler (error, request, response, next) {
19+
agent.reportError('express_error', error)
20+
next(error)
21+
}
22+
23+
// in express errorhandlers have 4 arguments
24+
function isErrorHandler (middleware) {
25+
return middleware && middleware.handle && middleware.handle.length === 4
26+
}
27+
28+
// it will be called every time a middleware is added to express
29+
// in these cases we remove our expressErrorHandler and add it later with addErrorHandler
30+
function removeErrorHandler (app) {
31+
var i
32+
if (app.stack && app.stack.length) {
33+
for (i = app.stack.length - 1; i >= 0; i -= 1) {
34+
if (app.stack[i] === errorHandlerLayer) {
35+
app.stack.splice(i, 1)
36+
break
37+
}
38+
}
39+
}
40+
}
41+
42+
// look for the error handler provided by user, if found insert right before
43+
// if not found insert to the end
44+
function addErrorHandler (app) {
45+
var errorMiddlewareFound = false
46+
var i
47+
48+
for (i = 0; i < app.stack.length; i++) {
49+
var middleware = app.stack[i]
50+
if (isErrorHandler(middleware)) {
51+
app.stack.splice(i, 0, errorHandlerLayer)
52+
errorMiddlewareFound = true
53+
break
54+
}
55+
}
56+
57+
if (!errorMiddlewareFound) {
58+
app.stack.push(errorHandlerLayer)
59+
}
60+
}
61+
62+
Shimmer.wrap(express.Router, 'express.Router', 'use', function (original) {
63+
return function () {
64+
// magic to create an express layer from the error handler function
65+
if (!errorHandlerLayer) {
66+
errorHandlerLayer = original
67+
.call(this, '/', expressErrorHandler)
68+
.stack.pop()
69+
}
70+
removeErrorHandler(this)
71+
var app = original.apply(this, arguments)
72+
addErrorHandler(this)
73+
return app
74+
}
75+
})
76+
77+
return express
78+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
var expect = require('chai').expect
2+
var express = require('./express')
3+
var Shimmer = require('../utils/shimmer')
4+
5+
describe('The express wrapper module', function () {
6+
it('only wraps express 4', function () {
7+
var shimmerStub = this.sandbox.stub(Shimmer, 'wrap')
8+
9+
express({})
10+
11+
expect(shimmerStub).to.be.not.called
12+
})
13+
14+
it('adds an error handler layer', function () {
15+
var useSpy = this.sandbox.spy(function (path, handler) {
16+
fakeExpress.Router.stack.push({
17+
handle: handler
18+
})
19+
return fakeExpress.Router
20+
})
21+
22+
var fakeExpress = {
23+
Router: {
24+
stack: [],
25+
process_params: 'process_params',
26+
use: useSpy
27+
},
28+
application: {
29+
del: 'del'
30+
}
31+
}
32+
33+
var fakeAgent = {
34+
reportError: this.sandbox.spy()
35+
}
36+
37+
express(fakeExpress, fakeAgent)
38+
fakeExpress.Router.use('/', function (a, b, c) {})
39+
fakeExpress.Router.use('/', function (a, b, c) {})
40+
fakeExpress.Router.use('/', function (a, b, c, d) {})
41+
42+
expect(fakeExpress.Router.stack.length).to.eql(4)
43+
expect(fakeExpress.Router.stack[2].handle.name).to.eql('expressErrorHandler')
44+
})
45+
})

lib/instrumentations/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ var INSTRUMENTED_LIBS = [
1313
'mysql',
1414
'when',
1515
'q',
16+
'koa',
17+
'express',
1618
// knex and bookshelf does some black magic, so we have to do this :(
1719
'bluebird.main'
1820
]

lib/instrumentations/koa.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
var Shimmer = require('../utils/shimmer')
2+
3+
module.exports = function (koa, agent) {
4+
Shimmer.wrap(koa.prototype, 'koa.prototype', 'onerror', function (original) {
5+
return function (error) {
6+
agent.reportError('koa_error', error)
7+
return original.apply(this, arguments)
8+
}
9+
})
10+
11+
return koa
12+
}

lib/instrumentations/koa.spec.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var expect = require('chai').expect
2+
var koa = require('./koa')
3+
4+
describe('The koa wrapper module', function () {
5+
it('wraps koa onerror', function () {
6+
var onerrorSpy = this.sandbox.spy()
7+
8+
function FakeKoa () {}
9+
FakeKoa.prototype.onerror = onerrorSpy
10+
11+
var fakeAgent = {
12+
reportError: this.sandbox.spy()
13+
}
14+
15+
koa(FakeKoa, fakeAgent)
16+
17+
var error = new Error('Test error')
18+
new FakeKoa().onerror(error)
19+
20+
expect(fakeAgent.reportError).to.be.calledWith('koa_error', error)
21+
expect(onerrorSpy).to.be.calledWith(error)
22+
})
23+
})

0 commit comments

Comments
 (0)