您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)怎么在Node中綁定全局TraceID,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
方案1:在調(diào)用 retrieveOneSub 函數(shù)的父函數(shù),即 retrieveOne 內(nèi),對(duì) paramData 進(jìn)行 解構(gòu) ,輸出學(xué)生相關(guān)信息,但該方案 無法細(xì)化日志輸出粒度 。
方案2:修改 retrieveOneSub 函數(shù)簽名,接收 paramData 為其參數(shù),該方案 能確保日志輸出粒度 ,但 在調(diào)用鏈很深的情況下,需要給各函數(shù)修改函數(shù)簽名 ,使其接收 paramData ,頗具工作量,并不太可行。
/** * 返回獲取一份提交的函數(shù) * @param {ParamData} paramData * @param {Context} ctx * @param {string} id */ export async function retrieveOne(paramData, ctx, id) { const { subModel } = paramData.ce; const sub_asgn_id = Number(id); // 通過 paramData.user 獲取 user 相關(guān)信息,如 user_id , // 但無法細(xì)化日志輸出粒度,除非修改 retrieveOneSub 的簽名, // 添加 paramData 為其參數(shù)。 const { user_id } = paramData.user; console.log(`${user_id} is trying to retreive one submission.`); // 調(diào)用了 retrieveOneSub 函數(shù)。 const sub = await retrieveOneSub(sub_asgn_id, subModel); const submission = sub; assign(sub, { sub_asgn_id }); assign(paramData, { submission, sub }); return sub; } /** * 從數(shù)據(jù)庫(kù)獲取一份提交 * @param {number} sub_asgn_id * @param {SubModel} model */ async function retrieveOneSub(sub_asgn_id, model) { const [sub] = await model.findById(sub_asgn_id); if (!sub) { throw new ME.SoftError(ME.NOT_FOUND, '找不到該提交'); } return sub; }
Async Hooks
其實(shí),針對(duì)以上的問題,我們還可以從 Node 的 Async Hooks 實(shí)驗(yàn)性 API 方面入手。在 Node.js v8.x 后,官方提供了可用于 監(jiān)聽異步行為 的 Async Hooks(異步鉤子)API 的支持。
Async Scope
Async Hooks 對(duì)每一個(gè)(同步或異步)函數(shù)提供了一個(gè) Async Scope ,我們可調(diào)用 executionAsyncId 方法獲取當(dāng)前函數(shù)的 Async ID ,調(diào)用 triggerAsyncId 獲取當(dāng)前函數(shù)調(diào)用者的 Async ID。
const asyncHooks = require("async_hooks"); const { executionAsyncId, triggerAsyncId } = asyncHooks; console.log(`top level: ${executionAsyncId()} ${triggerAsyncId()}`); const f = () => { console.log(`f: ${executionAsyncId()} ${triggerAsyncId()}`); }; f(); const g = () => { console.log(`setTimeout: ${executionAsyncId()} ${triggerAsyncId()}`); setTimeout(() => { console.log(`inner setTimeout: ${executionAsyncId()} ${triggerAsyncId()}`); }, 0); }; setTimeout(g, 0); setTimeout(g, 0);
在上述代碼中,我們使用 setTimeout 模擬一個(gè)異步調(diào)用過程,且在該異步過程中我們調(diào)用了 handler 同步函數(shù),我們?cè)诿總€(gè)函數(shù)內(nèi)都輸出其對(duì)應(yīng)的 Async ID 和 Trigger Async ID 。執(zhí)行上述代碼后,其運(yùn)行結(jié)果如下。
top level: 1 0 f: 1 0 setTimeout: 7 1 setTimeout: 9 1 inner setTimeout: 11 7 inner setTimeout: 13 9
通過上述日志輸出,我們得出以下信息:
調(diào)用同步函數(shù),不會(huì)改變其 Async ID ,如函數(shù) f 內(nèi)的 Async ID 和其調(diào)用者的 Async ID 相同。
同一個(gè)函數(shù),被不同時(shí)刻進(jìn)行異步調(diào)用,會(huì)分配至不同的 Async ID ,如上述代碼中的 g 函數(shù)。
追蹤異步資源
正如我們前面所說的,Async Hooks 可用于追蹤異步資源。為了實(shí)現(xiàn)此目的,我們需要了解 Async Hooks 的相關(guān) API ,具體說明參照以下代碼中的注釋。
const asyncHooks = require("async_hooks"); // 創(chuàng)建一個(gè) AsyncHooks 實(shí)例。 const hooks = asyncHooks.createHook({ // 對(duì)象構(gòu)造時(shí)會(huì)觸發(fā) init 事件。 init: function(asyncId, type, triggerId, resource) {}, // 在執(zhí)行回調(diào)前會(huì)觸發(fā) before 事件。 before: function(asyncId) {}, // 在執(zhí)行回調(diào)后會(huì)觸發(fā) after 事件。 after: function(asyncId) {}, // 在銷毀對(duì)象后會(huì)觸發(fā) destroy 事件。 destroy: function(asyncId) {} }); // 允許該實(shí)例中對(duì)異步函數(shù)啟用 hooks 。 hooks.enable(); // 關(guān)閉對(duì)異步資源的追蹤。 hooks.disable();
我們?cè)谡{(diào)用 createHook 時(shí),可注入 init 、 before 、 after 和 destroy 函數(shù),用于 追蹤異步資源的不同生命周期 。
全新解決方案
基于 Async Hooks API ,我們即可設(shè)計(jì)以下解決方案,實(shí)現(xiàn)日志與請(qǐng)求記錄的綁定,即 Trace ID 的全局綁定。
const asyncHooks = require("async_hooks"); const { executionAsyncId } = asyncHooks; // 保存異步調(diào)用的上下文。 const contexts = {}; const hooks = asyncHooks.createHook({ // 對(duì)象構(gòu)造時(shí)會(huì)觸發(fā) init 事件。 init: function(asyncId, type, triggerId, resource) { // triggerId 即為當(dāng)前函數(shù)的調(diào)用者的 asyncId 。 if (contexts[triggerId]) { // 設(shè)置當(dāng)前函數(shù)的異步上下文與調(diào)用者的異步上下文一致。 contexts[asyncId] = contexts[triggerId]; } }, // 在銷毀對(duì)象后會(huì)觸發(fā) destroy 事件。 destroy: function(asyncId) { if (!contexts[asyncId]) return; // 銷毀當(dāng)前異步上下文。 delete contexts[asyncId]; } }); // 關(guān)鍵!允許該實(shí)例中對(duì)異步函數(shù)啟用 hooks 。 hooks.enable(); // 模擬業(yè)務(wù)處理函數(shù)。 function handler(params) { // 設(shè)置 context ,可在中間件中完成此操作(如 Logger Middleware)。 contexts[executionAsyncId()] = params; // 以下是業(yè)務(wù)邏輯。 console.log(`handler ${JSON.stringify(params)}`); f(); } function f() { setTimeout(() => { // 輸出所屬異步過程的 params 。 console.log(`setTimeout ${JSON.stringify(contexts[executionAsyncId()])}`); }); } // 模擬兩個(gè)異步過程(兩個(gè)請(qǐng)求)。 setTimeout(handler, 0, { id: 0 }); setTimeout(handler, 0, { id: 1 });
在上述代碼中,我們先聲明了 contexts 用于存儲(chǔ)每個(gè)異步過程中的上下文數(shù)據(jù)(如 Trace ID),隨后我們創(chuàng)建了一個(gè) Async Hooks 實(shí)例。我們?cè)诋惒劫Y源初始化時(shí),設(shè)置當(dāng)前 Async ID 對(duì)應(yīng)的上下文數(shù)據(jù),使得其數(shù)據(jù)為調(diào)用者的上下文數(shù)據(jù);我們?cè)诋惒劫Y源被銷毀時(shí),刪除其對(duì)應(yīng)的上下文數(shù)據(jù)。
通過這種方式,我們只需在一開始設(shè)置上下文數(shù)據(jù),即可在其引發(fā)的各個(gè)過程(同步和異步過程)中,獲得上下文數(shù)據(jù),從而解決了問題。
執(zhí)行上述代碼,其運(yùn)行結(jié)果如下。根據(jù)輸出日志可知,我們的解決方案是可行的。
handler {"id":0} handler {"id":1} setTimeout {"id":0} setTimeout {"id":1}
以上就是怎么在Node中綁定全局TraceID,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。