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