131 lines
3.9 KiB
JavaScript
131 lines
3.9 KiB
JavaScript
'use strict';
|
|
|
|
const fatalReporter = require('./fatal_error_reporter');
|
|
const mailer = require('./mailer');
|
|
|
|
function normalizeReason(reason) {
|
|
if (!reason) return { message: 'Unknown error', stack: '' };
|
|
if (reason instanceof Error) return { message: String(reason.message || reason), stack: String(reason.stack || '') };
|
|
try {
|
|
return {
|
|
message: String(reason.message || reason.toString?.() || reason),
|
|
stack: String(reason.stack || ''),
|
|
};
|
|
} catch {
|
|
return { message: 'Unknown error', stack: '' };
|
|
}
|
|
}
|
|
|
|
function createDefaultIgnore() {
|
|
return () => false;
|
|
}
|
|
|
|
function createServerIgnore() {
|
|
return (errLike) => {
|
|
const errString = String((errLike && (errLike.message || errLike)) || '');
|
|
const errStack = String((errLike && errLike.stack) || '');
|
|
|
|
const isHttpStreamError =
|
|
errString.includes("reading 'readable'") ||
|
|
errStack.includes('_http_incoming') ||
|
|
errStack.includes('IncomingMessage._read') ||
|
|
errStack.includes('resume_') ||
|
|
(errLike && (errLike.code === 'ECONNRESET' || errLike.code === 'EPIPE' || errLike.code === 'ERR_STREAM_DESTROYED'));
|
|
|
|
const isFinalHandlerCleanup =
|
|
errStack.includes('finalhandler') ||
|
|
errStack.includes('ServerResponse.removeHeader') ||
|
|
errString.includes('Cannot convert undefined or null to object');
|
|
|
|
return isHttpStreamError || isFinalHandlerCleanup;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Registers process-level fatal handlers.
|
|
*
|
|
* - Writes a last-fatal JSON report to a file atomically (tolerates corrupt JSON by archiving it)
|
|
* - Optionally sends admin email
|
|
* - Optionally exits the process after a delay
|
|
*/
|
|
function registerFatalHandlers(proc, opts) {
|
|
const {
|
|
env,
|
|
debug,
|
|
kindPrefix,
|
|
reportFilePath,
|
|
ignore,
|
|
} = opts || {};
|
|
|
|
const ignoreFn = ignore || createDefaultIgnore();
|
|
|
|
const onUncaughtException = (err) => {
|
|
if (ignoreFn(err)) {
|
|
debug && debug('HTTP stream error (ignored - likely client disconnect):', String(err && (err.message || err) || ''));
|
|
return;
|
|
}
|
|
|
|
const { message, stack } = normalizeReason(err);
|
|
|
|
if (env && env.FATAL_REPORT_ENABLED) {
|
|
fatalReporter.reportFatal({
|
|
filePath: reportFilePath || (env && env.FATAL_REPORT_FILE),
|
|
kind: kindPrefix ? `${kindPrefix}:uncaughtException` : 'uncaughtException',
|
|
error: err,
|
|
message: stack || message,
|
|
throttleMs: env.FATAL_THROTTLE_MS,
|
|
emailEnabled: env.FATAL_REPORT_EMAIL_ENABLED,
|
|
emailTo: env.FATAL_REPORT_EMAIL_TO || env.AGM_ADM_EMAIL,
|
|
mailer,
|
|
});
|
|
}
|
|
|
|
debug && debug('uncaughtException:', err);
|
|
|
|
if (env && env.FATAL_EXIT_ON_ERROR) {
|
|
setTimeout(() => process.exit(1), env.FATAL_EXIT_DELAY_MS).unref();
|
|
}
|
|
};
|
|
|
|
const onUnhandledRejection = (reason, p) => {
|
|
if (ignoreFn(reason)) {
|
|
debug && debug('HTTP stream error (ignored - likely client disconnect):', String(reason && (reason.message || reason) || ''));
|
|
return;
|
|
}
|
|
|
|
const { message, stack } = normalizeReason(reason);
|
|
|
|
if (env && env.FATAL_REPORT_ENABLED) {
|
|
fatalReporter.reportFatal({
|
|
filePath: reportFilePath || (env && env.FATAL_REPORT_FILE),
|
|
kind: kindPrefix ? `${kindPrefix}:unhandledRejection` : 'unhandledRejection',
|
|
error: reason,
|
|
message: stack || message,
|
|
throttleMs: env.FATAL_THROTTLE_MS,
|
|
emailEnabled: env.FATAL_REPORT_EMAIL_ENABLED,
|
|
emailTo: env.FATAL_REPORT_EMAIL_TO || env.AGM_ADM_EMAIL,
|
|
mailer,
|
|
});
|
|
}
|
|
|
|
debug && debug('unhandledRejection:', reason, 'at Promise', p);
|
|
|
|
if (env && env.FATAL_EXIT_ON_ERROR) {
|
|
setTimeout(() => process.exit(1), env.FATAL_EXIT_DELAY_MS).unref();
|
|
}
|
|
};
|
|
|
|
proc.on('uncaughtException', onUncaughtException);
|
|
proc.on('unhandledRejection', onUnhandledRejection);
|
|
|
|
return () => {
|
|
proc.off('uncaughtException', onUncaughtException);
|
|
proc.off('unhandledRejection', onUnhandledRejection);
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
registerFatalHandlers,
|
|
createServerIgnore,
|
|
};
|