Nest.js从0到1搭建博客系统---基于 Winston应用日志服务(6)

529 阅读4分钟

基于 Winston应用日志服务

前言

后端服务中请求日志与错误记录是不可或缺的一环,对错误排查和保障应用运行稳定性具有积极意义。综合 GitHub 活跃度和 Nest 官方推荐的因素,决定将 Winston 作为 Nest 应用的日志服务模块,主要实现的功能是日志保存到本地文件,并根据日期自动创建新文件,自动归档等

日志等级

  • log: 通用日志,一般在控制台输出。
  • warn:警告日志,例如调用一些过时方法。
  • error: 错误日志,例如连接数据库异常等等。方便我们定位问题,给出友好提示。
  • debug: 调试日志,例如加载数据日志。方便我们开发。
  • verbose: 详细日志,所有的操作与详细信息。

日志记录位置

  • 控制台日志:方便调试用
  • 文件日志:方便回溯和追踪(24消失滚动)
  • 数据库日志:敏感操作、敏感数据

安装相关依赖

npm i -S nest-winston winston winston-daily-rotate-file

创建日志服务文件 logger.module.tslogger.service.ts

nest g mo logger
nest g s logger --no-spec
  • 公共配置文件dev.yml
# 项目配置
APP:
 prefix: '/api'
 host: 'localhost'
 port: 3000
# 日志
LOG:
 level: 'info'
 on: true
 dir: 'logs'
 timestamp: true
 # 数据库
DB:
 mysql:
   type: mysql # 数据库链接类型
   host: localhost
   port: 3306
   username: 'root' # 数据库链接用户名
   password: 'root' # 数据库链接密码
   database: 'nest_vhen_blog' # 数据库名
   logging: true # 数据库打印日志
   synchronize: true # 是否开启同步数据表功能
   autoLoadEntities: true # 是否自动加载实体
  • logger.module.ts
/*
 * @Author: vhen
 * @Date: 2023-12-29 20:02:41
 * @LastEditTime: 2023-12-30 15:51:35
 * @Description: 现在的努力是为了小时候吹过的牛逼!
 * @FilePath: \nest-vhen-blog\src\common\libs\logger\logger.module.ts
 * 
 */
import { Module } from '@nestjs/common';
import * as winston from 'winston';
import { WinstonModule, WinstonModuleOptions, utilities as nestWinstonModuleUtilities, } from 'nest-winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import { Console } from 'winston/lib/winston/transports';
import { ConfigService } from '@nestjs/config';
import { LoggerService } from './logger.service';
import { join } from 'path';
function createDailyRotateTrasnport(level: string, filename: string, dir: string) {
    console.log('ss', join(process.cwd(), dir));
    return new DailyRotateFile({
        level, // 日志等级,不设置所有日志将在同一个文件
        dirname: join(process.cwd(), dir),  // 日志文件文件夹
        filename: `${filename}-%DATE%.log`, // 日志文件名 %DATE% 会自动设置为当前日期
        datePattern: 'YYYY-MM-DD-HH', // 日期格式
        zippedArchive: true, // 压缩文档,用于定义是否对存档的日志文件进行 gzip 压缩 默认值 false
        maxSize: '10m', // 设置日志文件的最大大小,m 表示 mb 。可以是bytes、kb、mb、gb
        maxFiles: '7d', // 保留日志文件的最大天数,此处表示自动删除超过 7 天的日志文件。
        format: winston.format.combine(
            winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss', }),
            winston.format.json(),
        ),
    });
}

@Module({
    imports: [
        WinstonModule.forRootAsync({
            inject: [ConfigService],
            useFactory: (configService: ConfigService) => {
                const timestamp = configService.get('LOG').timestamp === 'true';
                const conbine = [];
                if (timestamp) {
                    conbine.push(winston.format.timestamp());
                }
                conbine.push(nestWinstonModuleUtilities.format.nestLike());
                const consoleTransports = new Console({
                    level: configService.get('LOG').level || 'info',
                    format: winston.format.combine(...conbine),
                });
                const writeTransport = configService.get('LOG').on ? [
                    createDailyRotateTrasnport('info', 'application', configService.get('LOG').dir),
                    createDailyRotateTrasnport('warn', 'error', configService.get('LOG').dir),
                ]
                    : []
                return {
                    transports: [
                        consoleTransports,
                        ...writeTransport,
                    ],
                } as WinstonModuleOptions;
            },
        }),
    ],
    providers: [LoggerService],
    exports: [LoggerService],
})
export class LoggerModule { }
  • logger.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class LoggerService { }
  • app.module.ts
import { Module, Logger } from '@nestjs/common';
import { LoggerModule } from '@/common/libs/logger/logger.module';
@Module({
    imports: [LoggerModule],
    providers:[Logger]
    exports: [Logger],
})
export class AppModule { }
  • main.ts全局注册
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
const flag: boolean = config.get('LOG').on;
flag && app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
  • user.controller.ts 使用
import { Controller, Inject, Get, Post, Body, Query, Patch, Param, Delete, HttpCode, HttpStatus, LoggerService } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { UserService } from './user.service';
@Controller({
  path: "user",
  version: '1'
})
export class UserController {
    constructor(private readonly userService: UserService, @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService) { }
}
@Get("getUserList")
findAll(@Query() query: { keyWord: string, page: number, pageSize: number }) {
    this.logger.error('用户控制器初始化...');
    return this.userService.findAll(query);
}

image.png

全局过滤器输出日志

/*
 * @Author: vhen
 * @Date: 2023-12-24 16:05:52
 * @LastEditTime: 2023-12-30 15:05:34
 * @Description: 现在的努力是为了小时候吹过的牛逼!
 * @FilePath: \nest-vhen-blog\src\common\filters\exceptions\http-exception.filter.ts
 * 
 */
import {
    Inject,
    ArgumentsHost,
    Catch,
    ExceptionFilter,
    HttpException,
    HttpStatus,
    LoggerService
} from '@nestjs/common';
import { Request, Response } from 'express';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
    constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: LoggerService) { }
    async catch(exception: HttpException, host: ArgumentsHost) {
        const request = host.switchToHttp().getRequest<Request>();
        const response = host.switchToHttp().getResponse<Response>();
        this.logger.error(exception.message, exception.stack);
        // http异常处理
        response.status(HttpStatus.NOT_FOUND).json({
            statusCode: HttpStatus.NOT_FOUND,
            timestamp: new Date().getTime(),
            path: request.url,
            message: exception.getResponse() || exception.message || exception.name,
        });
    }
}

image.png

  • 根目录生成logs日志文件

image.png

github

项目地址: nest_vhen_blog