NestJS——日志、NestJS-logger、pino、winston、全局异常过滤器

个人简介

👀个人主页: 前端杂货铺

🙋‍♂️学习方向: 主攻前端方向,正逐渐往全干发展

📃个人状态: 研发工程师,现效力于中国工业软件事业

🚀人生格言: 积跬步至千里,积小流成江海

🥇推荐学习:🍍前端面试宝典 🎨100个小功能 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒Three.js

🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧

内容 参考链接
NestJS(一) Docker入门
NestJS(二) NestJS------创建项目、编写User模块
NestJS(三) TypeScript入门
NestJS(四) 编程思想------FP、OOP、FRP、AOP、IOC、DI、MVC、DTO、DAO
NestJS(五) NestJS------多环境配置方案(dotenv、config、@nestjs/config、joi配置校验)
NestJS(六) NestJS------使用TypeORM连接MySQL数据库(Docker拉取镜像、多环境适配)
NestJS(七) NestJS------使用TypeORM操作数据库、增删改查、关联查询、QueryBuilder

文章目录

日志

日志等级

  • Log:通用日志,按需记录
  • Warning:警告日志,比如:尝试多次进行数据库操作
  • Error:严重日志,比如:数据库异常
  • Debug:调试日志,比如:加载数据日志
  • Verbose:详细日志,所有的操作与详细信息(非必要不打印)

功能分类日志

  • 错误日志:方便定位问题,给用户友好的提示
  • 调试日志:方便开发
  • 请求日志:记录敏感行为

日志记录位置

  • 控制台日志:方便监看(调试用)
  • 文件日志:方便回溯与追踪(24小时滚动)
  • 数据库日志:敏感操作、敏感数据记录

NestJS - logger

GitHub 提交记录

接下来,我们使用 @nestjs/common 中的 logger 来进行简单的日志打印。

修改 app.module.tslogging: false,以关闭 typeorm 的日志。

main.ts 中修改代码如下。

javascript 复制代码
import { NestFactory } from "@nestjs/core"; // 导入 NestFactory,用于创建 Nest 应用实例
import { AppModule } from "./app.module"; // 导入应用的根模块
import { Logger } from "@nestjs/common"; // 导入 Logger,用于日志记录

/**
 * 应用程序的入口文件
 * - 使用 NestFactory 创建应用实例
 * - 配置全局设置并启动应用
 */
async function bootstrap() {
  const logger = new Logger(); // 创建 Logger 实例,用于记录日志

  // 创建应用实例,并可选配置日志级别
  const app = await NestFactory.create(AppModule, {
    // 日志级别配置(可选)
    // logger: ["error", "warn"], // 仅记录错误和警告日志
  });

  // 设置全局路由前缀
  app.setGlobalPrefix("api"); // 所有路由将以 "api" 为前缀,例如 /api/user

  const port = 3000; // 定义应用监听的端口号

  // 启动应用并监听指定端口
  await app.listen(port);

  // 使用 Logger 记录应用启动信息
  logger.log(`App运行在:${port}`); // 普通日志
  logger.warn(`App运行在:${port}`); // 警告日志
  logger.error(`App运行在:${port}`); // 错误日志
}

// 启动应用
bootstrap();

GitHub 提交记录

在模块中使用 logger 也非常简单。接下来,我们修改 user.controller.ts 文件,以下是主要修改内容。

javascript 复制代码
@Controller("user") // 定义控制器的路由前缀为 "user"
export class UserController {
  private logger = new Logger(UserController.name); // 创建 Logger 实例,用于记录日志
  /**
   * 构造函数
   * - 注入用户服务
   * @param userService 用户服务
   */
  constructor(private userService: UserService) {
    this.logger.log("UserController initialized"); // 控制器初始化时记录日志
  }

  /**
   * 获取所有用户
   * - 路由:GET /user/getAll
   * @returns 所有用户的列表
   */
  @Get("getAll")
  getUsers(): any {
    this.logger.log("请求 getAll 成功"); // 记录获取所有用户的日志
    return this.userService.findAll(); // 调用服务层方法查询所有用户
  }
}

第三方日志模块(pino VS winston)

pino

pinio官网

javascript 复制代码
// 安装 pino
pnpm install nestjs-pino

// 安装 pino-pretty 便于直观显示
pnpm i pino-pretty

// 安装 pino-roll,日志文件滚动
pnpm i pino-roll 

GitHub 提交记录

app.module.ts 中添加对日志模块的配置

javascript 复制代码
import { LoggerModule } from "nestjs-pino";
import { join } from "path";

...

    // 配置日志模块
    LoggerModule.forRoot({
      pinoHttp: {
        transport: {
          targets: [
            process.env.NODE_ENV === "development"
              ? {
                  level: "info", // 日志级别为 info
                  target: "pino-pretty", // 使用 pino-pretty 格式化日志
                  options: {
                    colorize: true, // 启用日志颜色
                  },
                }
              : {
                  level: "info", // 日志级别为 info
                  target: "pino-roll", // 使用 pino-roll 将日志写入文件
                  options: {
                    file: join("log", "log.txt"), // 日志文件路径
                    frequency: "daily", // 日志文件按天滚动
                    size: "10M", // 每个日志文件的最大大小为 10MB
                    mkdir: true, // 如果目录不存在,则自动创建
                  },
                },
          ],
        },
      },
    }),...

