NodeJs服务链路追踪日志

news2024/11/27 16:52:57
(逆境给人宝贵的磨炼机会。仅有经得起环境考验的人,才能算是真正的强者。自古以来的伟人,大多是抱着不屈不挠的精神,从逆境中挣扎奋斗过来的。——松下幸之助)

在这里插入图片描述

服务链路追踪

服务的链路追踪指我们可以通过一个标记,快速排查整个业务从开始到结束的过程。快速定位整个业务环节的信息,异常等。
常见的业务场景是前后端通信,后端服务间通信。
链路id业界通常称为TraceId

前后端通信

场景一

前端每次对后端请求前都先生成"request-id",统一前后端的链路追踪id,前端可以用此id来做自己业务的链路追踪,后端也可以直接在请求头headers中取出使用。
优点

  • 前后端链路追踪id统一,方便排查业务问题
  • 不用后端生成链路id,减少业务性能损耗

缺点

  • 链路id由前端产生,存在安全问题
  • 前后端业务耦合度过高
场景二

前端不维护前端日志或没有日志的情况下,链路id通常由后端自己管理,在接收请求之前,利用多线程特性,将链路id存入线程上下文并在后续业务中获取。
优点

  • 链路id由后端管理,与前端解耦

缺点

  • 无法和前端统一链路id,增加前后端统一排查问题的难度

服务间通信

后端多服务通信或第三方通信时,为了快速定位问题,往往需要统一多服务间日志的链路id,假如A->B->C,A服务将生成的traceId放在请求头中交给B服务,B服务再获取请求头中的id进行业务处理,C服务也进行相同的处理,那么三个服务的日志id相同,就能够在日志排查中大大降低排查难度

NodeJs服务链路追踪的难点

对于java等多线程服务来说,每个线程有自己的上下文可以用来做链路追踪,但nodejs主线程是单线程,无法针对每个请求做多线程处理。
在过去,node服务的链路追踪通常会做类似koa框架的ctx上下文变量,通过每一层的传递来维护上下文,达到链路追踪的目的。但这样的做法有一些冗余,使代码不得不变得臃肿,难以维护。
所以在nodejs后续版本中提供了async_hooks异步函数钩子,可以在一个请求链路中保存上下文,让链路追踪成为可能

最佳实践

使用nestjs服务,通过三种不同的方式来演示链路追踪

  • async_hooks
    NodeJs官方
  • cls-rtracer
    cls-rtracer最佳实践
    链路追踪性能
  • nestjs-cls
    nestjs官方
async_hooks

async_hooks日志打印,使用懒加载的形式,提供静态方法打印

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

相比较原生的async_hooks,该组件更符合中间件加载模式,代码侵入性更小
提供了两种组件,TraceIdMiddleware使用了自动生成的uuid作为traceId,TraceIdRandomMiddleware使用自定义的随机数作为traceId。
这里为了演示使用了随机数,实际业务中通常是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

