|
1 | | -const logger = require('@eliware/log'); |
2 | | - |
3 | | -/** |
4 | | - * Registers process-level exception handlers for uncaught exceptions, unhandled rejections, warnings, and exit events. |
5 | | - * @param {Object} options |
6 | | - * @param {Object} [options.processObj=process] - The process object to attach handlers to. |
7 | | - * @param {Object} [options.log=logger] - Logger for output. |
8 | | - * @returns {Object} { removeHandlers } - Function to remove all registered handlers (for testability). |
9 | | - */ |
10 | | -const registerHandlers = ({ |
11 | | - processObj = process, |
12 | | - log = logger |
13 | | -} = {}) => { |
14 | | - const handlers = { |
15 | | - uncaughtException: (err) => log.error('Uncaught Exception', { |
16 | | - name: err?.name, |
17 | | - message: err?.message, |
18 | | - stack: err?.stack, |
19 | | - error: err |
20 | | - }), |
21 | | - unhandledRejection: (reason, promise) => log.error('Unhandled Rejection', { |
22 | | - reason: reason instanceof Error ? { |
23 | | - name: reason.name, |
24 | | - message: reason.message, |
25 | | - stack: reason.stack, |
26 | | - error: reason |
27 | | - } : reason, |
28 | | - promise |
29 | | - }), |
30 | | - warning: (warning) => log.warn('Warning', { |
31 | | - name: warning?.name, |
32 | | - message: warning?.message, |
33 | | - stack: warning?.stack, |
34 | | - warning |
35 | | - }) |
36 | | - }; |
37 | | - processObj.on('uncaughtException', handlers.uncaughtException); |
38 | | - processObj.on('unhandledRejection', handlers.unhandledRejection); |
39 | | - processObj.on('warning', handlers.warning); |
40 | | - const removeHandlers = () => { |
41 | | - processObj.off('uncaughtException', handlers.uncaughtException); |
42 | | - processObj.off('unhandledRejection', handlers.unhandledRejection); |
43 | | - processObj.off('warning', handlers.warning); |
44 | | - }; |
45 | | - log.debug('Exception handlers registered'); |
46 | | - return { removeHandlers }; |
47 | | -}; |
48 | | - |
49 | | -module.exports = { registerHandlers }; |
50 | | -module.exports.default = registerHandlers; |
| 1 | +const logger = require('@eliware/log'); |
| 2 | + |
| 3 | +/** |
| 4 | + * Registers process-level exception handlers for uncaught exceptions, unhandled rejections, warnings, and exit events. |
| 5 | + * @param {Object} options |
| 6 | + * @param {Object} [options.processObj=process] - The process object to attach handlers to. |
| 7 | + * @param {Object} [options.log=logger] - Logger for output. |
| 8 | + * @returns {Object} { removeHandlers } - Function to remove all registered handlers (for testability). |
| 9 | + */ |
| 10 | +const registerHandlers = ({ |
| 11 | + processObj = process, |
| 12 | + log = logger |
| 13 | +} = {}) => { |
| 14 | + // Safe serializer: shallowly serialize objects without invoking toJSON or other getters |
| 15 | + const safeSerialize = (obj) => { |
| 16 | + try { |
| 17 | + if (obj === null) return null; |
| 18 | + if (typeof obj !== 'object') return obj; |
| 19 | + const out = {}; |
| 20 | + for (const k of Object.keys(obj)) { |
| 21 | + try { |
| 22 | + const v = obj[k]; |
| 23 | + if (v === null) { out[k] = null; continue; } |
| 24 | + if (typeof v === 'object') { |
| 25 | + const info = { type: v.constructor && v.constructor.name ? v.constructor.name : 'Object' }; |
| 26 | + try { if ('id' in v && (typeof v.id === 'string' || typeof v.id === 'number')) info.id = v.id; } catch (e) {} |
| 27 | + try { if ('name' in v && typeof v.name === 'string') info.name = v.name; } catch (e) {} |
| 28 | + out[k] = info; |
| 29 | + } else if (typeof v === 'function') { |
| 30 | + out[k] = `[Function: ${v.name || 'anonymous'}]`; |
| 31 | + } else { |
| 32 | + out[k] = v; |
| 33 | + } |
| 34 | + } catch (e) { |
| 35 | + out[k] = '[Unserializable]'; |
| 36 | + } |
| 37 | + } |
| 38 | + return out; |
| 39 | + } catch (e) { |
| 40 | + try { return String(obj); } catch (ee) { return '[Unserializable]'; } |
| 41 | + } |
| 42 | + }; |
| 43 | + |
| 44 | + const handlers = { |
| 45 | + uncaughtException: (err) => { |
| 46 | + try { |
| 47 | + // Include raw error object for downstream consumers, but also provide safe fields |
| 48 | + log.error('Uncaught Exception', { |
| 49 | + name: err && err.name, |
| 50 | + message: err && err.message, |
| 51 | + stack: err && err.stack, |
| 52 | + error: err |
| 53 | + }); |
| 54 | + } catch (e) { |
| 55 | + try { log.error('Uncaught Exception (logging failed)', { error: String(err) }); } catch (ee) { /* swallow */ } |
| 56 | + } |
| 57 | + }, |
| 58 | + unhandledRejection: (reason, promise) => { |
| 59 | + try { |
| 60 | + if (reason instanceof Error) { |
| 61 | + return log.error('Unhandled Rejection', { reason: { name: reason.name, message: reason.message, stack: reason.stack, error: reason }, promise }); |
| 62 | + } |
| 63 | + return log.error('Unhandled Rejection', { reason: safeSerialize(reason), promise: safeSerialize(promise) }); |
| 64 | + } catch (e) { |
| 65 | + try { log.error('Unhandled Rejection (logging failed)', { reason: String(reason) }); } catch (ee) { /* swallow */ } |
| 66 | + } |
| 67 | + }, |
| 68 | + warning: (warning) => { |
| 69 | + try { |
| 70 | + // include raw warning object while also exposing key fields |
| 71 | + log.warn('Warning', { |
| 72 | + name: warning && warning.name, |
| 73 | + message: warning && warning.message, |
| 74 | + stack: warning && warning.stack, |
| 75 | + warning |
| 76 | + }); |
| 77 | + } catch (e) { |
| 78 | + try { log.warn('Warning (logging failed)', { warning: String(warning) }); } catch (ee) { /* swallow */ } |
| 79 | + } |
| 80 | + } |
| 81 | + }; |
| 82 | + processObj.on('uncaughtException', handlers.uncaughtException); |
| 83 | + processObj.on('unhandledRejection', handlers.unhandledRejection); |
| 84 | + processObj.on('warning', handlers.warning); |
| 85 | + const removeHandlers = () => { |
| 86 | + processObj.off('uncaughtException', handlers.uncaughtException); |
| 87 | + processObj.off('unhandledRejection', handlers.unhandledRejection); |
| 88 | + processObj.off('warning', handlers.warning); |
| 89 | + }; |
| 90 | + log.debug('Exception handlers registered'); |
| 91 | + return { removeHandlers }; |
| 92 | +}; |
| 93 | + |
| 94 | +module.exports = { registerHandlers }; |
| 95 | +module.exports.default = registerHandlers; |
0 commit comments