WEBKT

NestJS 日志进阶:Winston & Pino 打造结构化日志记录体系

31 0 0 0

为什么需要结构化日志?

NestJS 内置日志

Winston:灵活强大的日志库

安装 Winston

基本使用

在 NestJS 中使用 Winston

Winston 的高级配置

Pino:高性能日志库

安装 Pino

基本使用

在 NestJS 中使用 Pino

Pino 的高级配置

日志的最佳实践

总结

作为一名 NestJS 开发者,你肯定遇到过这样的场景:应用出问题了,却苦于没有详细的日志信息来定位问题,或者日志信息杂乱无章,难以分析。别担心,今天咱们就来聊聊如何在 NestJS 中使用 Winston 和 Pino 这两个强大的日志库,构建一个结构化、可配置的日志记录体系,让你的应用调试和问题排查事半功倍。

为什么需要结构化日志?

在传统的开发模式中,我们可能习惯于使用 console.log() 来输出一些信息。这种方式虽然简单,但在生产环境中却存在很多问题:

  • 信息杂乱console.log() 输出的信息通常是纯文本,缺乏结构,难以解析和分析。
  • 难以过滤:无法根据日志级别(如 debug、info、error)进行过滤。
  • 无法持久化console.log() 的输出通常只在控制台中显示,无法持久化到文件或数据库中。
  • 性能问题:在生产环境中大量使用 console.log() 可能会影响应用性能。

结构化日志则可以解决这些问题。它将日志信息以结构化(如 JSON)的形式记录,包含时间戳、日志级别、消息内容、上下文信息等,方便后续的分析、查询和可视化。

NestJS 内置日志

NestJS 内置了一个简单的日志系统 Logger。你可以通过 Logger 类来记录日志:

import { Logger } from '@nestjs/common';
const logger = new Logger('MyService');
logger.log('This is a log message.');
logger.error('This is an error message.');
logger.warn('This is a warning message.');
logger.debug('This is a debug message.');
logger.verbose('This is a verbose message.');

NestJS 的内置 Logger 提供了基本的日志功能,但在实际项目中,我们通常需要更强大的日志解决方案,例如:

  • 自定义日志格式:例如 JSON 格式,方便日志收集和分析工具处理。
  • 多样的输出目标:例如控制台、文件、远程日志服务等。
  • 更灵活的日志级别控制:例如根据环境(开发、测试、生产)设置不同的日志级别。
  • 性能优化:例如异步日志写入,避免阻塞主线程。

这时候,我们就需要引入第三方日志库,例如 Winston 或 Pino。

Winston:灵活强大的日志库

Winston 是一个非常流行的 Node.js 日志库,它提供了丰富的功能和灵活的配置选项。

安装 Winston

npm install winston

基本使用

import * as winston from 'winston';
const logger = winston.createLogger({
level: 'info', // 设置日志级别
format: winston.format.json(), // 设置日志格式为 JSON
transports: [
new winston.transports.Console(), // 输出到控制台
new winston.transports.File({ filename: 'combined.log' }), // 输出到文件
new winston.transports.File({ filename: 'error.log', level: 'error' }),//只记录error级别
],
});
logger.info('This is an info message.');
logger.error('This is an error message.');

在这个例子中,我们创建了一个 Winston logger,并配置了:

  • level:日志级别为 info,表示只记录 info 及以上级别的日志(infowarnerror)。
  • format:日志格式为 JSON。
  • transports:定义了两个输出目标:
    • Console:输出到控制台。
    • File:输出到文件 combined.log
    • File:输出到文件error.log,只记录error级别

在 NestJS 中使用 Winston

要在 NestJS 中使用 Winston,我们可以创建一个自定义的 logger 服务。

// logger.service.ts
import { Injectable, LoggerService } from '@nestjs/common';
import * as winston from 'winston';
@Injectable()
export class WinstonLogger implements LoggerService {
private logger: winston.Logger;
constructor() {
this.logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' }),
],
});
}
log(message: string, context?: string) {
this.logger.info(message, { context });
}
error(message: string, trace?: string, context?: string) {
this.logger.error(message, { trace, context });
}
warn(message: string, context?: string) {
this.logger.warn(message, { context });
}
debug(message: string, context?: string) {
this.logger.debug(message, { context });
}
verbose(message: string, context?: string) {
this.logger.verbose(message, { context });
}
}

在这个例子中:

  1. 我们创建了一个 WinstonLogger 类,实现了 NestJS 的 LoggerService 接口。
  2. 在构造函数中,我们创建了一个 Winston logger,并根据环境变量 NODE_ENV 设置了日志级别。
  3. 我们实现了 LoggerService 接口的各个方法,将日志信息传递给 Winston logger。
  4. 使用了winston.format.combine组合了多种日志格式

接下来,我们需要在 NestJS 模块中提供这个自定义的 logger 服务。