修改 user.controller.ts 中对 pino 的使用。

javascript 复制代码
import { Controller, Get, Post } from "@nestjs/common"; // 导入控制器和 HTTP 请求装饰器
import { UserService } from "./user.service"; // 导入用户服务
import { User } from "./user.entity"; // 导入用户实体
import { Logger } from "nestjs-pino";

@Controller("user") // 定义控制器的路由前缀为 "user"
export class UserController {
  /**
   * 构造函数
   * - 注入用户服务
   * @param userService 用户服务
   */
  constructor(
    private userService: UserService,
    private logger: Logger
  ) {
    this.logger.log("UserController initialized"); // 控制器初始化时记录日志
  }

  /**
   * 获取所有用户
   * - 路由:GET /user/getAll
   * @returns 所有用户的列表
   */
  @Get("getAll")
  getUsers(): any {
    return this.userService.findAll(); // 调用服务层方法查询所有用户
  }
}

这样,我们在访问 getAll() 请求时即可在控制台看到相关信息的打印。

与此同时,log 信息写入了 log 文件夹的 log.txt.1 中。


winston

winston官网
nest-winston网址

安装 winston 和 nest-winston。

javascript 复制代码
pnpm i --save nest-winston winston

// 安装日志滚动 -第三方库
pnpm install winston-daily-rotate-file

GitHub 提交记录

main.ts 中添加对 winston 的配置

javascript 复制代码
import { NestFactory } from "@nestjs/core"; // 导入 NestFactory,用于创建 Nest 应用实例
import { AppModule } from "./app.module"; // 导入应用的根模块
import { createLogger } from "winston";
import * as winston from "winston";
import { utilities, WinstonModule } from "nest-winston"; // 导入 nest-winston,用于 Nest 集成 Winston 日志库}
import "winston-daily-rotate-file"; // 导入 Winston 日志轮转文件传输器

/**
 * 应用程序的入口文件
 * - 使用 NestFactory 创建应用实例
 * - 配置全局设置并启动应用
 */
async function bootstrap() {
  // 创建 Winston 日志实例
  const instance = createLogger({
    transports: [
      // 配置控制台日志输出
      new winston.transports.Console({
        level: "info", // 日志级别为 info
        format: winston.format.combine(
          winston.format.timestamp(), // 添加时间戳
          utilities.format.nestLike() // 格式化日志为 Nest 风格
        ),
      }),
      // 配置日志文件轮转(警告级别)
      new winston.transports.DailyRotateFile({
        level: "warn", // 日志级别为 warn
        dirname: "logs", // 日志文件存储目录
        filename: "application-%DATE%.log", // 日志文件名,包含日期占位符
        datePattern: "YYYY-MM-DD-HH", // 日期格式
        zippedArchive: true, // 启用压缩存档
        maxSize: "20m", // 每个日志文件的最大大小为 20MB
        maxFiles: "14d", // 保留日志文件的天数为 14 天
        format: winston.format.combine(
          winston.format.timestamp(), // 添加时间戳
          winston.format.simple() // 简单格式化日志
        ),
      }),
      // 配置日志文件轮转(信息级别)
      new winston.transports.DailyRotateFile({
        level: "info", // 日志级别为 info
        dirname: "logs", // 日志文件存储目录
        filename: "info-%DATE%.log", // 日志文件名,包含日期占位符
        datePattern: "YYYY-MM-DD-HH", // 日期格式
        zippedArchive: true, // 启用压缩存档
        maxSize: "20m", // 每个日志文件的最大大小为 20MB
        maxFiles: "14d", // 保留日志文件的天数为 14 天
        format: winston.format.combine(
          winston.format.timestamp(), // 添加时间戳
          winston.format.simple() // 简单格式化日志
        ),
      }),
    ],
  });

  // 创建应用实例,并可选配置日志级别
  const app = await NestFactory.create(AppModule, {
    // 日志级别配置(可选)
    // logger: ["error", "warn"], // 仅记录错误和警告日志
    logger: WinstonModule.createLogger({
      instance,
    }),
  });

  // 设置全局路由前缀
  app.setGlobalPrefix("api"); // 所有路由将以 "api" 为前缀,例如 /api/user

  const port = 3000; // 定义应用监听的端口号
  // 启动应用并监听指定端口
  await app.listen(port);
  // 使用 Winston 记录应用启动信息
  instance.info(`应用已启动,监听端口:${port}`);
}

// 启动应用
bootstrap();

修改 app.module.ts,提供全局 Logger

javascript 复制代码
import { Global, Logger, Module } from "@nestjs/common";

@Global() // 声明为全局模块,所有其他模块均可直接注入
@Module({
  ...
  providers: [Logger], // 服务提供者
  exports: [Logger], // 导出 Logger 以供其他模块使用
})

user.controller.ts 中测试使用

