Nest.js 实战 (十):使用 winston 打印和收集日志记录

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 这篇文章介绍了在Nest服务中如何使用Winston记录日志。文章首先强调了日志记录在后台服务中的重要性,接着提到Nest默认的内部日志记录器,并指出可以通过@nestjs/common包中的Logger类来全面控制日志系统的行为。文章还提到,为了在生产环境中实现更高级的日志功能,可以使用如Winston之类的Node.js日志包。接下来,文章介绍了如何在Nest服务中使用Winston记录日志,包括安装相关依赖、创建winston配置文件以及实现简单的日志记录示例。最后,文章指出更高级的自定义日志功能需要读者自己去探索。

前言

日志记录在后台服务的重要性不言而喻,它可以帮助开发者调试和故障排查性能监控审计和安全监控和警报等。

Nest 附带一个默认的内部日志记录器实现,它在实例化过程中以及在一些不同的情况下使用,比如发生异常等等(例如系统记录)。这由 @nestjs/common 包中的 Logger 类实现。你可以全面控制如下的日志系统的行为:

  1. 完全禁用日志
  2. 指定日志系统详细水平(例如,展示错误,警告,调试信息等)
  3. 覆盖默认日志记录器的时间戳(例如使用 ISO8601 标准作为日期格式)
  4. 完全覆盖默认日志记录器
  5. 通过扩展自定义默认日志记录器
  6. 使用依赖注入来简化编写和测试你的应用

更多高级的日志功能,可以使用任何 Node.js 日志包,比如Winston,来生成一个完全自定义的生产环境水平的日志系统。

今天我们就看看在 Nest 服务中应该如何使用 Winston 记录日志。

Nest 控制台

我们先看一下 Nest 服务原生的控制台输出:
trl1r148puvqjeeeyydn8yc451zc98jr.png
在接口请求和执行 SQL 的时候,控制台并没有相应的输出信息,这不方便我们排查和调试。

我们需要在服务执行操作的时候,控制台应该输出信息:

  1. 执行 SQL 时,打印 SQL 日志
  2. 调用接口时,打印接口请求日志
  3. 将接口调用时的日志生成保存到指定文件夹中

打印 Prisma 日志

由于我的项目是使用 Prisma 客户端,按照官网文档配置日志记录

PrismaService 中配置:

import {
   
    Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import {
   
    PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
   
   
  constructor() {
   
   
    super({
   
   
      log: ['query', 'info', 'warn', 'error'], // 这里设置日志级别
    });
  }
  async onModuleInit() {
   
   
    await this.$connect(); // 在模块初始化时连接到数据库
  }

  async onModuleDestroy() {
   
   
    await this.$disconnect(); // 在应用程序关闭时断开与数据库的连
  }
}

在执行 SQL 时,控制台就会输出信息:
6cuibjxlbn3qpwmhgtnjtudhgu15udm9.png

接口请求日志

Nest 内部自带了 Logger 类,我们创建一个日志中间件:

import {
   
    Injectable, Logger, NestMiddleware } from '@nestjs/common';
import dayjs from 'dayjs';
import {
   
    NextFunction, Request, Response } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
   
   
  private logger = new Logger();
  use(req: Request, res: Response, next: NextFunction) {
   
   
    // 记录开始时间
    const start = Date.now();
    // 获取请求信息
    const {
   
    method, originalUrl, ip, httpVersion, headers } = req;

    // 获取响应信息
    const {
   
    statusCode } = res;

    res.on('finish', () => {
   
   
      // 记录结束时间
      const end = Date.now();
      // 计算时间差
      const duration = end - start;

      // 这里可以根据自己需要组装日志信息:[timestamp] [method] [url] HTTP/[httpVersion] [client IP] [status code] [response time]ms [user-agent]
      const logFormat = `${
     
     dayjs().valueOf()} ${
     
     method} ${
     
     originalUrl} HTTP/${
     
     httpVersion} ${
     
     ip} ${
     
     statusCode} ${
     
     duration}ms ${
     
     headers['user-agent']}`;

      // 根据状态码,进行日志类型区分
      if (statusCode >= 500) {
   
   
        this.logger.error(logFormat, originalUrl);
      } else if (statusCode >= 400) {
   
   
        this.logger.warn(logFormat, originalUrl);
      } else {
   
   
        this.logger.log(logFormat, originalUrl);
      }
    });

    next();
  }
}

AppModule 中全局注册:

import {
   
    MiddlewareConsumer, Module, NestModule } from '@nestjs/common';

import {
   
    LoggerMiddleware } from '@/middleware/logger.middleware'; // 全局日志中间件

@Module({
   
   
  imports: [],
})
export class AppModule implements NestModule {
   
   
  configure(consumer: MiddlewareConsumer) {
   
   
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}

在接口调用时,控制台就会输出信息:
7i5fk6lco9vsl1m1jfm5e4qstqlsxd09.png

Winston 生成日志

我们需要安装几个依赖:

