使用Winston替换NestJS项目中Nest内置的logger以及结合全局异常过滤器

 

   npm install --save nest-winston winston 

  • main.ts中创建并配置logger
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { createLogger } from 'winston';
import { WinstonModule } from 'nest-winston';
async function bootstrap() {
  // 1.创建winston实例
  const logger = createLogger({
    //  一些配置项
  });

  const app = await NestFactory.create(AppModule, {
    // logger: ['error', 'warn'],
    // 2.配置nest logger为winston
    logger: WinstonModule.createLogger(logger),
  });
  await app.listen(3000);
}
bootstrap();
  • 随后按照官方事例 在app.module.ts中全局提供全局提供已被替换为Winston的logger
import { Logger, Module } from '@nestjs/common'

@Module({
  providers: [Logger] 
})

export class AppModule {}
  •  在user.controller.ts中注入并使用则会报错:无法解析logger
  • 而事实上根据上述官方提供的案例 仅仅是在对应模块module中提供,以及在对应的controller中注入使用,模块和模块之间想要相互引用则需要exports出来(其他模块进行import即可,或将这个模块注册为全局模块);其他模块才能正常使用。
  • 将app.module注册为全局模块 
import {} from '@nest/common'
// app.module
@Global()
@Module({
  imports: [
//
  ],
  controllers: [AppController],
  providers: [AppService, Logger],
  exports: [Logger], //仍然需要
})
export class AppModule {}
  • 全局注册后在其余模块controller中使用也无需装饰器注入
import { Logger } from '@nestjs/common'
@Controller('user')
export class UserController {
  constructor(
    private userService: UserService,
    private readonly logger: Logger,
  ) {}
  • 安装winston-daily-rotate-file ,这是一个与Winston集成的模块,能自动每天或按需轮换日志文件。
npm i winston-daily-rotate-file 
  • 导入并补全winston其余配置
//main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as winston from 'winston';
import { WinstonModule, utilities } from 'nest-winston';
import 'winston-daily-rotate-file';
// import { format } from 'path';
async function bootstrap() {
  // 1.创建winston实例
  const logger = winston.createLogger({
    //  一些配置项

    transports: [
      new winston.transports.Console({
        format: winston.format.combine(
          winston.format.timestamp(),
          utilities.format.nestLike(),
        ),
      }),
      new winston.transports.DailyRotateFile({
        // 日志文件文件夹,建议使用path.join()方式来处理,或者process.cwd()来设置,此处仅作示范
        dirname: 'src/logs',
        // 日志文件名 %DATE% 会自动设置为当前日期
        filename: 'info-%DATE%.info.log',
        // 日期格式
        datePattern: 'YYYY-MM-DD',
        // 压缩文档,用于定义是否对存档的日志文件进行 gzip 压缩 默认值 false
        zippedArchive: true,
        // 文件最大大小,可以是bytes、kb、mb、gb
        maxSize: '20m',
        // 最大文件数,可以是文件数也可以是天数,天数加单位"d",
        maxFiles: '7d',
        // 格式定义,同winston
        format: winston.format.combine(
          winston.format.timestamp({
            format: 'YYYY-MM-DD HH:mm:ss',
          }),
          winston.format.json(),
          winston.format.simple(),
        ),

        // 日志等级,不设置所有日志将在同一个文件
        level: 'info',
      }),
      // 同上述方法,区分error日志和info日志,保存在不同文件,方便问题排查
      new winston.transports.DailyRotateFile({
        dirname: 'src/logs',
        filename: 'error-%DATE%.error.log',
        datePattern: 'YYYY-MM-DD',
        zippedArchive: true,
        maxSize: '20m',
        maxFiles: '14d',
        format: winston.format.combine(
          winston.format.timestamp({
            format: 'YYYY-MM-DD HH:mm:ss',
          }),
          winston.format.json(),
          winston.format.simple(),
        ),
        level: 'warn',
      }),
    ],
  });

  const app = await NestFactory.create(AppModule, {
    // logger: ['error', 'warn'],
    // 2.配置nestjs logger为winston
    logger: WinstonModule.createLogger(logger),
  });
  await app.listen(3000);
}
bootstrap();
  • winston可配置功能多但是缺点则是 需要在需要的地方手动调用以加入日志
//user.controller.ts
@Get()
  getUser(): any {
    this.logger.log('getUser success');
    return this.userService.findAll();
  }
  • 可以在配合全局过滤器来使用方便记录 
//all-exceptions.filter.tss
import {
  Catch,
  ExceptionFilter,
  LoggerService,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import * as requestIp from 'request-ip';
import { HttpAdapterHost } from '@nestjs/core';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  // ...
  constructor(
    private readonly logger: LoggerService,
    private readonly httpAdapterHost: HttpAdapterHost,
  ) {}
  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const httpStatus =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const { httpAdapter } = this.httpAdapterHost;
    const responseBody = {
      headers: request.headers,
      query: request.query,
      body: request.body,
      params: request.params,
      path: httpAdapter.getRequestUrl(request),
      timestamp: new Date().toISOString(),
      //   statusCode: httpStatus,
      ip: requestIp.getClientIp(request),
      exception: exception['name'],
      error: exception['response'] || 'Internal Server Error',
    };
    this.logger.error('[toimc]', responseBody); //加了一个错误的日志
    httpAdapter.reply(response, responseBody, httpStatus);
  }
}
  • 按照官方文档的过滤器使用时会报错,根据提示改成如下则可以成功在收到错误请求时通过过滤器报错
