NodeJs service link tracking log

(Adversity gives people precious opportunities for tempering. Only those who can stand the test of the environment can be regarded as the real strong. Since ancient times, most of the great men have struggled from adversity with an indomitable spirit. ——Panasonic Fortunately)

insert image description here

Service Link Tracking

Service link tracking means that we can quickly check the entire business process from start to finish through a mark. Quickly locate information and exceptions in the entire business link.
Common business scenarios are front-end and back-end communication, and communication between back-end services.
The link id industry is usually calledTraceId

Front-end and back-end communication

scene one

The front-end generates a "request-id" before each request to the back-end to unify the link tracking id of the front-end and back-end. The front-end can use this id to track the link of its own business, and the back-end can also directly retrieve it from the request headers. use.
advantage

  • The front-end and back-end link tracking ids are unified, making it easy to troubleshoot business problems
  • No need to generate link id at the back end, reducing business performance loss

shortcoming

  • The link id is generated by the front end, and there are security issues
  • Front-end and back-end business coupling is too high
scene two

When the front-end does not maintain the front-end log or there is no log, the link id is usually managed by the back-end itself. Before receiving the request, the multi-thread feature is used to store the link id into the thread context and obtain it in the subsequent business.
advantage

  • The link id is managed by the backend and decoupled from the frontend

shortcoming

  • It is impossible to unify the link id with the front-end, which increases the difficulty of unified troubleshooting of the front-end and front-end

Inter-service communication

In back-end multi-service communication or third-party communication, in order to quickly locate the problem, it is often necessary to unify the link id of the logs between multiple services. If A->B->C, A service will put the generated traceId in the request header and hand it over For service B, service B obtains the id in the request header for business processing, and service C also performs the same processing, so the log ids of the three services are the same, which can greatly reduce the difficulty of troubleshooting in log troubleshooting

Difficulties in NodeJs service link tracking

For multi-threaded services such as java, each thread has its own context that can be used for link tracking, but the main thread of nodejs is single-threaded, and cannot perform multi-threaded processing for each request.
In the past, the link tracking of the node service usually made the ctx context variable similar to the koa framework, and maintained the context through the transmission of each layer to achieve the purpose of link tracking. But this approach has some redundancy, which makes the code bloated and difficult to maintain.
Therefore, async_hooks asynchronous function hooks are provided in subsequent versions of nodejs, which can save the context in a request link and make link tracking possible

Best Practices

Use the nestjs service to demonstrate link tracking in three different ways

async_hooks

async_hooks log printing, using lazy loading form, providing static method printing

import {
    
     AsyncLocalStorage } from "async_hooks";
import * as dayjs from "dayjs";
let asyncLocalStorage: AsyncLocalStorage<unknown>;

export class LogAsyncLocalStorage {
    
    

  static init (next) {
    
    
    this.getInstance().run(Math.random(), () => next());
  }

  static getInstance () {
    
    
    if (!asyncLocalStorage) {
    
    
      asyncLocalStorage = new AsyncLocalStorage();
    }
    return asyncLocalStorage;
  }

  static info (message: string) {
    
    
    const nowTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
    const requlestId = asyncLocalStorage.getStore();
    console.log(`requestId:${
      
      requlestId} time:${
      
      nowTime} info:${
      
      message}`);
  }

}
cls-rtracer

Compared with native async_hooks, this component is more in line with the middleware loading mode, and the code is less intrusive.
Two components are provided. TraceIdMiddleware uses an automatically generated uuid as traceId, and TraceIdRandomMiddleware uses a custom random number as traceId.
Random numbers are used here for demonstration purposes. In actual business, they are usually unique strings or numbers such as uuid

import * as rTracer from 'cls-rtracer';
export const TraceIdMiddleware = rTracer.expressMiddleware({
    
    
  useHeader: true,
  headerName: 'traceId',
});
export const TraceIdRandomMiddleware = rTracer.expressMiddleware({
    
    
  requestIdFactory: () => Math.random(),
});

nestjs-cls

It is optimized for nestjs, and on the basis of modularization, ts inspection and other customized apis are done. Among them, userId is a custom traceId. It is initialized when the package is imported, which will be mentioned below.

import {
    
     Injectable } from "@nestjs/common/decorators";
import * as dayjs from "dayjs";
import {
    
     ClsService } from "nestjs-cls/dist/src/lib/cls.service";

@Injectable()
export class LogNestCls {
    
    

  constructor(
    private readonly clsService: ClsService
  ) {
    
     }