  1. winston:一个通用的日志记录库,为 Node.js 应用提供灵活的日志记录功能
  2. nest-winston: 一个用于 winstonNest 模块包装器
  3. winston-daily-rotate-file: 用于将日志文件按天轮换保存
  4. chalk: 用于在终端中输出带有颜色的文本

终端执行命令:

pnpm add winston nest-winston winston-daily-rotate-file chalk@4

新建 winston 配置文件:

import chalk from 'chalk'; // 用于颜色化输出
import {
   
    createLogger, format, transports } from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';

// 定义日志级别颜色
const levelsColors = {
   
   
  error: 'red',
  warn: 'yellow',
  info: 'green',
  debug: 'blue',
  verbose: 'cyan',
};

const winstonLogger = createLogger({
   
   
  format: format.combine(format.timestamp(), format.errors({
   
    stack: true }), format.splat(), format.json()),
  defaultMeta: {
   
    service: 'log-service' },
  transports: [
    new DailyRotateFile({
   
   
      filename: 'logs/errors/error-%DATE%.log', // 日志名称,占位符 %DATE% 取值为 datePattern 值。
      datePattern: 'YYYY-MM-DD', // 日志轮换的频率,此处表示每天。
      zippedArchive: true, // 是否通过压缩的方式归档被轮换的日志文件。
      maxSize: '20m', // 设置日志文件的最大大小,m 表示 mb 。
      maxFiles: '14d', // 保留日志文件的最大天数,此处表示自动删除超过 14 天的日志文件。
      level: 'error', // 日志类型,此处表示只记录错误日志。
    }),
    new DailyRotateFile({
   
   
      filename: 'logs/warnings/warning-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      zippedArchive: true,
      maxSize: '20m',
      maxFiles: '14d',
      level: 'warn',
    }),
    new DailyRotateFile({
   
   
      filename: 'logs/app/app-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      zippedArchive: true,
      maxSize: '20m',
      maxFiles: '14d',
    }),
    new transports.Console({
   
   
      format: format.combine(
        format.colorize({
   
   
          colors: levelsColors,
        }),
        format.simple(),
        format.printf((info) => {
   
   
          // 获取 Info Symbols key
          const symbols = Object.getOwnPropertySymbols(info);
          const color = levelsColors[info[symbols[0]]]; // 获取日志级别的颜色
          const chalkColor = chalk[color];
          const message = `${
     
     chalkColor(info.timestamp)} ${
     
     chalkColor(info[symbols[2]])}`;
          return message;
        }),
      ),
      level: 'debug',
    }),
  ],
});

export default winstonLogger;

这里我们按照日志不同级别区分,在 AppModule 配置服务:

import {
   
    MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import {
   
    WinstonModule } from 'nest-winston';

import {
   
    LoggerMiddleware } from '@/middleware/logger.middleware'; // 全局日志中间件

import winstonLogger from './config/winston.config';

@Module({
   
   
  imports: [
    WinstonModule.forRoot({
   
   
      transports: winstonLogger.transports,
      format: winstonLogger.format,
      defaultMeta: winstonLogger.defaultMeta,
      exitOnError: false, // 防止意外退出
    }),
  ],
})
export class AppModule implements NestModule {
   
   
  configure(consumer: MiddlewareConsumer) {
   
   
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}

main.ts 中更换日志记录器:

import {
   
    WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';

async function bootstrap() {
   
   
  const app = await NestFactory.create(AppModule);
  app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
  await app.listen(3000);
}
bootstrap();

最终效果

j0whuhvn4re2lsz6po7lbmi0u3pvyr0i.png

总结

这里只是简单的日志记录示例,更加高级自定义的日志功能需要自己去探索。

Github 仓库: Vue3 Admin

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
JavaScript
Nest.js 实战 (十一):配置热重载 HMR 给服务提提速
这篇文章介绍了Nest.js服务在应用程序引导过程中,TypeScript编译对效率的影响,以及如何通过使用webpackHMR来降低应用实例化的时间。文章包含具体教程,指导读者如何在项目中安装依赖包,并在根目录下新增webpack配置文件webpack-hmr.config.js来调整HMR相关的配置。最后,文章总结了如何通过自定义webpack配置来更好地控制HMR行为。
|
29天前
Nest.js 实战 (十三):实现 SSE 服务端主动向客户端推送消息
这篇文章介绍了在Nest.js应用中使用Server-Sent Events (SSE)的技术。文章首先讨论了在特定业务场景下,为何选择SSE而不是WebSocket作为实时通信系统的实现方式。接着解释了SSE的概念,并展示了如何在Nest.js中实现SSE。文章包含客户端实现的代码示例,并以一个效果演示结束,总结SSE在Nest.js中的应用。
Nest.js 实战 (十三):实现 SSE 服务端主动向客户端推送消息
|
18天前
|
JavaScript 前端开发 安全
js逆向实战之烯牛数据请求参数加密和返回数据解密
【9月更文挑战第20天】在JavaScript逆向工程中,处理烯牛数据的请求参数加密和返回数据解密颇具挑战。本文详细分析了这一过程,包括网络请求监测、代码分析、加密算法推测及解密逻辑研究,并提供了实战步骤,如确定加密入口点、逆向分析算法及模拟加密解密过程。此外,还强调了法律合规性和安全性的重要性,帮助读者合法且安全地进行逆向工程。
65 11
|
1月前
Nest.js 实战 (十二):优雅地使用事件发布/订阅模块 Event Emitter
这篇文章介绍了在Nest.js构建应用时,如何通过事件/发布-订阅模式使应用程序更健壮、灵活、易于扩展,并简化服务间通信。文章主要围绕@nestjs/event-emitter模块展开,这是一个基于eventemitter2库的社区模块,提供了事件发布/订阅功能,使得实现事件驱动架构变得简单。文章还介绍了如何使用该模块,包括安装依赖、初始化模块、注册EventEmitterModule、使用装饰器简化监听等。最后总结,集成@nestjs/event-emitter模块可以提升应用程序的事件驱动能力,构建出更为松耦合、易扩展且高度灵活的系统架构,是构建现代、响应迅速且具有高度解耦特性的Nest.
|
1月前
|
SQL 安全 数据库
基于SQL Server事务日志的数据库恢复技术及实战代码详解
基于事务日志的数据库恢复技术是SQL Server中一个非常强大的功能,它能够帮助数据库管理员在数据丢失或损坏的情况下,有效地恢复数据。通过定期备份数据库和事务日志,并在需要时按照正确的步骤恢复,可以最大限度地减少数据丢失的风险。需要注意的是,恢复数据是一个需要谨慎操作的过程,建议在执行恢复操作之前,详细了解相关的操作步骤和注意事项,以确保数据的安全和完整。
65 0
|
2月前
|
数据库 Java 监控
Struts 2 日志管理化身神秘魔法师,洞察应用运行乾坤,演绎奇幻篇章!
【8月更文挑战第31天】在软件开发中,了解应用运行状况至关重要。日志管理作为 Struts 2 应用的关键组件,记录着每个动作和决策,如同监控摄像头,帮助我们迅速定位问题、分析性能和使用情况,为优化提供依据。Struts 2 支持多种日志框架(如 Log4j、Logback),便于配置日志级别、格式和输出位置。通过在 Action 类中添加日志记录,我们能在开发过程中获取详细信息,及时发现并解决问题。合理配置日志不仅有助于调试,还能分析用户行为,提升应用性能和稳定性。
40 0
|
2月前
|
前端开发 Java UED
JSF 面向组件开发究竟藏着何种奥秘?带你探寻可复用 UI 组件设计的神秘之路
【8月更文挑战第31天】在现代软件开发中,高效与可维护性至关重要。JavaServer Faces(JSF)框架通过其面向组件的开发模式,提供了构建复杂用户界面的强大工具,特别适用于设计可复用的 UI 组件。通过合理设计组件的功能与外观,可以显著提高开发效率并降低维护成本。本文以一个具体的 `MessageComponent` 示例展示了如何创建可复用的 JSF 组件,并介绍了如何在 JSF 页面中使用这些组件。结合其他技术如 PrimeFaces 和 Bootstrap,可以进一步丰富组件库,提升用户体验。
45 0
|
2月前
|
大数据 数据处理 分布式计算
JSF 逆袭大数据江湖!看前端框架如何挑战数据处理极限?揭秘这场技术与勇气的较量!
【8月更文挑战第31天】在信息爆炸时代,大数据已成为企业和政府决策的关键。JavaServer Faces(JSF)作为标准的 Java Web 框架,如何与大数据技术结合,高效处理大规模数据集?本文探讨大数据的挑战与机遇,介绍 JSF 与 Hadoop、Apache Spark 等技术的融合,展示其实现高效数据存储和处理的潜力,并提供示例代码,助您构建强大的大数据系统。
33 0
|
2月前
|
API UED 开发者
如何在Uno Platform中轻松实现流畅动画效果——从基础到优化,全方位打造用户友好的动态交互体验!
【8月更文挑战第31天】在开发跨平台应用时,确保用户界面流畅且具吸引力至关重要。Uno Platform 作为多端统一的开发框架,不仅支持跨系统应用开发,还能通过优化实现流畅动画,增强用户体验。本文探讨了Uno Platform中实现流畅动画的多个方面,包括动画基础、性能优化、实践技巧及问题排查,帮助开发者掌握具体优化策略,提升应用质量与用户满意度。通过合理利用故事板、减少布局复杂性、使用硬件加速等技术,结合异步方法与预设缓存技巧,开发者能够创建美观且流畅的动画效果。
62 0
|
2月前
|
JavaScript 安全 数据安全/隐私保护
深入Node.js与Express:从表单接收到数据验证的完整指南——实战技巧揭秘后端开发中的表单处理流程
【8月更文挑战第31天】在Web开发中,处理表单数据至关重要。本文通过实例详细讲解了如何利用Node.js与Express框架接收和验证表单数据。首先,搭建环境并初始化一个简单的Express应用;接着,演示如何接收用户注册表单中的`username`和`email`字段;最后,引入`joi`模块进行数据验证,确保数据安全准确。掌握这些技能有助于开发者构建更安全、友好的Web应用。随着Node.js和Express的不断进步,未来将有更多高级功能值得期待。
43 0