nest学习笔记(7) :日志模块
- 完全禁用日志
- 指定日志系统详细水平(例如,展示错误,警告,调试信息等)
- 覆盖默认日志记录器的时间戳(例如使用 ISO8601 标准作为日期格式)
- 完全覆盖默认日志记录器
- 通过扩展自定义默认日志记录器
- 使用依赖注入来简化编写和测试你的应用
- 你也可以使用内置日志记录器,或者创建你自己的应用来记录你自己应用水平的事件和消息。
更多高级的日志功能,可以使用任何 Node.js
日志包,比如Winston或Pino,来生成一个完全自定义的生产环境水平的日志系统。
日志等级
Log
: 通用日志,按需进行记录(打印)
Warning
:警告日志,比如: 尝试多次进行数据库操作
Error
:严重日志,比如:数据库异常
Debug
: 调试日志,比如:加载数据日志
Verbose
:详细日志,所有的操作与详细信息(非必要不打印)
功能分类日志
- 错误日志
->
方便定位问题,给用户友好的提示
- 调试日志
->
方便开发
- 请求日志
->
记录敏感行为
日志记录位置
- 控制台日志
->
方便监看(调试用)
- 文件日志
->
方便回溯与追踪(24小时滚动)
- 数据库日志
->
敏感操作、敏感数据记录
Nest
中记录日志
Nest
内置Logger
模块
nest
为我们内置了一个日志模块
,如果不将日志数据存入文件的话基本上使用这个模块是完全足够的
1. 全局使用(在main.ts
中)
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module';
async function bootstrap() { const app = await NestFactory.create(AppModule, { logger: ['error', 'warn'], }); await app.listen(3000); } bootstrap();
|
2. 在其他模块中使用
import { Controller, Get, Inject, Logger, } from '@nestjs/common'; import { BoysService } from './boys.service';
@Controller('boys') export class BoysController { private logger = new Logger('模块1');
constructor( @Inject('boys') private BoysService: BoysService, ) {
this.logger.log('userController init !!!!'); }
@Get('/user_profile') getUser_Profile(): any { this.logger.log('请求发出...') return this.BoysService.findUser_Profile(1); } }
|
结果展示
使用第三方插件Pino
1. 安装
2. 使用
import { Module, NestModule, MiddlewareConsumer, RequestMethod, } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';
@Module({ imports: [ TypeOrmModule.forFeature([Boys, User, Logs, Profile]), LoggerModule.forRoot() ],
}
|
import { Controller, Get, } from '@nestjs/common'; import { BoysService } from './boys.service'; import { Logger } from 'nestjs-pino';
@Controller('boys') export class BoysController {
constructor( private logger: Logger, ) {
this.logger.log('userController init !!!!'); }
@Get('/user_profile') getUser_Profile(): any { return this.BoysService.findUser_Profile(1); } }
|
结果展示:
设置美化日志输出格式以及保存方式配置
- 由上图我们可以看出,原生的
pino
输出的日志信息是非常的丑陋的,因此我们可以使用它的配套插件,将整体的日志格式美化一下
1. 安装
npm i pino-pretty npm i pino-roll
|
2. 配置(app.modules.ts
)
import { Module } from '@nestjs/common'; import { BoysModule } from './boys/boys.module';
import { LoggerModule } from 'nestjs-pino'; import { join } from 'path';
const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;
@Module({ imports: [ LoggerModule.forRoot({ pinoHttp: { transport: process.env.NODE_ENV === 'development' ? { target: 'pino-pretty', options: { colorize: true, }, } : { target: 'pino-roll', options: { file: join('logs', 'log.txt'), frequency: 'daily', size: '10M', mkdir: true, }, }, }, }), BoysModule, ], controllers: [], providers: [], }) export class AppModule {}
|
结果展示:
使用第三方插件winston
- 这同样是一个第三方的日志插件,与
pino
一样,但是它可以自定义日志记录的模板,不考虑性能的话更推荐使用它
- 官方文档
异常处理-异常过滤器(重点)
我们在nodejs
开发中经常会遇见接口报错的情况出现,那么此时我们常用的解决方法就是在对应的就口中做try…catch
,但是当接口非常的多的时候,这种方法的效率是非常的低的,由此Nestjs
官方为我们提供了一个解决方案,那就设置一个全局的异常捕获过滤器
官方文档
由上图可见,所有的异常处理都会响应给Nest
内置的异常处理器,作统一的消息返回,下面讲一下Nest
中一些内置的异常类
@Get('exception_test') exception_Test(): any {
throw new ForbiddenException('测试异常!!!'); }
|
开箱即用,此操作由内置的全局异常过滤器执行,该过滤器处理类型 HttpException(及其子类)的异常。每个发生的异常都由全局异常过滤器处理, 当这个异常无法被识别时 (既不是 HttpException 也不是继承的类 HttpException ) , 用户将收到以下 JSON 响应:
{ "statusCode": 500, "message": "Internal server error" }
|
在项目中构建全局的异常捕获处理器
- 首先在项目的根目录(
src
)下创建一个新的文件夹为logs
,再在其下面创建一个http-exception.filter.ts
的文件用于配置全局的http异常捕获处理过滤器
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, } from '@nestjs/common';
@Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception.getStatus();
response.status(status).json({ code: status, timestamp: new Date().toISOString(), path: request.url, method: request.method, message: exception.message || HttpException.name, }); } }
|
import { HttpExceptionFilter } from './filter/http-exception.filter';
app.useGlobalFilters(new HttpExceptionFilter());
|
结合 pino
将日志数据导出至文件中
- 基本与上面是一致的,只不过是将原来的全局过滤器从
main.ts
中挂起移动到app.modules.ts
中挂起而已,具体实现步骤如下:
- 在
NestJS
的根模块(通常是app.module.ts
)中导入PinoLoggerModule
并将其添加到imports
数组中。这将启用Pino
日志记录器:
import { Module } from '@nestjs/common'; import { PinoLoggerModule } from 'nestjs-pino';
@Module({ imports: [PinoLoggerModule.forRoot()], }) export class AppModule {}
|
- 创建一个全局异常过滤器(例如
global-exception.filter.ts
),并实现ExceptionFilter
接口。在catch()
方法中,你可以使用this.logger.error()
方法将异常信息记录到日志文件中:
import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common'; import { PinoLogger } from 'nestjs-pino';
@Catch() export class GlobalExceptionFilter implements ExceptionFilter { constructor(private readonly logger: PinoLogger) {}
catch(exception: any, host: ArgumentsHost) { this.logger.error(exception.message, exception.stack); } }
|
- 在
NestJS
的根模块中将全局异常过滤器添加到providers
数组中,并使用useClass
属性指定它的类名:
import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { GlobalExceptionFilter } from './global-exception.filter';
@Module({ providers: [ { provide: APP_FILTER, useClass: GlobalExceptionFilter, }, ], }) export class AppModule {}
|
- 现在,当你的应用程序抛出异常时,全局异常过滤器将捕获异常并将其记录到日志文件中。你可以在
Pino
的配置中指定日志文件的路径(app.modules.ts
)和其他选项。例如,你可以在main.ts
文件中进行配置:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { Logger } from 'nestjs-pino';
async function bootstrap() { const app = await NestFactory.create(AppModule, { logger: new Logger({ pinoHttp: { }, }), }); await app.listen(3000); } bootstrap();
|
拓展知识 - 全局错误异常捕获处理过滤器(包括socket等报错)
import { ExceptionFilter, HttpAdapterHost, HttpException, HttpStatus, LoggerService, } from '@nestjs/common'; import { ArgumentsHost, Catch } from '@nestjs/common';
import * as requestIp from 'request-ip';
@Catch() export class AllExceptionFilter implements ExceptionFilter { constructor( private readonly logger: LoggerService, private readonly httpAdapterHost: HttpAdapterHost, ) {} catch(exception: unknown, host: ArgumentsHost) { const { httpAdapter } = this.httpAdapterHost; const ctx = host.switchToHttp(); const request = ctx.getRequest(); const response = ctx.getResponse();
const httpStatus = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = { headers: request.headers, query: request.query, body: request.body, params: request.params, timestamp: new Date().toISOString(), ip: requestIp.getClientIp(request), exceptioin: exception['name'], error: exception['response'] || 'Internal Server Error', };
this.logger.error('[lam]', responseBody); httpAdapter.reply(response, responseBody, httpStatus); } }
|
const httpAdapter = app.get(HttpAdapterHost); app.useGlobalFilters(new AllExceptionFilter(logger, httpAdapter));
|