Node绑定全局TraceID的实现方法

2020-06-17 05:35:09易采站长站整理

问题描述

由于Node.js的 单线程模型 的限制,我们无法设置全局 traceid 来聚合请求,即 实现输出日志与请求的绑定 。如果不实现日志和请求的绑定,我们难以判断日志输出与对应用户请求的对应关系,这对 线上问题排查 带来了困难。

例如,在用户访问 retrieveOne API 时,其会调用 retrieveOneSub 函数,如果我们想在 retrieveOneSub 函数中输出当前请求对应的学生信息,是繁琐的。在 course-se 现有实现下,我们针对此问题的解决方法是:

方案1:在调用 retrieveOneSub 函数的父函数,即 retrieveOne 内,对 paramData 进行 解构 ,输出学生相关信息,但该方案 无法细化日志输出粒度 。
方案2:修改 retrieveOneSub 函数签名,接收 paramData 为其参数,该方案 能确保日志输出粒度 ,但 在调用链很深的情况下,需要给各函数修改函数签名 ,使其接收 paramData ,颇具工作量,并不太可行。


/**
* 返回获取一份提交的函数
* @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 相关信息,如 user_id ,
// 但无法细化日志输出粒度,除非修改 retrieveOneSub 的签名,
// 添加 paramData 为其参数。
const { user_id } = paramData.user;
console.log(`${user_id} is trying to retreive one submission.`);
// 调用了 retrieveOneSub 函数。
const sub = await retrieveOneSub(sub_asgn_id, subModel);
const submission = sub;
assign(sub, { sub_asgn_id });
assign(paramData, { submission, sub });
return sub;
}

/**
* 从数据库获取一份提交
* @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

其实,针对以上的问题,我们还可以从 Node 的 Async Hooks 实验性 API 方面入手。在 Node.js v8.x 后,官方提供了可用于 监听异步行为 的 Async Hooks(异步钩子)API 的支持。

Async Scope

Async Hooks 对每一个(同步或异步)函数提供了一个 Async Scope ,我们可调用 executionAsyncId 方法获取当前函数的 Async ID ,调用 triggerAsyncId 获取当前函数调用者的 Async ID。


const asyncHooks = require("async_hooks");
const { executionAsyncId, triggerAsyncId } = asyncHooks;