Context lost in an unhandled exception (or rejection)
ecdeveloper opened this issue · comments
First of all - great module!
I was wondering if there's any workaround to get the context, in case of an unhandled exception (or rejection, in case of promises). Here's a sample code:
// cls.js
const cls = require('cls-hooked');
const clsNamespace = cls.createNamespace(process.getuid());
module.exports = {
middleware: (req, res, next) => {
run(() => {
let someContextId = getTheIDSomehow();
set('context-id', someContextId)
next()
}),
ns: clsNamespace
}
// some-other-file.js
const ns = require('./cls').ns;
process.on('unhandledRejection', () => {
// this will be undefined
console.log(ns.get('context-id'));
});
express? you can use the express-async-errors to handle the unhandleRejection issue.
const express = require('express');
require("express-async-errors");
const uuid = require('uuid');
const createNamespace = require('cls-hooked').createNamespace;
const namespace = createNamespace('com.foo');
const app = express();
app.use((req, res, next) => {
const tid = uuid.v4();
namespace.bindEmitter(req);
namespace.bindEmitter(res);
namespace.run(() => {
req.on('end', () => {
printContext("End Event");
});
namespace.set("url", req.url);
namespace.set("tid", tid);
next();
});
});
app.get('/normal', (req, res) => {
printContext();
res.send('ok');
});
app.get('/promise', async (req, res) => {
await asyncOperation();
printContext();
res.send('ok');
});
app.get('/exception', (req, res) => {
throw new Error('unhandle exception!');
});
app.get('/rejection', async (req, res) => {
throw new Error("unhandle rejection!");
});
app.use((err, req, res, next) => {
printContext("uncaughtException");
throw err;
});
function printContext(scienaro) {
const tid = namespace.get("tid");
const url = namespace.get("url");
console.log(`[${scienaro ? scienaro : url}]: ${tid}`);
}
function asyncOperation() {
return new Promise((resolve, reject) => {
printContext();
setTimeout(() => {
printContext();
resolve();
});
});
}
process.on("uncaughtException", (err) => {
console.trace(err);
});
process.on("unhandledRejection", (err) => {
console.trace(err);
});
app.listen(8586);
I would strongly recommend against this module (express-async-errors
). Reaching deeply into the router like this is a bad idea, it will also not work in v5. The behavior you want is already added in v5 (pillarjs/router#47 & pillarjs/router@d2bce0c).
In express v4, a much better approach is to use something like https://github.com/express-promise-router/express-promise-router
All that said, this is a bit off topic for this issue which is specifically about usage of this module.
Thanks for your advise :)
@ecdeveloper I hit this with a generic error being thrown so maybe it will help. But if you look in the code for run
it adds the context to a "symbol" in the error and throws it back up the chain
let context = this.createContext();
this.enter(context);
try {
if (DEBUG_CLS_HOOKED) {
const triggerId = async_hooks.triggerAsyncId();
const asyncHooksCurrentId = async_hooks.executionAsyncId();
const indentStr = ' '.repeat(this._indent < 0 ? 0 : this._indent);
debug2(`${indentStr}CONTEXT-RUN BEGIN: (${this.name}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${this._set.length} context:${util.inspect(context)}`);
}
fn(context);
return context;
} catch (exception) {
if (exception) {
exception[ERROR_SYMBOL] = context;```
so i referenced it with `err[ERROR_SYMBOL]`
I have write a tidy npm package http-request-context to do this, using async_hooks, u can try it.