  info (message: string) {
    
    
    const nowTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
    const requlestId = this.clsService.get('userId');
    console.log(`requestId:${
      
      requlestId} time:${
      
      nowTime} info:${
      
      message}`);
  }

}
control layer
import {
    
     Controller, Get } from '@nestjs/common';
import {
    
     AppService } from './app.service';
import {
    
     LogAsyncLocalStorage } from './log.asyncLocalStorage';
import {
    
     LogNestCls } from './log.nest.cls';
import {
    
     LogRtracer } from './log.rTracer';

@Controller()
export class AppController {
    
    
  constructor(
    private readonly appService: AppService,
    private readonly LogNestCls: LogNestCls
  ) {
    
     }

  @Get('asyncLocalStorage')
  getHeeloAsyncLocalStorage () {
    
    
    LogAsyncLocalStorage.info('收到前端请求,即将进入service');
    return this.appService.getHeeloAsyncLocalStorage();
  }

  @Get('rTracer')
  getRTracer () {
    
    
    LogRtracer.info('收到前端请求,即将进入service');
    return this.appService.getRTracer();
  }

  @Get('nestCliId')
  getNestCli () {
    
    
    this.LogNestCls.info('收到前端请求,即将进入service');
    return this.appService.getNestCli();
  }

}

Business Layer
import {
    
     Injectable } from "@nestjs/common";
import {
    
     LogRtracer } from "./log.rTracer";
import {
    
     LogAsyncLocalStorage } from './log.asyncLocalStorage';
import {
    
     LogNestCls } from "./log.nest.cls";

@Injectable()
export class AppService {
    
    

  constructor(
    private readonly LogNestCls: LogNestCls
  ) {
    
     }

  getRTracer () {
    
    
    LogRtracer.info('getHello 进入service,即将处理业务');
  }
  getHeeloAsyncLocalStorage () {
    
    
    LogAsyncLocalStorage.info('getHeeloAsyncLocalStorage 进入service,即将处理业务');
  }
  getNestCli () {
    
    
    this.LogNestCls.info('getNestCli 进入service,即将处理业务');
  }
}

module loading

async_hooks
import {
    
     MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import {
    
     AppController } from "./app.controller";
import {
    
     AppService } from "./app.service";
import {
    
     LogAsyncLocalStorage } from "./log.asyncLocalStorage";

@Module({
    
    
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule implements NestModule {
    
    
  configure (consumer: MiddlewareConsumer) {
    
    
  consumer
  .apply((req, res, next) => LogAsyncLocalStorage.init(next))
  .forRoutes('*')
  }
}

cls-rtracer
import {
    
     MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import {
    
     AppController } from "./app.controller";
import {
    
     AppService } from "./app.service";
import {
    
     TraceIdMiddleware } from "./rTracer";

@Module({
    
    
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule implements NestModule {
    
    
  configure (consumer: MiddlewareConsumer) {
    
    
  consumer
  .apply(TraceIdMiddleware)
  // .apply(TraceIdRandomMiddleware)
  .forRoutes('*')
  }
}

nestjs-cls
import {
    
      Module } from "@nestjs/common";
import {
    
     AppController } from "./app.controller";
import {
    
     AppService } from "./app.service";
import {
    
     ClsModule } from "nestjs-cls";
import {
    
     LogNestCls } from "./log.nest.cls";

@Module({
    
    
  imports: [
    ClsModule.forRoot({
    
    
      global: true,
      middleware: {
    
    
        mount: true,
        setup: (cls, req) => {
    
    
          cls.set('userId', Math.random());
        }
      }
    })
  ],
  controllers: [AppController],
  providers: [AppService, LogNestCls],
})
export class AppModule {
    
     }

operation result

async_hooks
requestId:0.8233251518769558 time:2023-05-27 22:40:27 info:收到前端
请求,即将进入service
requestId:0.8233251518769558 time:2023-05-27 22:40:27 info:getHeeloAsyncLocalStorage 进入service,即将处理业务
cls-rtracer
requestId:354b79f0-fc9c-11ed-beac-f57b0395da38 time:2023-05-27 22:38:55 info:收到前端请求,即将进入service
requestId:354b79f0-fc9c-11ed-beac-f57b0395da38 time:2023-05-27 22:38:55 info:getHello 进入service,即将处理业务
nestjs-cls
requestId:0.43237919752342924 time:2023-05-27 22:41:04 info:收到前端请求,即将进入service
requestId:0.43237919752342924 time:2023-05-27 22:41:04 info:getNestCli 进入service,即将处理业务

Guess you like

Origin blog.csdn.net/qq_42427109/article/details/130906290
Recommended