I have an Express server running for the backend of a website with Sentry (v5.15.5) successfully implemented. I'm now trying to improve the error handling on the backend, as at the moment if something goes wrong with the request, the request is not ended and the client sits there waiting for a response, which it never gets.
To tell the client if the request has failed I'm using the custom error handler in the documentation:
app.use(function onError(err, req, res, next) {
// The error id is attached to `res.sentry` to be returned
// and optionally displayed to the user for support.
res.statusCode = 500;
res.end(res.sentry + "\n");
});
However, when I use this on my server, only the custom error handler runs - Sentry never creates an event for the error, but if I just use another custom error function, they both get called fine, which makes me think this is a Sentry issue. Here's the relevant parts of the server code:
...
const Sentry = require('#sentry/node');
...
const app = express()
Sentry.init({ dsn: process.env.SENTRY });
...
// Middlewares
app.use(Sentry.Handlers.requestHandler());
app.use(express.json())
app.use(helmet())
app.use(cors())
app.use(morgan('tiny'))
const controllers = require('./controllers')
const wrap = fn => (...args) => Promise
.resolve(fn(...args))
.catch(args[2])
// Routes
...
app.post('/test', authUser, wrap(controllers.testController))
...
app.use(Sentry.Handlers.errorHandler());
app.use(function onError(err, req, res, next) {
res.statusCode = 500
res.end(res.sentry + "\n")
})
app.listen(PORT, () => console.log(`APP RUNNING ON PORT ${PORT}`))
The controllers on the server make database requests, etc. so they are async functions - that's why I use the wrap function to catch promise rejections and pass them to the error handler. If I unwrap the controller then Sentry works fine, but then the server never sends the error to the client.
I expect I'm probably going about this wrong as it should be pretty simple to do, but no matter what I do I cannot get Sentry + async controllers + custom error handler to work. Any help would be appreciated.
(This may be an Express issue, if so let me know and I'll take it over there)
Thanks
For some reason Sentry's 'Filter out localhost' option (which was turned off but somehow got toggled on) doesn't actually filter out all local errors. When I removed the custom error handler and wrap function, the errors managed to get past the localhost filter. After I turned it back off all the errors came through on Sentry fine.
Related
I'm trying to create an integration test using Jest for my Express app. I think I have a conceptual misunderstanding as my tests are behaving strangely. My goal is to test the following scenario. I'm hitting a specific endpoint using Supertest, and I want to check whether an error handler middleware is called if there is a mocked error. I want to check whether the error handler is not called, if there is no error present. I have the following test file:
test.js
const request = require('supertest')
describe('Error handler', () => {
let server
let router
beforeEach(() => {
jest.resetModules()
jest.resetAllMocks()
})
afterEach(async () => {
await server.close()
})
it('should be triggered if there is a router error', async () => {
jest.mock('../../routes/')
router = require('../../routes/')
router.mockImplementation(() => {
throw new Error()
})
server = require('../../server')
const res = await request(server)
.get('')
.expect(500)
.expect('Content-Type', /json/)
expect(res.body.error).toBe('Error')
expect(res.body.message).toBe('Something went wrong!')
expect(res.body.status).toBe(500 )
})
it('should not be triggered if there is no router error', async () => {
server = require('../../server')
const res = await request(server)
.get('')
.expect(201)
.expect('Content-Type', /text/)
})
})
What I think is happening is the following. Before each test I reset all modules, because I don't want to have the cached version of my server from the first require, I want to overwrite it. I also reset all mocks, so when the second test runs, no mock is used, no fake error is forced, so the middleware is not called and I'm getting back a vanilla 200 result.
After this is done, I start testing the scenario when there is an error. I mock the routes file that exports my routes so I can force a fake error. Then I require the server, this way, I suppose, it's loading the server up with the fake, error throwing route. Then I wait for the response with Supertest, and assert that I indeed got an error back - hence the error handler middleware has been triggered and worked.
The afterEach hook is called, the server is closed, then the beforeEach hook initializes everything, again. Now I have my vanilla implementation without the mock. I require my server, hit the homepage with a get request, and I get back the correct response.
The strange thing is that for some reason the second test seems to not exit gracefully. If I change my implementation from async - await in the second test, to specify the done callback, and then if I call it at the end of the test, it seems to be working.
I tried a lot of possible permutations, including putting the mocking part to the beforeEach hook, starting the server before / after mocking, and I got weird results. I feel like I have conceptual misunderstandings, but I don't know where, because there are so many moving parts.
Any help to make me understand what is wrong would be greatly appreciated
EDIT:
I thought that most parts can be considered a black box, but now I realize that the fact that I'm trying to create an app using Socket.IO makes the setup process a bit more convoluted.
I don't want Express to automatically create a server for me, because I want to use socketIO. So for now I only create a function with the appropiate signature, and that is 'app'. This can be given as an argument to http.Server(). I configure it with options and the middlewares that I want to use. I do not want to call app.listen, because that way Socket.IO could not do its own thing.
config.js
const path = require('path')
const express = require('express')
const indexRouter = require('./routes/')
const errorHandler = require('./middlewares/express/errorHandler')
const app = express()
app.set('views', path.join(__dirname + '/views'))
app.set('view engine', 'ejs')
app.use(express.static('public'))
app.use('', indexRouter)
app.use(errorHandler)
module.exports = app
In server.js I require this app, and then I create a HTTP server using it. After that, I feed it to 'socket.io', so it is connected to the proper instance. In server.js I do not call server.listen, I want to export it to a file that actually starts up the server (index.js) and I want to export it to my tests, so Supertest can spin it up.
server.js
// App is an Express server set up to use specific middlewares
const app = require('./config')
// Create a server instance so it can be used by to SocketIO
const server = require('http').Server(app)
const io = require('socket.io')(server)
const logger = require('./utils/logger')
const Game = require('./service/game')
const game = new Game()
io.on('connection', (socket) => {
logger.info(`There is a new connection! Socket ID: ${socket.id}`)
// If this is the first connection in the game, start the clock
if (!game.clockStarted) {
game.startClock(io)
game.clockStarted = true
}
game.addPlayer(socket)
socket.on('increaseTime', game.increaseTime.bind(game))
})
module.exports = server
If I understand everything correctly, basically the same thing happens, expect for a few additional steps in the example that you provided. There is no need to start the server, and then use Supertest on it, Supertest handles the process of starting up the server when I use request(server).get, etc.
EDIT 2
Right now I'm not sure whether mocking like that is enough. Some mysterious things leaves the Supertest requests hanging, and it might be that somewhere along the way it can not be ended, although I do not see why would that be the case. Anyway, here is the router:
routes/index.js
const express = require('express')
const router = express.Router()
router.get('', (req, res, next) => {
try {
res.status(200).render('../views/')
} catch (error) {
next(error)
}
})
router.get('*', (req, res, next) => {
try {
res.status(404).render('../views/not-found')
} catch (error) {
next(error)
}
})
module.exports = router
The order of requiring and mocking is correct but the order of setting up and shutting down a server probably isn't.
A safe way is to make sure the server is available before doing requests. Since Node http is asynchronous and callback-based, errors cannot be expected to be handled in async functions without promisification. Considering that server.listen(...) was called in server.js, it can be:
...
server = require('../../server')
expect(server.listening).toBe(true);
await new Promise((resolve, reject) => {
server.once('listening', resolve).once('error', reject);
});
const res = await request(server)
...
close is asynchronous and doesn't return a promise so there's nothing to await. Since it's in a dedicated block, a short way is to use done callback:
afterEach(done => {
server.close(done)
})
In case errors are suppressed in error listener, server.on('error', console.error) can make troubleshooting easier.
Supertest can handle server creation itself:
You may pass an http.Server, or a Function to request() - if the server is not already listening for connections then it is bound to an ephemeral port for you so there is no need to keep track of ports.
And can be provided with Express instance instead of Node server, this eliminates the need to handle server instances manually:
await request(app)
...
The legacy Sentry Node SDK (raven) allowed sending HTTP request information along with the error, by passing the request object inside the options object (2nd argument):
Raven.captureException(someError, { req: req });
Line of code extracted from the documentation: https://docs.sentry.io/clients/node/usage/#raven-recording-breadcrumbs
With that we could get additional context about the error, such as what was the device being used.
Is there any way we can pass the request object with the new SDK? The context section of the new SDK explains how to send data such as user identification, custom tags, etc by using scopes, but no request option is available in there. Does this mean that the request information should now be manually sent inside tags and/or extra objects?
As #MarkusUnterwaditzer shared, the Express integration worked for me: https://docs.sentry.io/platforms/node/express/ The key parts are adding the Sentry.Handlers.requestHandler() middleware and then either the errorHandler middleware or doing Sentry.captureException(error) yourself (with no need to pass in {req: req}).
Sample code:
const express = require('express');
const app = express();
const Sentry = require('#sentry/node');
Sentry.init({ dsn: 'https://8f0620a3bfea4f2ca26aefb074851e23#sentry.io/280382' });
// The request handler must be the first middleware on the app
app.use(Sentry.Handlers.requestHandler());
app.get('/', function mainHandler(req, res) {
throw new Error('Broke!');
});
// The error handler must be before any other error middleware
app.use(Sentry.Handlers.errorHandler());
// Optional fallthrough error handler
app.use(function onError(err, req, res, next) {
// The error id is attached to `res.sentry` to be returned
// and optionally displayed to the user for support.
res.statusCode = 500;
res.end(res.sentry + '\n');
});
app.listen(3000);
I'm experimenting with KrakenJS, trying to build an basic API to understand things.
One thing I'm unsure of is the middleware, specifically the 404/500 error handling.
"fileNotFound": {
"enabled": true,
"priority": 130,
"module": {
"name":"path:./lib/exceptions/404"
}
}
This catches any 404 errros, and then I handle that myself in my own configuration. However, why is this fileNotFound thrown? Where is serverError thrown for 500 errors?
I would like to define my own files for other exceptions like a 403, however how would I get this to trigger a middleware?
As #HeadCode mentioned, definitely read up on meddleware to understand how middleware gets loaded a bit better.
That said, we have to go over a few things in order to make what's happening more clear.
Handling 404s
First, let's go over how one would typically register a 404 handler in a plain old express app.
Typically you'd have the final middleware in your middleware continuation chain just assume that, if we made it that far without bailing, we simply cannot find the resource. Here's an example:
var express = require('express');
var app = express();
app.get('/firstRoute', function handler(req, res) { res.send('found me'); });
app.get('/secondRoute', function handler(req, res) { res.send('found me'); });
app.use(function notFoundHandler(req, res, next) {
res.status(404).send('Route Not Found');
});
app.listen(8000, function onListen() { console.log('listening on 8000...'); });
Since routes are resolved in the order they're added in Express 4, as long as your 404 handler is last you can be certain no other route matched.
This pattern is briefly described in the Express FAQs.
Handling 500s
Now let's move on to 500s.
Express has the concept of an error-handling middleware (also described on the Express site). An error handling middleware requires an arity of 4 (i.e., takes four arguments) and that's the only difference. They're only executed if an error is signaled which is done by passing an object into your next call. Easier explained in code:
var express = require('express');
var app = express();
app.get('/firstRoute', function handler(req, res) { res.send('found me'); });
app.get('/secondRoute', function handler(req, res) { throw new Error('oops'); });
app.use(function notFoundHandler(req, res, next) {
res.status(404).send('Route Not Found');
});
app.use(function errorHandler(err, req, res, next) {
res.status(500).send('Broken. :(');
});
app.listen(8000, function onListen() { console.log('listening on 8000...'); });
In the above example, errorHandler will only execute if either 1) another middleware or route handler throws an error or 2) we call next with an argument*, .e.g. next(new Error('oops')).
Go ahead and run that. if you visit /notFound or any random route, you'll correctly get a 404. If you visit /firstRoute you'll get found me, and if you go to secondRoute you'll get Broken. :(.
What about kraken?
Kraken—or more accurately, meddleware—just moves defining your middleware into your config. That little block of json you copied above is basically functionally equivalent to the following in a vanilla express app:
var fileNotFound = require('./lib/exceptions/404');
// ... app.use() everything with a priority lower than 130 ...
app.use(fileNotFound());
// ... app.use() everything with a priority *greater* than 130 ...
Hi I'm developing my first Node.js application and I found my first issue in something really basic, I can't catch the HTTP errors (404, 500, ...)
I've followed the doc (I think so) but I suppose that I'm missing something.
This is my code:
var express = require('express')
app = express()
app.use(express.logger('dev'));
app.get('/', function(req, res) {
res.send('Hellow World!!!')
})
app.use(app.router);
app.use(function(err, req, res, next) {
console.error('Oh!! ERROR');
console.error('My ERROR: ' + JSON.stringify(err));
});
app.listen(3000);
console.log('Server running at http://127.0.0.1:3000/');
Thanks in advance.
So the way express works is if it hits the end of the entire stack of middleware and route handlers and nothing has responded yet, it's going to send the fallback 404 response automatically without raising an error. This is why your error handling middleware isn't being called. By default, express doesn't consider a 404 an error, which is probably the right way to think of it.
If you want to customize this, you can put a middleware at the end of your stack to either serve a custom 404 page directly or call next with an error if you prefer that way.
app.get('/'...
app.get('/two'...
app.get(/'three'...
app.use(middleware1...
app.use(middleware2...
app.use(app.router);
app.use(function (req, res, next) {
//Option 1: respond here
return res.status(404).render('my_404_view');
//Option 2: trigger error handler
return next(new Error('404ed!'));
}
Option 2 will trigger your error handling middleware. There you will have to get info from the error object to determine the desired view to render and status code to send. I personally define an errors.NotFound class that has a 404 code and lets me render a friendly 404 page view.
I am trying to setup error handling for my express app and running into the following problem.
I defined an error middleware and add it as the last middleware:
// error handler
app.use(function(err, req, res, next) {
console.log('JUST TESTING. ERROR HANLDER HAS BEEN CALLED...');
next(err);
});
Now I would expect this middleware to be called whenever an error occurs:
app.get('/datenschutz', function(req, res, next){
return next(new Error('Just testing')); // handle everything here
});
However my middleware is never called! The browser does display the stack trace however.
This seems that there is another middleware that is catching this error and processing it before I can do anything about it.
The problem is that I have no clue where this middleware could be defined, as I have a very simple setup:
// setup ssl for local testing
var
app = express();
app.
use(express.static(__dirname + '/public')).
use(express.bodyParser()).
use(express.cookieParser());
Why is my error handling middleware not being called?
Where is this 'default' error handling taking place?
Thanks!
* EDIT *
I see that the middleware is indeed working. However this is the case if I call it from another middleware function.
However it is not being invoked if the error occurs inside a function defined as an express route (GET, POST, etc..). This is very strange.
If I add my error middleware to the route callbacks it then works:
app.get('/testError', function(req, res, next){
return next(new Error('Just testing')); // handle everything here
}, function(err,req,res,next) {
console.log('This error handler is called!!');
return next();
});
* EDIT 2 - FOUND ACCEPTABLE WORKAROUND **
I am surprised it has to be done this way. As I had read many entries/questions about error handling in express and never found this possibility mentioned.
However it seems that if an error ocurrs inside a route callback regular error middleware handlers will not pick it up. You will need to define an error handler at route level.
app.all('*', function(err,req,res,next) {
console.log('This is a global error handler at route level....');
return next(err);
});
I had this problem as well, but I couldn't figure out why it wasn't working even though I set my error handler after the app.user(app.router). As it turns out, I already had an error handler that I wasn't aware of.
Specifically, if you use the express cli to generate an app like I did, it will automatically add in this in:
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
Unfortunately for me, I added a bit more middleware to my app, which consequently obscured this statement and thus prevented my custom error handler from being called.
Simply remove that and then it should work properly.
On a side note, I should mention that the original solution still worked - even with the app.use(express.errorHandler()).
app.all('*', function(err,req,res,next) {
console.log('This is a global error handler at route level....');
return next(err);
});
Updated answer for Express 4 users from the Express 4 docs. See example from docs below. Note that app.router is deprecated and no longer used. I also added a dummy route to make the ordering clear:
"You define error-handling middleware last, after other app.use() and routes calls; For example:
var bodyParser = require('body-parser');
app.use(bodyParser());
app.get('/', function(req, res) {
res.send('hello world');
})
app.use(function(err, req, res, next) {
// logic
});
"
EDIT 2 (sabtioagoIT) works. But just for those who missed it, emostar's solution also works.
I understood to move the error handling 'use' call to the end, but there seems to be an easier option as emoster suggests, use app.router (before the error handling 'use' call).
instead of making
app.get('/datenschutz', function(req, res, next){
return next(new Error('Just testing')); // handle everything here
});
you can install express-async-errors
and just make
app.get('/datenschutz', function(req, res){
throw new Error('Just testing');
});
it works as expected