agmission/Development/server/helpers/process_fatal_handlers.js

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,
};