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 从入门到实战
相关推荐
漫谈网络2 天前
Python logging模块使用指南
python·logging·日志
Doker 多克2 天前
Python-Django系列—日志
python·日志
Amo Xiang2 天前
Python 常用模块(八):logging模块
python·logging·日志
Tee xm17 天前
运维仙途 第2章 日志深渊识异常
linux·运维·服务器·日志
林太白19 天前
NestJS用户模块CRUD和分页实现
前端·javascript·nestjs
plusone19 天前
【Nest指北系列-源码】(一)目录结构
nestjs
亚洲小炫风23 天前
flutter 中各种日志
前端·flutter·日志·log
前端笨鸟1 个月前
NestJS+MongoDB高效CRUD接口开发全景指南
前端·mongodb·nestjs
前端实习生鲸落1 个月前
nest 静态文件打包
前端·node.js·nestjs