从零开始搭建nestjs项目-2-日志管理(winston)

工具版本

  1. nodejs:20.9.0
  2. npm:10.1.0
  3. nestjs:10.0.0

安装相关包

pnpm i winston nest-winston winston-daily-rotate-file

配置日志

日志配置会用到环境变量,具体怎么请参考从零开始搭建nestjs项目-1-环境配置 - 掘金 (juejin.cn)

新建日志的配置文件(/src/config/log.config.ts):

import { WinstonModuleOptions } from "nest-winston"
import { transports, format } from "winston";

const env = process.env.NODE_ENV || 'development'
import "winston-daily-rotate-file";

export const winstonModuleOptions: WinstonModuleOptions = {
    transports: (() => {
        //开发环境下输出到控制台
        if (env === 'development') {
            return [new transports.Console()]
        }

        if (env === 'production') {
            return [
                // 记录错误日志
                new transports.DailyRotateFile({
                    level: "error",
                    dirname: `logs/errors`,
                    filename: `%DATE%-error.log`,
                    datePattern: "YYYY-MM-DD",
                    maxSize: "20m"
                }),
                new transports.DailyRotateFile({
                    dirname: `logs/logs`,
                    filename: `%DATE%-combined.log`,
                    datePattern: "YYYY-MM-DD",
                    maxSize: "20m",
                    format: format.combine(
                        format((info) => {
                            if (info.level === "error") {
                                return false; // 过滤掉'error'级别的日志
                            }
                            return info;
                        })()
                    )
                })
            ]
        }
        return []
    })()
}

app.module.ts中

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config'
import { WinstonModule } from 'nest-winston';
import { winstonModuleOptions } from './config/log.config';

@Module({
  imports: [
    // 环境变量配置
    ConfigModule.forRoot({
      isGlobal: true,
      // 指定存储环境变量的文件, 靠前的文件拥有较高的优先级
      envFilePath: [`.env.${process.env.NODE_ENV || 'development'}`, '.env'],
    }),

    // 日志
    WinstonModule.forRoot(winstonModuleOptions),

  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

使用

在服务中注入即可:@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger
示例:

import { Inject, Injectable } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';

@Injectable()
export class AppService {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger
  ) {
    this.logger.error('demo')
  }
}

利用nest的全局异常处理机制记录日志

异常过滤器如何使用请参考异常过滤器 | NestJS 中文网 (nodejs.cn)

新建过滤器

nest g f filters/all-exception

我这里只记录了http的异常日志

import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter, HttpException, Inject } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import * as requestIp from 'request-ip'

@Catch()
export class AllExceptionFilter implements ExceptionFilter {

  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger

  ) { }

  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    // 如果是http异常
    if (exception instanceof HttpException) {
      const status = exception.getStatus();
      const results = exception.getResponse() as any;
      const code = results.statusCode;
      // 返回的对象
      const jsonData = {
        code: code,
        message: results.message,
        data: null
      };
      // 参数校验错误,默认都是BadRequestException
      const isArrayMessage = Array.isArray(results.message);
      const isValidationError =
        isArrayMessage && typeof results.message[0] === "string" && results.message[0].includes("⓿");
      if (exception instanceof BadRequestException && isValidationError) {
        const message: Array<{ field: string; message: Array<string> }> = [];
        results.message.forEach((item) => {
          const [key, val] = item.split("⓿") as [string, string];
          const findData = message.find((item) => item.field === key);
          if (findData) {
            findData.message.push(val);
          } else {
            message.push({ field: key, message: [val] });
          }
        });
        jsonData.message = message;
      }

      // // 记录日志
      const { method, originalUrl, body, query, params } = request;
      this.logger.error("HttpException", {
        res: {
          code,
          status,
          message: jsonData.message
        },
        req: {
          method,
          url: originalUrl,
          body,
          query,
          params,
          ip: requestIp.getClientIp(request),
          timestamp: new Date().toISOString()
        }
      });
      return response.status(status).json(jsonData);
    }

    return response.status(500).json({ code: 500, msg: '服务器内部异常',success:false });
  }
}

在app.module.ts中配置过滤器,使全局使用

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config'
import { WinstonModule } from 'nest-winston';
import { winstonModuleOptions } from './config/log.config';
import { APP_FILTER } from '@nestjs/core';
import { AllExceptionFilter } from './filters/all-exception/all-exception.filter';

@Module({
  //...省略的代码
  
  
  providers: [
    AppService,
    {
      provide: APP_FILTER,
      useClass: AllExceptionFilter
    },
  ],
})
export class AppModule { }

结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值