// app.module.ts
import { Module } from '@nestjs/common';
import { WinstonLogger } from './logger.service';
@Module({
providers: [
{
provide: LoggerService, // 使用 LoggerService 作为提供者
useClass: WinstonLogger, // 使用 WinstonLogger 作为实现
},
],
})
export class AppModule {}

现在,我们就可以在 NestJS 应用的任何地方注入 LoggerService 并使用它了。

// my.service.ts
import { Injectable, LoggerService } from '@nestjs/common';
@Injectable()
export class MyService {
constructor(private readonly logger: LoggerService) {}
doSomething() {
this.logger.log('Doing something...', MyService.name);
// ...
}
}

Winston 的高级配置

Winston 提供了很多高级配置选项,可以满足各种复杂的日志需求。

  • 自定义格式:除了 JSON 格式,Winston 还支持其他格式,例如 winston.format.simple()winston.format.printf() 等。你也可以自定义格式。
  • 添加元数据:你可以在日志信息中添加自定义的元数据,例如请求 ID、用户信息等。
  • 使用 transports:Winston 支持多种 transports,例如 ConsoleFileHttpStream 等。你也可以自定义 transports。
  • 日志轮换:可以使用winston-daily-rotate-file进行日志轮换。

Pino:高性能日志库

Pino 是另一个流行的 Node.js 日志库,它以高性能著称。Pino 的设计理念是尽可能减少日志记录对应用性能的影响。

安装 Pino

npm install pino pino-pretty

pino-pretty 是一个可选的工具,可以将 Pino 输出的 JSON 日志格式化为更易读的格式。

基本使用

import pino from 'pino';
const logger = pino({
level: 'info', // 设置日志级别
transport:{
target: 'pino-pretty',
options:{
colorize:true
}
}
});
logger.info('This is an info message.');
logger.error('This is an error message.');

在 NestJS 中使用 Pino

与 Winston 类似,我们也可以创建一个自定义的 logger 服务来在 NestJS 中使用 Pino。

// logger.service.ts
import { Injectable, LoggerService } from '@nestjs/common';
import pino from 'pino';
@Injectable()
export class PinoLogger implements LoggerService {
private logger: pino.Logger;
constructor() {
this.logger = pino({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
transport:{
target: 'pino-pretty',
options:{
colorize:true
}
}
});
}
log(message: string, context?: string) {
this.logger.info({ context }, message);
}
error(message: string, trace?: string, context?: string) {
this.logger.error({ context, trace }, message);
}
warn(message: string, context?: string) {
this.logger.warn({ context }, message);
}
debug(message: string, context?: string) {
this.logger.debug({ context }, message);
}
verbose(message: string, context?: string) {
this.logger.verbose({ context }, message);
}
}

然后,在 AppModule 中提供这个自定义的 logger 服务:

// app.module.ts
import { Module } from '@nestjs/common';
import { PinoLogger } from './logger.service';
@Module({
providers: [
{
provide: LoggerService,
useClass: PinoLogger,
},
],
})
export class AppModule {}

Pino 的高级配置

  • 自定义日志级别:Pino 允许你自定义日志级别。
  • 添加钩子:你可以在日志记录的不同阶段添加钩子,例如在记录日志前修改日志信息。
  • 子 logger:你可以创建子 logger,继承父 logger 的配置,并添加额外的元数据。
  • 异步模式:Pino 默认使用异步模式,将日志写入操作放在事件循环的下一个 tick 中执行,避免阻塞主线程。 你也可以使用pino.transport进行配置。

日志的最佳实践

  • 选择合适的日志级别:根据不同的环境(开发、测试、生产)设置不同的日志级别。在开发环境中,可以使用较低的日志级别(如 debug)来记录更详细的信息;在生产环境中,使用较高的日志级别(如 infoerror)来减少日志量。
  • 记录有用的信息:日志应该记录足够的信息来帮助你定位问题,但也要避免记录过多的无用信息。例如,可以记录请求的 URL、参数、响应状态码、错误堆栈等。
  • 使用结构化日志:使用 JSON 格式记录日志,方便后续的分析和查询。
  • 添加上下文信息:在日志信息中添加上下文信息,例如请求 ID、用户信息等,方便追踪和关联日志。
  • 日志轮换:对于文件日志,要定期轮换日志文件,避免单个日志文件过大。
  • 集中式日志管理:在生产环境中,建议使用集中式日志管理系统(如 ELK Stack、Graylog、Splunk 等)来收集、存储和分析日志。

总结

通过本文,相信你已经对如何在 NestJS 中使用 Winston 和 Pino 构建结构化日志记录体系有了更深入的了解。选择哪个日志库取决于你的具体需求,Winston 功能更丰富,Pino 性能更高。记住,良好的日志记录是应用可维护性和可观察性的基石,希望你能充分利用这些工具,让你的 NestJS 应用更上一层楼!

技术老兵 NestJS日志Winston

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/7891