//main.ts
import { NestFactory, HttpAdapterHost } from '@nestjs/core';
//....
async function bootstrap() {
  //....
  const app = await NestFactory.create(AppModule, {
    // logger: ['error', 'warn'],
    // 2.配置nestjs logger为winston
    logger: logger,
  });
 // const { httpAdapter } = app.get(HttpAdapterHost);官方提供的写法会报类型错误
  const httpAdapter = app.get(HttpAdapterHost);

  app.useGlobalFilters(new AllExceptionsFilter(logger, httpAdapter)); //全局过滤器只允许提供一个
  await app.listen(3000);
}
bootstrap();
  • 发送一个路径错误的请求,可以看到目标目录下产生错误日志表示日志模块替换并且成功使用!

  • 以上分散步骤有不少根据教程和官方文档的案例直接配置,实际大量参数写在main.ts显然不合适,我们将以上逻辑挪到创建好的logs模块中:
  • 值得一提的是之前参照的是nest-winston中 (Replacing the Nest logger (also for bootstrapping))是直接在main.ts配置的过程
  • 重新用自己logs模块替换nest内置logger模块则参考其中标题为(Replacing the Nest logger)的部分,现在代码如下:
// logs.module.ts
import { Module } from '@nestjs/common';
import { WinstonModule, WinstonModuleOptions } from 'nest-winston';
import { ConfigService } from '@nestjs/config';
import * as winston from 'winston';
import { Console } from 'winston/lib/winston/transports';
import { utilities } from 'nest-winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import { LogEnum } from 'src/enum/config.enum';
import { LogsController } from './logs.controller';
import { LogsService } from './logs.service';

import { join } from 'path';

function createDailyRotateTrasnport(level: string, filename: string) {
  return new DailyRotateFile({
    level,
    dirname: join(process.cwd(), 'logs'),
    filename: `${filename}-%DATE%.log`,
    datePattern: 'YYYY-MM-DD-HH',
    zippedArchive: true,
    maxSize: '20m',
    maxFiles: '7d',
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.simple(),
    ),
  });
}
@Module({
  imports: [
    WinstonModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => {
        const timestamp = configService.get(LogEnum.TIMESTAMP) === 'true';
        const conbine = [];
        if (timestamp) {
          conbine.push(winston.format.timestamp());
        }
        conbine.push(utilities.format.nestLike());
        const consoleTransports = new Console({
          level: configService.get(LogEnum.LOG_LEVEL) || 'info',
          format: winston.format.combine(...conbine),
        });

        return {
          transports: [
            consoleTransports,
            ...(configService.get(LogEnum.LOG_ON)
              ? [
                  createDailyRotateTrasnport('info', 'application'),
                  createDailyRotateTrasnport('warn', 'error'),
                ]
              : []),
          ],
        } as WinstonModuleOptions;
      },
    }),
  ],
  controllers: [LogsController],
  providers: [LogsService],
})
export class LogsModule {}
//main.ts
import { NestFactory, HttpAdapterHost } from '@nestjs/core';
import { AppModule } from './app.module';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
// import 'winston-daily-rotate-file';
import { AllExceptionsFilter } from './filters/all-exception.filter';
// import { format } from 'path';
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {});
  app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));

  const httpAdapter = app.get(HttpAdapterHost);

  app.useGlobalFilters(
    new AllExceptionsFilter(app.get(WINSTON_MODULE_NEST_PROVIDER), httpAdapter),
  ); //全局过滤器只允许提供一个
  await app.listen(3000);
}
bootstrap();

 

 

 

 

posted on 2024-07-03 02:08  小二上酒~  阅读(21)  评论(0编辑  收藏  举报