even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

如果需要winston作为日志工具可以参看下面仓库

 nest.js的服务端的仓库地址: https://github.com/yufengctbu/nest-service.git  (目前还在整理中)

1、安装

yarn add log4js -S

2、配置

 a、创建日志模块

// 创建日志模块
nest g module /lib/log4js

// 在log4js文件夹下创建constants.ts存放常量


// constants.ts内容如下

export const LOG4JS_PROVIDER = 'LOG4JS_PROVIDER';
export const LOG4JS_CONFIG = 'LOG4JS_CONFIG';

export const LoggerScope = 'Log4jsModule';

b、创建log4js的配置文件接口

export interface Log4jsConfig {
/**
     * 日志存放路径
     * @default "logs/http"
     * */
    filename?: string;

    /**
     * 文件名规则,默认 'yyyy-MM-dd.log'
     * The following strings are recognized in the pattern:
     *  - yyyy : the full year, use yy for just the last two digits
     *  - MM   : the month
     *  - dd   : the day of the month
     *  - hh   : the hour of the day (24-hour clock)
     *  - mm   : the minute of the hour
     *  - ss   : seconds
     *  - SSS  : milliseconds (although I'm not sure you'd want to roll your logs every millisecond)
     *  - O    : timezone (capital letter o)
     */
    fileNameSuffixPattern?: string;

    /**
     *
     * The number of old files that matches the pattern to keep (excluding the hot file).
     * @default 90
     */
    numBackups?: number;
}

注意:以上只是随手写了些配置,如果需要其他配置的,可以添加

c、创建log4js的服务

nest g service /lib/log4js

import { ConfigService } from '@nestjs/config';
import { NextFunction, Request, Response } from 'express';
import { Injectable, Logger } from '@nestjs/common';
import * as log4js from 'log4js';

import { Log4jsConfig } from './log4js.interface';
import { LOG4JS_DEFAULT_HTTP_FILENAME } from './log4js.contants';

@Injectable()
export class Log4jsService extends Logger {
  public constructor(private readonly log4jsConfig: Log4jsConfig, private readonly configService: ConfigService) {
    super();
    this.initialize();
  }

  private initialize(): void {
    // 如果全局配置是否需要在控制台中输出错误日志console
    const errorConfig = this.configService.get('log.errorConsole') ? ['ruleFile', 'ruleConsole'] : ['ruleFile'];

    log4js.configure({
      appenders: {
        ruleFile: {
          type: 'dateFile',
          // 模式是否包含在当前日志文件名中,默认为false
          alwaysIncludePattern: true,
          // 文件写入路径
          filename: `logs/${this.log4jsConfig.filename || LOG4JS_DEFAULT_HTTP_FILENAME}.log`,

          // 文件名日期格式,例如:'http.2023-04-23.log'
          pattern: 'yyyy-MM-dd',
          // 保留多少天以前的日志文件
          numBackups: this.log4jsConfig.numBackups || 30,

          flags: 'a', // 打开文件时使用的标志。默认值是'a'

          encoding: 'utf-8', // 日志文件编码格式,默认是utf-8

          // 是否对超过保留期限的日志文件进行压缩(gzip)
          compress: true,

          keepFileExt: true, // 是否保留文件扩展名,设置为true有助于区分不同的日志文件
        },
        ruleConsole: { type: 'stdout' }, // 在控制台输出
        // 如果后续需要日志分文件,如错误日志,info日志,可以再定义类型
      },
      categories: {
        default: { appenders: ['ruleFile'], level: 'info' },
        console: { appenders: ['ruleConsole'], level: 'info' },
        warn: { appenders: ['ruleConsole'], level: 'warn' },
        error: { appenders: errorConfig, level: 'error' },
      },
    });
  }

  // 格式化log并且写入文件
  public useLogger(req: Request, res: Response, next: NextFunction) {
    log4js.connectLogger(log4js.getLogger('default'), {
      level: 'info',
      format: (request: Request, response: Response, format: (str: string) => string) => {
        /**
         * :method: HTTP 请求方法(例如 GET、POST 等)
         * :url: 请求的 URL
         * :status: 响应状态码(例如 200、404、500 等)
         * :response-time: 响应时间(以毫秒为单位)
         * :referrer: 请求来源网址
         * :http-version: HTTP 版本
         * :remote-addr: 发送请求的客户端 IP 地址
         * :user-agent: 发送请求的客户端 User-Agent
         * :content-length: 响应内容长度
         */
        const requestId = request.requestId;

        return format(`【requestId: ${requestId}】\n Ip: :remote-addr \n Method-Code: :method :status \n Url: ${decodeURI(
          request.url,
        )} \n Param: ${JSON.stringify(req.params)} \n Query: ${JSON.stringify(req.query)} \n Body: ${JSON.stringify(
          req.body,
        )} \n Headers: ${JSON.stringify(req.headers)} \n Client: :user-agent \n ${'='.repeat(20)}
            `);
      },
    })(req, res, next);
  }

  // 常规的log只输出到控制台
  public log(message: any, context?: string): void {
    const logger = log4js.getLogger(context || 'console');

    logger.info(message);
  }

