2026/2/2 11:29:19
网站建设
项目流程
流量卡网站,服务商查询,徐州手工活外发加工网,江苏建设教育网首页续一下上篇的 async-hook 所有异步函数
这个走了一个弯路#xff0c;本来想打印堆栈 异步回调函数的tostring, 但是一直获取不到业务代码app.js的堆栈。突然想起来#xff0c;这里没有必要也不应该输出堆栈#xff0c;否则日志量就太夸张了 。
因此只输出 回调函数的tostri…续一下上篇的 async-hook 所有异步函数这个走了一个弯路本来想打印堆栈 异步回调函数的tostring, 但是一直获取不到业务代码app.js的堆栈。突然想起来这里没有必要也不应该输出堆栈否则日志量就太夸张了 。因此只输出 回调函数的tostring .// trace_all_async_safe.js —— 专注 init 函数源码追踪版 // 用法:node--require ./trace_all_async_safe.js app.js const async_hooksrequire(async_hooks);const fsrequire(fs);functionlog(msg){fs.writeSync(1, msg \n);// 同步写 stdout避免 TTYWRAP 递归}// 必须忽略这些类型否则 init 阶段就会递归崩溃 const IGNORE_TYPESnew Set([TTYWRAP,TickObject,TIMERWRAP,Immediate,SHUTDOWNWRAP,SIGNALWRAP,TCPCONNECTWRAP, // ← 加这一行GETADDRINFOREQWRAP// ← 也可以加这一行]);// 你关心的类型这些会输出详细日志 源码 栈 const TRACK_TYPESnew Set([Timeout, // setTimeout / setIntervalImmediate, // setImmediate即使在 IGNORE也想看源码时可移出 IGNOREPROMISE, // Promise无源码FSREQCALLBACK, // fs 操作GETADDRINFOREQWRAP, // dns.lookupTCPCONNECTWRAP, // net.connectHTTPINCOMINGMESSAGE,HTTPCLIENTREQUEST,DNSCHANNEL]);// 安全的 toString 封装函数源码截断到 maxLengthfunctionsafeToString(obj, maxLength500){try{if(objnull)returnnull;if(objundefined)returnundefined;if(typeof objfunction){const strobj.toString();returnstr.lengthmaxLength ? str.substring(0, maxLength)...\n// ... (truncated):str;}if(typeof objobject){returnObject.prototype.toString.call(obj);}returnString(obj);}catch(e){return[toString Error: ${e.message}];}}// 提取回调函数不同类型不同字段functiongetCallback(resource,type){if(!resource)returnnull;try{switch(type){caseTimeout:returnresource._onTimeout||null;caseImmediate:returnresource._onImmediate||null;caseTickObject:returnresource._callback||null;default:returnnull;}}catch(e){returnnull;}}async_hooks.createHook({init(asyncId, type, triggerAsyncId, resource){if(IGNORE_TYPES.has(type)){return;// 完全忽略防止任何递归}if(TRACK_TYPES.has(type)){log([CREATED]${type}asyncId${asyncId}trigger${triggerAsyncId||root});const callbackgetCallback(resource,type);const callbackSourcecallback ? safeToString(callback,500):No callback found;console.log(${typeTimeout?Delay:${resource?._idleTimeout || unknown}ms:}${typeTimeout?Repeat:${resource?._repeat ? yes (setInterval):no (setTimeout)}:}${callbackSource})}}, before(asyncId){},}).enable();log(\n.repeat(70));log(Async Tracer STARTED - Tracking selected types with source stack);log(Tracked types: Array.from(TRACK_TYPES).join(, ));log(.repeat(70)\n);// Timeout(setTimeout)setTimeout(functiontimeoutCallback(){console.log( ✓ Timeout executed);},500)// 日志[CREATED]TimeoutasyncId13trigger1Delay: 500ms Repeat: no(setTimeout)functiontimeoutCallback(){console.log( ✓ Timeout executed);}✓ Timeout executed但是这个貌似c层也有解决方案。改到c层试一下看一下c层的async-hook其实就是把上面的js 逻辑写到下面的cc里。位置D:\Code\C\node\src\async_wrap.cc#include tracing/trace_event.h void AsyncWrap::EmitAsyncInit(Environment* env, LocalObject object, LocalString type, double async_id, double trigger_async_id) { // koohai add 1224 static bool async_trace_enabled (getenv(NODE_ASYNC_TRACE_ENABLED) ! nullptr); if (async_trace_enabled) { Isolate* isolate env-isolate(); LocalContext context env-context(); String::Utf8Value type_str(isolate, type); fprintf(stderr, {async|init:[%s] - id:%.0f, *type_str, async_id); if (strcmp(*type_str, Timeout) 0) { LocalValue callback_val; if (object-Get(context, FIXED_ONE_BYTE_STRING(isolate, _onTimeout)) .ToLocal(callback_val) callback_val-IsFunction()) { Localv8::Function fn callback_val.Asv8::Function(); String::Utf8Value name(isolate, fn-GetName()); fprintf(stderr, - cb:[%s], name.length() 0 ? *name : anonymous); } } fprintf(stderr, }\n); fflush(stderr); } // koohai add end CHECK(!object.IsEmpty()); CHECK(!type.IsEmpty()); //....源代码 void AsyncWrap::EmitBefore(Environment* env, double async_id) { // koohai add 1224 static bool async_trace_enabled (getenv(NODE_ASYNC_TRACE_ENABLED) ! nullptr); if (async_trace_enabled) { fprintf(stderr, {async|before - id:%f}\n, async_id); fflush(stderr); } //koohai added 1224 Emit(env, async_id, AsyncHooks::kBefore, env-async_hooks_before_function()); }编译测试一波测试一下异步hookwindowglobal; window.test 123; console.log(window.test); // 故意使用 global 来触发你的对象拦截器 global.myAppStatus starting; console.log(\n--- 开始异步测试 ---); // 1. 测试 Timeout (触发 EmitAsyncInit 和 EmitBefore) setTimeout(function myTimer() { global.timeoutTriggered true; console.log(1. 定时器回调执行中...); }, 100); // 2. 测试 Promise (触发 PROMISE 类型) Promise.resolve().then(() { global.promiseResolved yes; console.log(2. Promise 微任务执行中...); }); // 3. 测试文件 I/O (触发 FSREQCALLBACK) const fs require(fs); fs.readFile(__filename, (err, data) { global.fsReadDone true; console.log(3. 文件读取完成长度:, data.length); }); console.log(--- 同步代码执行完毕 ---\n);执行后出现D:\Code\C\node.\out\Release\node.exe demo.js {async|init:[TTYWRAP] - id:2} {async|init:[SIGNALWRAP] - id:3} {async|init:[TTYWRAP] - id:4} 123 --- 开始异步测试 --- {async|init:[FSREQCALLBACK] - id:7} --- 同步代码执行完毕 --- 2. Promise 微任务执行中... {async|before - id:7.000000} {async|init:[FSREQCALLBACK] - id:9} {async|before - id:9.000000} {async|init:[FSREQCALLBACK] - id:10} {async|before - id:10.000000} {async|init:[FSREQCALLBACK] - id:11} {async|before - id:11.000000} 3. 文件读取完成长度: 845 1. 定时器回调执行中...只是这个FSREQCALLBACK 等不明显回头改成对应的函数名试试。 这个输出的太多了还是要修改成过滤的模式也没有输出tostring。下篇继续。Trace Events的使用浅试 一下trace Events,还没get到方法。# 调试模式输出所有日志node--trace-events-enabled\--trace-event-categories proxy,async\your_app.js# 生产模式不传参数零开销nodeyour_app.js直接使用# 开启并指定只追踪异步钩子和文件系统 node --trace-event-categories node.async_hooks,node.fs app.js # 生成 node_trace.1.log生成的log如下{ traceEvents: [ { pid: 45268, tid: 41240, ts: 660125182515, tts: 0, ph: B, cat: node,node.fs,node.fs.sync, name: fs.sync.lstat, dur: 0, tdur: 0, args: {} }, { pid: 45268, tid: 41240, ts: 660125182564, tts: 0, ph: E, cat: node,node.fs,node.fs.sync, name: fs.sync.lstat, dur: 0, tdur: 0, args: {} },....]打开chrome://tracing ,把log 拖进去emm 这个不是给人看的是给机器看的。最后再处理。更多文章敬请关注gzh零基础爬虫第一天