针对nestjs做了优化,在模块化的基础上,做了ts检查和其他定制化的api。其中userId是使用了自定义的traceId。在包引入时进行初始化,在下面会提到。

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}`);
  }

}
控制层
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();
  }

}

业务层
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,即将处理业务');
  }
}

模块加载

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 { }

运行结果

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,即将处理业务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/577300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云ECS部署Trojan代理

项目地址&#xff1a;GitHub - Jrohy/trojan: trojan多用户管理部署程序, 支持web页面管理 一、容器部署&#xff1a; docker run --name trojan-mariadb --restartalways -p 3306:3306 -v /home/mariadb:/var/lib/mysql -e MYSQL_ROOT_PASSWORDtrojan -e MYSQL_ROOT_HOST% -e…

在滴滴和字节跳动干了 2 年,太真实…

先简单交代一下背景吧&#xff0c;某不知名985的本硕&#xff0c;17年毕业加入滴滴&#xff0c;之后跳槽到了头条&#xff0c;一直从事软件测试相关的工作。之前没有实习经历&#xff0c;算是两年半的工作经验吧。 这两年半之间完成了一次晋升&#xff0c;换了一家公司&#x…

Linux 软件安装及vim详细用法和配置

文章目录 一、Linux下的软件1、什么是软件包&#xff1f;2、软件安装的三种方法3、yum 安装 lrzsz软件&#xff08;windows和Linux消息互传&#xff09;4、深入理解yum源 二、 L i n u x 编辑器 − v i m 使用 Linux编辑器-vim使用 Linux编辑器−vim使用1、vim三种模式作用及其…

递归之谜:解析无限嵌套的美

一、前言 嵌套是指在一个事物中包含另一个事物&#xff0c;而递归是一种特殊形式的嵌套&#xff0c;其中一个事物包含自身。 递归就是一种嵌套的形式&#xff0c;递归函数解决问题时嵌套调用自身。递归的核心思想是通过反复应用相同的过程来解决问题&#xff0c;每一次调用都…

容器化:MongoDB

1 缘起 开启容器化之路。 2 容器化MongDB 2.1 查看镜像 docker search mongodb2.2 安装 前台安装 sudo docker run \ --name mongodb \ -p 27017:27017 \ -v /home/xindaqi/mongodb/conf:/data/configdb \ -v /home/xindaqi/data/mongodb-data:/data/db \ -v /home/xind…

99年表示真干不过,部门新来的00后测试员已把我卷崩溃,想离职了...

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&#x…

常见的GPIO口框架分析

目录 1、单片机平台 2、嵌入式 Linux 平台 GPIO 八种工作模式详解 接着上一篇的讲&#xff0c;我们上一篇研究了 GPIO 的硬件结构&#xff0c;其来源于 STM32 官方手册&#xff0c;研究了 GPIO 的八种工作模式和推挽输出及开漏输出原理&#xff0c;接下来我们研究 GPIO 的软件…

孙燕姿谈“AI孙燕姿”:她的反应让人意外,深入体验揭示其背后的真相与潜力!

目录 前言AI歌手简介AI歌手的技术原理孙燕姿对“AI孙燕姿”的看法结论个人感受一、你听过AI歌手的音乐呈现吗&#xff1f;作为听众你的感受如何&#xff1f;二、你认为这种新型演艺模式能否获得广泛的市场认可&#xff1f;原因是什么&#xff1f;三、你认为AI歌手会取代流行歌手…

SQL查询语言(3) 嵌套查询

如果不进行去重可能会出现一个情况 嵌套查询根据子查询的结果是否依赖于外层循环,分成相关子查询和不相关子查询 分类 IN 笔者总结&#xff1a;一般这种方法适用于查找有共性的元组&#xff0c;同一类事物比如查找和elsa选修相同科目的学生/选修相同科目的女同学。在后面我…

【随手查】数据手册研读笔记

一个付费课程的学习之旅&#xff0c;将课程中所学到的东西以及实践中学到的悟到的记录下来&#xff0c;方便日后查看&#xff0c;持续更。。。 笔记目录 一、电阻1、贴片电阻表面的阻值标记2、额定功率下降曲线3、贴片电阻的温度系数 二、电容1、电容值的计算公式2、ESR曲线3、…

JVM Sandbox入门详解

一. 概述 在日常开发中&#xff0c;经常会接触到面向AOP编程的思想&#xff0c;我们通常会使用Spring AOP来做统一的权限认证、异常捕获返回、日志记录等工作。之所以使用Spring AOP来实现上述功能&#xff0c;是因为这些场景本质上来说都是与业务场景挂钩的&#xff0c;但是具…

http请求和响应(包含状态码)+过滤器

目录 一、http协议概述 二、http请求 三、http响应 四、过滤器 一、http协议概述 1.http&#xff1a;超文本传输协议&#xff0c;是用于在网络上传输数据的应用层协议。是互联网上应用最为流行的一种网络协议,用于定义客户端浏览器和服务器之间交换数据的过程&#xff0c;基…

软考A计划-试题模拟含答案解析-卷二

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

当我按下开关,震惊了一众答辩老师,乍一看,啊,就这?

基于机器视觉的爬行机器人&#xff08;毕业设计&#xff09; 零、实现功能一、关于本想法二、理论分析三、3D结构设计四、硬件设计主控板驱动板 五、软件设计控制程序机器视觉APP设计 六、结束语 零、实现功能 实现了爬行机器人的移动控制功能。采用三角步态控制机器人移动&am…

【技术分享】万字长文图文并茂读懂高性能无锁 “B-Tree 改”:Bw-Tree

【技术分享】万字长文图文并茂读懂高性能无锁 “B-Tree 改”&#xff1a;Bw-Tree 原文链接&#xff1a; https://mp.weixin.qq.com/s/I5TphQP__tHn6JoPcP–_w 参考文献可能需要科学上网才能下载。如果你获取不到这几篇论文&#xff0c;可以关注公众号 IT技术小密圈 回复 bw-tre…

类和对象初阶

目录 一、再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1.3 注意 1.4 总结 二、拷贝对象时的一些编译器优化 三、static成员 3.1 静态成员变量 3.1.1 引入 3.1.2 特点 3.1.3 区别 3.2 静态成员函数 3.2.1 引入 3.2.2 特点 3.2.3 例题 四、友元 4.1 友元函…

数据结构与算法·第2章【线性表】

线性结构具有以下基本特征&#xff1a; 有唯一的一个被称为首元素&#xff08;或头元素&#xff09;的元素&#xff0c;没有直接前驱&#xff1b;有唯一的一个被称为尾元素&#xff08;或尾节点&#xff09;的元素&#xff0c;没有直接后继。 数据元素之间存在一对一的线性关…

python 实现单链表

链表 链表是一种在存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现。 链表是由一系列的结点组成&#xff0c;结点可以在运行时动态生成。每个结点包含两部分&#xff1a;数据域与指针域。数据域存储数据元素&#xff0c;指针域存储下一…

Yapi内网部署[CentOS7]

mongo安装 # 下载MongoDB https://www.mongodb.com/try/download/community4.2.24 RedHat/CentOS7.0 tgz# 安装MongoDB mkdir -p /home/jpge/devp-tools/tools cd /home/jpge/devp-tools/tools wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.2.24.tgz…