  // 常规的warn, 输出到控制台
  public warn(message: any, context?: string): void {
    const logger = log4js.getLogger(context || 'warn');

    logger.warn(message);
  }

  // 进行错误日志打印, 输出控制台, 并且写入文件
  public error(message: any, trace?: string, context?: string): void {
    const logger = log4js.getLogger(context || 'error');

    logger.error(message, trace);
  }
}

d、创建日志的provider

import { Log4jsService } from './log4js.service';
import { Log4jsConfig } from './interfaces';
import { Provider } from '@nestjs/common';

import { LOG4JS_PROVIDER, LOG4JS_CONFIG } from './log4js.constants';

export const log4jsProvider: Provider = {
    provide: LOG4JS_PROVIDER,
    useFactory: (config: Log4jsConfig): Log4jsService => {
        return new Log4jsService(config);
    },
    inject: [{ token: LOG4JS_CONFIG, optional: true }],  // 这里引入 的是key为LOG4JS_CONFIG的服务
};

e、配置log4js.modules.ts如下

import { DynamicModule, Global, Module } from '@nestjs/common';

import { log4jsProvider } from './log4js.providers';
import { Log4jsConfig } from './interfaces';
import { LOG4JS_CONFIG } from './log4js.constants';

/**
 * 目前仅支持将用户请求写入日志,基于 log4js
 */
@Global()
@Module({
    providers: [log4jsProvider],
    exports: [log4jsProvider],
})
export class Log4jsModule {
    public static withConfig(config: Log4jsConfig): DynamicModule {
        const providers = [{ provide: LOG4JS_CONFIG, useValue: config }];  // 这是用LOG4JS_CONFIG常量做为传入的config的key,方便调用, 与provider中的一一对应

        return {
            module: Log4jsModule,
            providers: providers,
            exports: providers,
        };
    }
}

f、在log4js中创建中间件

nest g  middleware /lib/log4js

import { Inject, Injectable, NestMiddleware } from '@nestjs/common';

import { Request, Response, NextFunction } from 'express';

import { LOG4JS_PROVIDER } from './log4js.constants';
import { Log4jsService } from './log4js.service';

@Injectable()
export abstract class Log4jsMiddleware implements NestMiddleware {
    constructor(@Inject(LOG4JS_PROVIDER) private readonly log4js: Log4jsService) {}  //注入log4的provider,方便调用

    use(req: Request, res: Response, next: NextFunction) {
        this.log4js.useLogger(req, res, next);
    }
}

通过中间件的配置,这里就可以收集到所有请求的参数

在app.use中进行全局配置,或者在app.module.ts中进行配置使用

g、在拦截器中配置log4js,这样就可以收集到所有的成功请求的响应

import { Log4jsService } from '@app/libs/log4js/log4js.service';
import { formatResponseData } from '@app/utils/response.helper';
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, map } from 'rxjs';

@Injectable()
export class TransformInterceptor implements NestInterceptor {
    public constructor(private readonly log4js: Log4jsService) {}

    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const caller = next.handle();

        // 暂时不需要提供成功请求的log,如果有需求,可以添加

        return caller.pipe(
            map((data: any) => {
                const requestId = context.switchToHttp().getRequest().requestId;
                return formatResponseData(data, requestId);
            }),
        );
    }
}

这个按需求配置, 注意这里的constructor中的参数,需要传入,所以在main.ts中需要进行传入

// 配置全局的拦截器
app.useGlobalInterceptors(new TransformInterceptor(app.get(LOG4JS_PROVIDER)));

这里的获取方法用的是app.get()方法,只要有在全局注册,那么就可以进行获取

h、在ExceptionFilter中进行配置,这样就可以获取所有的错误日志

import { Log4jsService } from '@app/libs/log4js/log4js.service';
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';
import { CustomException } from './fail-operation.exception';

@Catch()
export class FailExceptionFilter implements ExceptionFilter {
    public constructor(private readonly log4js: Log4jsService) {}

    catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse<Response>();
        const request = ctx.getRequest<Request>();

        const status = exception.getStatus
            ? exception.getStatus()
            : HttpStatus.INTERNAL_SERVER_ERROR;

        //@ts-ignore
        const requestId = request.requestId;

        this.log4js.logError(`【requestId: ${requestId}】\n message: ${
            exception.message
        } \n stack: ${exception.stack} \n ${'='.repeat(20)}
        `);

        const res = exception.getResponse
            ? exception.getResponse()
            : {
                  statusCode: status,
                  message:
                      exception instanceof CustomException
                          ? exception.message
                          : 'Internal server error',
              };

        Object.assign(res, { requestId });

        response.status(status).json(res);
    }
}

注意该方法也需要在main.ts中进行参数传入

app.useGlobalFilters(new FailExceptionFilter(app.get(LOG4JS_PROVIDER)));

这个时候当有请求产生,就会在指定的日志文件夹下生成一个日志记录, 如果有需求还可以制作些日志展示下载页面,根据requestId查询日志的页面

 

posted on 2022-05-22 23:30  even_blogs  阅读(1875)  评论(4编辑  收藏  举报