blob: 16debdf9c6edca4f9fe33d28f8fb4b5cb55bb4c9 [file] [log] [blame]
const sourceMap = require('source-map');
const util = require('util');
const path = require('path');
const fse = require("fs-extra");
const ErrorCodes = {
UNCAUGHT: 120,
UNKNOWN: 127,
INVALID_ARGUMENT: 128,
RESOURCE_PROBLEM: 129,
KARMA_FAIL: 130,
UNHANDLED_REJECTION_FAILURE: 131
};
const mappingSource = async (parsedLine) => {
if (typeof parsedLine === 'string') {
return parsedLine;
}
const functionName = parsedLine[1];
const fileName = parsedLine[2];
const line = +parsedLine[3];
const column = +parsedLine[4];
const mapFileName = fileName + ".map";
if (!fse.pathExistsSync(mapFileName)) {
return parsedLine.input;
}
const mapData = await fse.readJson(mapFileName);
const consumer = await new sourceMap.SourceMapConsumer(mapData);
const sourcePos = consumer.originalPositionFor({ line: line, column: column });
if (sourcePos && sourcePos.source) {
const source = path.join(path.dirname(fileName), sourcePos.source);
return util.format(" at %s (%s:%s:%s)", functionName, source, sourcePos.line, sourcePos.column);
}
return util.format(" at %s (%s:%s:%s)", functionName, fileName, line, column);
}
const resolveCallStack = async (error) => {
const stackLines = error.stack.split("\n");
const parsed = stackLines.map((line) => {
let match = line.match(/^\s*at ([^(]*) \((.*?):([0-9]+):([0-9]+)\)$/);
if (match) {
return match;
}
match = line.match(/^\s*at (.*?):([0-9]+):([0-9]+)$/);
if (match) {
match.splice(1, 0, "<anonymous>");
return match;
}
return line;
});
// https://stackoverflow.com/questions/40140149/use-async-await-with-array-map
const remapped = await Promise.all(
parsed.map(async (parsedLine) => {
return await mappingSource(parsedLine)
})
);
let outputMessage = remapped.join("\n");
if (outputMessage.indexOf(error.message) === -1) {
// when fibers throw error in node 0.12.x, the stack does NOT contain the message
outputMessage = outputMessage.replace(/Error/, "Error: " + error.message);
}
return outputMessage;
}
const installUncaughtExceptionListener = (actionOnException) => {
const handler = async (err) => {
try {
let callstack = err.stack;
if (callstack) {
try {
// try to resolve stack to the source file
callstack = await resolveCallStack(err);
} catch (err) {
console.error("Error while resolving callStack:", err);
}
}
console.error(callstack || err.toString());
if (actionOnException) {
actionOnException();
}
} catch (err) {
// In case the handler throws error and we do not catch it, we'll go in infinite loop of unhandled rejections.
// We cannot do anything here as even `console.error` may fail. So just exit the process.
process.exit(ErrorCodes.UNHANDLED_REJECTION_FAILURE);
}
};
// we want to see real exceptions with backtraces and stuff
process.removeAllListeners('uncaughtException')
process.removeAllListeners('unhandledRejection')
process.on("uncaughtException", handler);
process.on("unhandledRejection", handler);
}
module.exports = {
installUncaughtExceptionListener
}