NestJS + Winston 创建日志模块,生成本地日志文件

1,088 阅读3分钟

近来一直在使用NestJS写一些小项目,根据NestJS官方文档,结合Winston,写了一个LoggerModule,作为日志的记录,首次写内容分享,如有不满意的地方,多多包涵。

主要实现的功能是日志保存到本地文件,并根据日期自动创建新文件,自动归档等。

第一步 安装相关依赖

pnpm add winston winston-daily-rotate-file
yarn add winston winston-daily-rotate-file
npm install winston winston-daily-rotate-file

第二步 新建LoggerModule

nest g mo logger
nest g s logger --no-spec

这时nest cli会自动创建 logger.module.tslogger.service.ts

import { Module } from '@nestjs/common';
import { LoggerService } from './logger.service';

@Module({
  providers: [LoggerService],
  exports: [LoggerService], // 导出LoggerService,这样可以在其他模块中使用该Service
})
export class LoggerModule {}

重点是logger.service.ts的编写

winston - npm (npmjs.com) winston文档

winston-daily-rotate-file - npm (npmjs.com) 相关文档可在该网站查询,主要功能是可以根据日期、大小限制轮换日志,可以根据计数或已用天数删除旧日志。

import { Logger, createLogger, format, transports } from 'winston';
import 'winston-daily-rotate-file';
import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerService {
  private context?: string;
  private logger: Logger;

  public setContext(context: string): void {
    this.context = context;
  }

  constructor(private configService: ConfigService) {
    this.logger = createLogger({
        // winston 格式定义
      format: format.combine(
        format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
        format.prettyPrint(),
      ),
      // 生成文件
      // winston 文档中使用的方法为 new transports.File()
      // 因为加入日志归档等相关功能,所以使用transports.DailyRotateFile()方法来实现
      transports: [
        // 打印到控制台,生产可注释关闭该功能
        new transports.Console(),
        // 保存到文件
        new transports.DailyRotateFile({
          // 日志文件文件夹,建议使用path.join()方式来处理,或者process.cwd()来设置,此处仅作示范
          dirname: 'src/logs',
          // 日志文件名 %DATE% 会自动设置为当前日期
          filename: 'application-%DATE%.info.log',
          // 日期格式
          datePattern: 'YYYY-MM-DD',
          // 压缩文档,用于定义是否对存档的日志文件进行 gzip 压缩 默认值 false
          zippedArchive: true,
          // 文件最大大小,可以是bytes、kb、mb、gb
          maxSize: '20m',
          // 最大文件数,可以是文件数也可以是天数,天数加单位"d",
          maxFiles: '7d',
          // 格式定义,同winston
          format: format.combine(
            format.timestamp({
              format: 'YYYY-MM-DD HH:mm:ss',
            }),
            format.json(),
          ),
          // 日志等级,不设置所有日志将在同一个文件
          level: 'info',
        }),
        // 同上述方法,区分error日志和info日志,保存在不同文件,方便问题排查
        new transports.DailyRotateFile({
          dirname: 'src/logs',
          filename: 'application-%DATE%.error.log',
          datePattern: 'YYYY-MM-DD',
          zippedArchive: true,
          maxSize: '20m',
          maxFiles: '14d',
          format: format.combine(
            format.timestamp({
              format: 'YYYY-MM-DD HH:mm:ss',
            }),
            format.json(),
          ),
          level: 'error',
        }),
      ],
    });
  }

  // 错误日志记录
  error(ctx: any, message: string, meta?: Record<string, any>): Logger {
    return this.logger.error({
      message,
      contextNmae: this.context,
      ctx,
      ...meta,
    });
  }
  // 警告日志记录
  warn(ctx: any, message: string, meta?: Record<string, any>): Logger {
    return this.logger.warn({
      message,
      contextNmae: this.context,
      ctx,
      ...meta,
    });
  }
  // debug日志记录
  debug(ctx: any, message: string, meta?: Record<string, any>): Logger {
    return this.logger.debug({
      message,
      contextNmae: this.context,
      ctx,
      ...meta,
    });
  }
  // 基本日志记录
  info(ctx: any, message: string, meta?: Record<string, any>): Logger {
    return this.logger.info({
      message,
      contextNmae: this.context,
      ctx,
      ...meta,
    });
  }
}

使用方式,例如user.module.ts user.controller.ts user.service.ts

// user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
// 请注意自己的实际文件目录,此处仅作展示
import { LoggerModule } from 'src/shared/logger/logger.module';
import { UserProvider } from './user.provider';

@Module({
  imports: [LoggerModule],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}


// user.controller.ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
// 请注意自己的实际文件目录,此处仅作展示
import { LoggerService } from 'src/shared/logger/logger.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('user')
export class UserController {
  constructor(
      private readonly userService: UserService,
      private readonly logger: LoggerService
  ) {}

  @Get()
  findAll() {
    this.logger.info(null, '查询所有用户')
    return this.userService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.userService.update(+id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.userService.remove(+id);
  }
}
// user.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
// 请注意自己的实际文件目录,此处仅作展示
import { LoggerService } from 'src/shared/logger/logger.service';
import { Repository } from 'typeorm';

@Injectable()
export class UserService {
  constructor(
    // 注入 LoggerService
    private readonly logger: LoggerService,
  ) {
    this.logger.setContext(UserService.name);
  }

  async findAll() {
    try {
        // Repository 相关操作
        this.logger.info(null, 'test logger', { name: 'logger info' });
    } catch(error) {
        // 也可以通过 throw new Error('Method not implemented.'); 在Controller层捕获异常
        this.logger.error(null, error.message)
    }
    return `This action returns all user`;
  }

  findOne(id: number) {
    return `This action returns a #${id} user`;
  }

  update(id: number, updateUserDto: UpdateUserDto) {
    return `This action updates a #${id} user`;
  }

  remove(id: number) {
    return `This action removes a #${id} user`;
  }

以上代码仅供参考,如有错误,多多包涵