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 |
文章目录
- 日志
- 日志等级
- 功能分类日志
- 日志记录位置
- NestJS - logger
- 第三方日志模块(pino VS winston)
- pino
- winston
- 全局异常过滤器
- 总结
日志
日志等级
- Log:通用日志,按需记录
- Warning:警告日志,比如:尝试多次进行数据库操作
- Error:严重日志,比如:数据库异常
- Debug:调试日志,比如:加载数据日志
- Verbose:详细日志,所有的操作与详细信息(非必要不打印)
功能分类日志
- 错误日志:方便定位问题,给用户友好的提示
- 调试日志:方便开发
- 请求日志:记录敏感行为
日志记录位置
- 控制台日志:方便监看(调试用)
- 文件日志:方便回溯与追踪(24小时滚动)
- 数据库日志:敏感操作、敏感数据记录
NestJS - logger
GitHub 提交记录
接下来,我们使用 @nestjs/common
中的 logger 来进行简单的日志打印。
修改 app.module.ts
的 logging: false
,以关闭 typeorm 的日志。
在 main.ts
中修改代码如下。
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/userconst 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
文件,以下是主要修改内容。
@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官网
// 安装 pino
pnpm install nestjs-pino// 安装 pino-pretty 便于直观显示
pnpm i pino-pretty// 安装 pino-roll,日志文件滚动
pnpm i pino-roll
GitHub 提交记录
在 app.module.ts
中添加对日志模块的配置
import { LoggerModule } from "nestjs-pino";
import { join } from "path";...// 配置日志模块LoggerModule.forRoot({pinoHttp: {transport: {targets: [process.env.NODE_ENV === "development"? {level: "info", // 日志级别为 infotarget: "pino-pretty", // 使用 pino-pretty 格式化日志options: {colorize: true, // 启用日志颜色},}: {level: "info", // 日志级别为 infotarget: "pino-roll", // 使用 pino-roll 将日志写入文件options: {file: join("log", "log.txt"), // 日志文件路径frequency: "daily", // 日志文件按天滚动size: "10M", // 每个日志文件的最大大小为 10MBmkdir: true, // 如果目录不存在,则自动创建},},],},},}),...
修改 user.controller.ts
中对 pino 的使用。
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。
pnpm i --save nest-winston winston// 安装日志滚动 -第三方库
pnpm install winston-daily-rotate-file
GitHub 提交记录
在 main.ts
中添加对 winston
的配置
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", // 日志级别为 infoformat: winston.format.combine(winston.format.timestamp(), // 添加时间戳utilities.format.nestLike() // 格式化日志为 Nest 风格),}),// 配置日志文件轮转(警告级别)new winston.transports.DailyRotateFile({level: "warn", // 日志级别为 warndirname: "logs", // 日志文件存储目录filename: "application-%DATE%.log", // 日志文件名,包含日期占位符datePattern: "YYYY-MM-DD-HH", // 日期格式zippedArchive: true, // 启用压缩存档maxSize: "20m", // 每个日志文件的最大大小为 20MBmaxFiles: "14d", // 保留日志文件的天数为 14 天format: winston.format.combine(winston.format.timestamp(), // 添加时间戳winston.format.simple() // 简单格式化日志),}),// 配置日志文件轮转(信息级别)new winston.transports.DailyRotateFile({level: "info", // 日志级别为 infodirname: "logs", // 日志文件存储目录filename: "info-%DATE%.log", // 日志文件名,包含日期占位符datePattern: "YYYY-MM-DD-HH", // 日期格式zippedArchive: true, // 启用压缩存档maxSize: "20m", // 每个日志文件的最大大小为 20MBmaxFiles: "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/userconst port = 3000; // 定义应用监听的端口号// 启动应用并监听指定端口await app.listen(port);// 使用 Winston 记录应用启动信息instance.info(`应用已启动,监听端口:${port}`);
}// 启动应用
bootstrap();
修改 app.module.ts
,提供全局 Logger
import { Global, Logger, Module } from "@nestjs/common";@Global() // 声明为全局模块,所有其他模块均可直接注入
@Module({...providers: [Logger], // 服务提供者exports: [Logger], // 导出 Logger 以供其他模块使用
})
在 user.controller.ts
中测试使用
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 异常处理。
@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 异常过滤器。
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, // 请求的 URLmethod: request.method, // 请求的方法(GET、POST 等)message: exception.message || exception.name, // 异常信息});}
}
在 main.ts
中进行全局使用。
app.useGlobalFilters(new HttpExceptionFilter(logger)); // 使用全局异常过滤器处理 HTTP 异常
此时在访问一个不存在的路由(如:http://localhost:3000/api/user/getAll123)时,即可在控制台看到如下错误。
此时在日志文件中也记录了相关错误日志。
总结
本篇文章,我们认识了日志的作用,学习了 NestJS-logger、pino日志、winston日志,并了解了如何把日志输入到文件。最后,我们学习了全局异常过滤器,它帮助我们统一处理异常的请求,并且我们还实现了和日志相结合。
好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!
参考资料:
- DeepSeek
- NestJS 从入门到实战