基于 Winston应用日志服务
前言
后端服务中请求日志与错误记录是不可或缺的一环,对错误排查和保障应用运行稳定性具有积极意义。综合 GitHub 活跃度和 Nest 官方推荐的因素,决定将 Winston 作为 Nest 应用的日志服务模块,主要实现的功能是日志保存到本地文件,并根据日期自动创建新文件,自动归档等
日志等级
log: 通用日志,一般在控制台输出。warn:警告日志,例如调用一些过时方法。error: 错误日志,例如连接数据库异常等等。方便我们定位问题,给出友好提示。debug: 调试日志,例如加载数据日志。方便我们开发。verbose: 详细日志,所有的操作与详细信息。
日志记录位置
控制台日志:方便调试用文件日志:方便回溯和追踪(24消失滚动)数据库日志:敏感操作、敏感数据
安装相关依赖
npm i -S nest-winston winston winston-daily-rotate-file
- winston
- winston-daily-rotate-file: 主要功能是可以根据日期、大小限制轮换日志,可以根据计数或已用天数删除旧日志。
创建日志服务文件 logger.module.ts 与 logger.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);
}
全局过滤器输出日志
/*
* @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,
});
}
}
- 根目录生成logs日志文件
github
项目地址: nest_vhen_blog