利用async_hooks实现请求生命周期的数据共享
nodejsasync_hooks

在项目中我们经常会日志打印出来,但是,由于请求一般的异步或者多线程的。N个请求生命周期中的日志会杂乱的显示在一起,我们很难判断出某个日志是同一请求的。这个时候,我们需求用到traceIdl来标记。在java中,我们会利用slf4j的MDC记录traceId。那么,在nodejs中,我们要怎么做呢?

我们都知道,nodejs在v10.5.0之后才增加了对多线程的支持。正常情况下,我们还是用单线程去执行的,然后,内部执行是异步的。那么,既然是异步的,我们就不可能通过类似Java的ThreadLocal的方案去做。那么,我们只能通过参数传递发方式去把ctx传递到需要的地方。

在node8.2中新增了async_hooks模块,虽然好像该api一直是试验阶段,但是,目前看来,还是可行的。

官方介绍async_hooks是这么用的

const asyncHook = require('async_hooks');

const hook = asyncHooks.createHook({
    init(asyncId, type, triggerAsyncId, resource) {
    },
    before(asyncId) {
    },
    after(asyncId) {
    },
    destroy(asyncId) {
    }
});

hook.enable();





在实际使用的时候,我们更多的是用init和destory就够了。下面,我们就以日志的traceId为例,写一个demo

1.AsyncHookTrace.js

const asyncHooks = require("async_hooks");
const traceStore = new Map();

const asyncHook = asyncHooks.createHook({
    init(asyncId,type,triggerAsyncId,resource){
        const parentTrace = traceStore.get(triggerAsyncId);
        if(parentTrace){
            traceStore.set(asyncId,parentTrace);
        }
    },
    destroy(asyncId) {
        traceStore.delete(asyncId);
    }
});
asyncHook.enable();

class AsyncHookTrace {

    static setTrace(trace){
        traceStore.set(asyncHooks.executionAsyncId(),trace);
    }
    static getTrace(){
        return traceStore.get(asyncHooks.executionAsyncId());
    }
}
export default AsyncHookTrace



2.写一个middleware去赋值,这个中间件建议放在route中,因为,koa的项目有很多的中间件,而中间件又都是异步的,所以会给traceStore塞很多值。

import uuid from "uuid/v4";
import AsyncHookTrace from "../../async_hooks/AsyncHookTrace";

function trace(root,ops) {

    return async function trace(ctx,next){
        try {
            AsyncHookTrace.setTrace(uuid());
            await next();
        }catch (e) {
            throw e
        }
    }

}
export default trace



3.logger中格式化

import moment from "moment";
import appConfig from "./AppConfig";
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, printf } = format;

const myFormat = printf(({ level, message, label, timestamp }) => {
    let tradeId = AsyncHookTrace.getTrace();
    return `[${moment(timestamp).format("YYYY-MM-DD hh:mm:ss")}] [${tradeId}] : ${message}`;
});

const logger = createLogger({
    level: appConfig.get("logger.level"),
    format: combine(
        timestamp(),
        myFormat
    ),
    transports: [new transports.Console()]
});
export default logger;



运行结果如下:

[2019-11-27 11:09:34] [02e04e04-d9a4-4630-ba18-daca8f702f8a] : getaddrinfo ENOTFOUND localhost localhost:8300



traceId打印成功

暂无评论