javascript 复制代码
import { Controller, Get, Logger, Post } from "@nestjs/common"; // 导入控制器和 HTTP 请求装饰器
import { UserService } from "./user.service"; // 导入用户服务
import { User } from "./user.entity"; // 导入用户实体
/**
 * 用户控制器类
 * - 定义与用户相关的 HTTP 路由和处理逻辑
 * - 使用 `UserService` 提供的业务逻辑操作用户数据
 */
@Controller("user") // 定义控制器的路由前缀为 "user"
export class UserController {
  // private logger = new Logger(UserController.name); // 创建 Logger 实例,用于记录日志
  /**
   * 构造函数
   * - 注入用户服务
   * @param userService 用户服务
   */
  constructor(
    private userService: UserService,
    private readonly logger: Logger // 注入日志服务
  ) {
    this.logger.log("UserController initialized"); // 控制器初始化时记录日志
  }

  /**
   * 获取所有用户
   * - 路由:GET /user/getAll
   * @returns 所有用户的列表
   */
  @Get("getAll")
  getUsers(): any {
    this.logger.log("Fetching all users"); // 记录获取所有用户的日志
    this.logger.warn("Fetching all users");
    this.logger.error("Fetching all users");
    return this.userService.findAll(); // 调用服务层方法查询所有用户
  }
}

全局异常过滤器

GitHub 提交记录

修改 user.controller.ts,给 getAll 接口设置 http 异常处理。

javascript 复制代码
  @Get("getAll")
  getUsers(): any {
    const user = { isAdmin: false };
    if (!user.isAdmin) {
      throw new HttpException("User is not admin", HttpStatus.FORBIDDEN);
    }
    this.logger.log("Fetching all users"); // 记录获取所有用户的日志
    this.logger.warn("Fetching all users");
    this.logger.error("Fetching all users");
    return this.userService.findAll(); // 调用服务层方法查询所有用户
  }

那么我们在访问 http://localhost:3000/api/user/getAll 网址的时候将在控制台看到如下日志信息。

src 目录下创建 filters 文件,并在该文件下创建 http-exception-filer.ts 文件,封装它为全局的 Http 异常过滤器。

javascript 复制代码
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  LoggerService,
} from "@nestjs/common"; // 导入相关装饰器和类型

/**
 * 自定义 HTTP 异常过滤器
 * - 捕获所有 `HttpException` 类型的异常
 * - 记录异常日志并返回标准化的响应
 */
@Catch(HttpException) // 捕获 HttpException 类型的异常
export class HttpExceptionFilter implements ExceptionFilter {
  /**
   * 构造函数
   * - 注入日志服务,用于记录异常日志
   * @param logger 日志服务
   */
  constructor(private logger: LoggerService) {}

  /**
   * 异常捕获处理方法
   * - 捕获异常并返回标准化的响应
   * @param exception 捕获的异常对象
   * @param host 参数上下文
   */
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp(); // 获取 HTTP 上下文
    const response = ctx.getResponse(); // 获取响应对象
    const request = ctx.getRequest(); // 获取请求对象
    const status = exception.getStatus(); // 获取异常状态码

    // 使用日志服务记录错误信息
    this.logger.error(exception.message, exception.stack);

    // 返回标准化的 JSON 响应
    response.status(status).json({
      code: status, // HTTP 状态码
      timestamp: new Date().toISOString(), // 当前时间戳
      path: request.url, // 请求的 URL
      method: request.method, // 请求的方法(GET、POST 等)
      message: exception.message || exception.name, // 异常信息
    });
  }
}

main.ts 中进行全局使用。

javascript 复制代码
app.useGlobalFilters(new HttpExceptionFilter(logger)); // 使用全局异常过滤器处理 HTTP 异常

此时在访问一个不存在的路由(如:http://localhost:3000/api/user/getAll123)时,即可在控制台看到如下错误。

此时在日志文件中也记录了相关错误日志。


总结

本篇文章,我们认识了日志的作用,学习了 NestJS-logger、pino日志、winston日志,并了解了如何把日志输入到文件。最后,我们学习了全局异常过滤器,它帮助我们统一处理异常的请求,并且我们还实现了和日志相结合。

好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!


参考资料:

  1. DeepSeek
  2. NestJS 从入门到实战
相关推荐
Eric_见嘉10 小时前
NestJS 🧑‍🍳 厨子必修课(九):API 文档 Swagger
前端·后端·nestjs
stevenzqzq20 小时前
Android Studio Logcat 基础认知
android·ide·android studio·日志
Swuagg4 天前
Flutter 日志模块之参考设计
日志·logger
0和1的舞者9 天前
SpringBoot日志框架全解析
java·学习·springboot·日志·打印·lombok
XiaoYu20029 天前
第3章 Nest.js拦截器
前端·ai编程·nestjs
XiaoYu200210 天前
第2章 Nest.js入门
前端·ai编程·nestjs
实习生小黄11 天前
NestJS 调试方案
后端·nestjs
当时只道寻常14 天前
NestJS 如何配置环境变量
nestjs
余子桃15 天前
ubutun日志文件自动流转
linux·日志
少云清16 天前
【接口测试】7_代码实现 _日志收集
接口测试·日志·代码实现