Winston-MongoDB日志器未捕获异常未记录:setTimeout是否为推荐方案?
Winston-MongoDB未捕获异常日志失效问题优化方案
问题背景
在Node.js应用中使用Winston结合Winston-MongoDB记录日志时,遇到未捕获异常无法正常写入日志的问题:
- 直接在
uncaughtException事件处理中调用process.exit(1),日志既不会写入MongoDB,也不会写入本地日志文件 - 在
process.exit(1)前添加setTimeout延迟500ms后,日志能正常写入
相关代码如下:
初始异常处理代码
process.on("uncaughtException", (ex) => { logger.error(ex.message, ex); process.exit(1); });
日志器配置
const { createLogger, format, transports } = require("winston"); require("winston-mongodb"); const logger = createLogger({ transports: [ new transports.File({ filename: "logfile.log", format: format.combine(format.timestamp(), format.json()), }), new transports.MongoDB({ db: "mongodb://127.0.0.1:27017/vidly", level: "info", options: { useUnifiedTopology: true }, collection: "log", format: format.combine(format.timestamp(), format.json()), }), ], }); module.exports = logger;
临时修复代码(添加延迟)
process.on("uncaughtException", (ex) => { logger.error(ex.message, ex); setTimeout(() => { process.exit(1); }, 500); });
方案分析与优化
不推荐使用setTimeout的原因
setTimeout本质是靠硬编码的延迟时间来赌日志操作完成,存在明显缺陷:
- 延迟时间无法适配所有场景:数据库连接慢、系统高负载时,500ms可能不够,日志依然写不进去;系统资源充足时,又会白白浪费等待时间
- 属于临时 workaround,不是从根本上解决异步日志未完成就退出的问题
更优解决方案
Winston的日志方法支持回调函数和Promise返回值,可以通过等待日志写入完成后再退出进程,从根源解决问题:
方法1:使用async/await等待Promise完成
process.on("uncaughtException", async (ex) => { try { // 等待日志写入操作完成 await logger.error(ex.message, ex); process.exit(1); } catch (logErr) { // 处理日志写入失败的情况 console.error("日志写入失败:", logErr); process.exit(1); } });
方法2:使用回调函数
process.on("uncaughtException", (ex) => { logger.error(ex.message, ex, (logErr) => { if (logErr) { console.error("日志写入失败:", logErr); } // 确认日志操作完成后再退出 process.exit(1); }); });
原理说明
Winston的文件、MongoDB传输器都是异步执行写入操作的,直接调用process.exit(1)会立刻终止Node.js进程,所有未完成的异步任务都会被强制中断。通过等待日志方法的Promise或回调触发,能确保异步写入操作完成后再退出进程。
内容的提问来源于stack exchange,提问作者Haitam-Elgharras
相关产品推荐
相关产品推荐

