actionhero
Version:
The reusable, scalable, and quick node.js API server for stateless and stateful applications
156 lines (142 loc) • 3.76 kB
text/typescript
import { api, config, log, Initializer, Action } from "../index";
import { ExceptionReporter } from "../classes/exceptionReporter";
import { ParsedJob } from "node-resque";
import { ActionheroLogLevel } from "../modules/log";
export interface ExceptionHandlerAPI {
reporters: Array<ExceptionReporter>;
report: ExceptionsInitializer["report"];
initializer: ExceptionsInitializer["initializer"];
action: ExceptionsInitializer["action"];
task: ExceptionsInitializer["task"];
}
/**
* Handlers for when things go wrong.
*/
export class ExceptionsInitializer extends Initializer {
constructor() {
super();
this.name = "exceptions";
this.loadPriority = 1;
}
report = (
error: NodeJS.ErrnoException,
type: string,
name: string,
objects?: any,
severity?: ActionheroLogLevel,
) => {
if (!severity) severity = "error";
for (const reporter of api.exceptionHandlers.reporters) {
reporter(error, type, name, objects, severity);
}
};
initializer = (error: NodeJS.ErrnoException, fullFilePath: string) => {
const name = "initializer:" + fullFilePath;
api.exceptionHandlers.report(
error,
"initializer",
name,
{ fullFilePath: fullFilePath },
"alert",
);
};
action = (
error: NodeJS.ErrnoException,
{
to,
action,
params,
duration,
response,
}: {
to: string;
action: Action["name"];
params: string;
duration: number;
response: string;
},
) => {
api.exceptionHandlers.report(
error,
"action",
`action: ${action}`,
{ to, action, params, duration, error, response },
"alert",
);
};
task = (
error: NodeJS.ErrnoException,
queue: string,
task: ParsedJob,
workerId: string | number,
) => {
let simpleName;
try {
simpleName = task["class"];
} catch (e) {
simpleName = error.message;
}
const name = "task:" + simpleName;
api.exceptionHandlers.report(
error,
"task",
name,
{ task: task, queue: queue, workerId: workerId },
config.tasks.workerLogging.failure,
);
};
async initialize() {
api.exceptionHandlers = {
reporters: [],
report: this.report,
initializer: this.initializer,
action: this.action,
task: this.task,
};
api.exceptionHandlers.reporters.push(consoleReporter);
}
}
const consoleReporter: ExceptionReporter = (
error: NodeJS.ErrnoException & { [key: string]: any },
type,
name,
objects,
severity,
) => {
let message = "";
const data = error["data"] ?? {};
if (type === "uncaught") {
message = `Uncaught ${name}`;
} else if (type === "action") {
// no need to log anything, it was handled already by the actionProcessor
} else if (type === "initializer") {
message = `Error from Initializer`;
} else if (type === "task") {
message = `Error from Task`;
data["name"] = name;
data["queue"] = objects.queue;
data["worker"] = objects.workerId;
data["arguments"] = objects?.task?.args
? JSON.stringify(objects.task.args[0])
: undefined;
} else {
message = `Error: ${error?.message || error.toString()}`;
Object.getOwnPropertyNames(error)
.filter((prop) => prop !== "message")
.sort((a, b) => (a === "stack" || b === "stack" ? -1 : 1))
.forEach((prop) => (data[prop] = error[prop]));
data["type"] = type;
data["name"] = name;
data["data"] = objects;
}
if (error["stack"]) {
data["stack"] = error.stack;
} else {
data["stack"] = error.message ?? error.toString();
}
try {
if (message) log(message, severity, data);
} catch (e) {
console.log(message, data);
}
};