在 Node.js v18 中,async_hooks 没有系统级的全局配置文件开关。要实现全局追踪,必须通过命令行参数预加载脚本或在入口文件手动注册。由于该 API 会拦截所有异步操作,存在性能开销,建议仅用于调试或特定链路追踪场景。
先说结论:无法通过配置文件全局开启,需在启动时预加载钩子脚本或在入口文件手动注册。
- 适合:链路追踪调试、异步资源泄漏排查
- 先看:性能开销文档,评估业务承受能力
- 建议:仅在内测或排查问题时临时启用
核心实现代码(tracer.js)
以下是一个安全的预加载脚本示例。关键点在于 Hook 回调内部避免使用 console.log 等异步方法,改用 fs.writeSync 同步写入,防止触发新的异步操作导致无限递归或性能雪崩。
const async_hooks = require('async_hooks');
const fs = require('fs');
// 使用同步写入标准输出,避免在 hook 中触发新的异步操作
function safeLog(msg) {
fs.writeSync(1, `[AsyncHooks] ${msg}\n`);
}
const hook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
// 过滤掉部分内部类型以减少日志噪音,例如 'TIMERWRAP'
if (type !== 'TIMERWRAP') {
safeLog(`Init: ${type} (ID: ${asyncId})`);
}
},
before(asyncId) {
// 生产环境建议注释掉 before/after 以减少开销
// safeLog(`Before: ${asyncId}`);
},
after(asyncId) {
// safeLog(`After: ${asyncId}`);
},
destroy(asyncId) {
// safeLog(`Destroy: ${asyncId}`);
}
});
// 启用钩子
hook.enable();
启动与验证方法
保存上述代码为 tracer.js,然后创建一个简单的测试文件 test.js 来触发异步操作。
// test.js
setTimeout(() => {
console.log('Timeout callback executed');
}, 100);
Promise.resolve().then(() => {
console.log('Promise callback executed');
});
使用 `--require` 参数启动应用。注意该参数仅适用于 CommonJS 模块环境。
node `--require` ./tracer.js test.js
验证成功标志:运行后,控制台应在业务日志之前打印出 [AsyncHooks] Init: TIMEOUT 或 [AsyncHooks] Init: PROMISE 等预设日志信息。
注意事项与常见风险
- 日志递归风险:严禁在 Hook 回调中使用
console.log、process.stdout.write(异步) 或任何会触发异步 I/O 的方法,必须使用fs.writeSync。 - 模块系统兼容性:
`--require`仅对 CommonJS 有效。如果项目配置了"type": "module"或使用 ESM,预加载脚本可能无法生效,需改用`--import`(Node.js v18.19+) 或在入口文件显式 require。 - 性能开销:开启 async_hooks 会增加 CPU 和内存消耗。生产环境长期开启可能导致请求延迟增加,务必经过压测评估。
- 清理资源:如果是通过代码引入而非命令行预加载,确保在不需要追踪时调用
hook.disable()或移除 require,避免持续开销。
参考来源
Node.js Official Documentation - Async Hooks
URL: https://nodejs.org/docs/latest-v18.x/api/async_hooks.html