NestJS 进阶:中间件、错误处理与日志记录的完美结合,以及对接第三方监控平台
NestJS 进阶:中间件、错误处理与日志记录的完美结合,以及对接第三方监控平台
一、 NestJS 中间件:请求的“守门员”
1.1 创建中间件
1.2 应用中间件
二、 NestJS 错误处理:系统的“安全网”
2.1 内置异常过滤器
2.2 自定义异常过滤器
三、 NestJS 日志记录:应用的“黑匣子”
3.1 内置 Logger
3.2 使用第三方日志库
四、对接第三方监控平台
4.1 安装 Sentry SDK
4.2 初始化 Sentry
4.3 在代码中捕获并上报异常
五、总结
NestJS 进阶:中间件、错误处理与日志记录的完美结合,以及对接第三方监控平台
大家好,我是你们的“代码搬运工”小猿。今天咱们来聊聊 NestJS 开发中至关重要的几个环节:中间件、错误处理和日志记录。更进一步,我们还会探讨如何将这些环节与第三方监控平台无缝对接,打造一个稳定、可靠、易于维护的后端应用。
作为一名有经验的 NestJS 开发者,你肯定深知这三者的重要性。中间件如同请求的“守门员”,负责请求的预处理、验证、修改等;错误处理是系统的“安全网”,确保应用在遇到异常时能够优雅地降级,而不是直接崩溃;日志记录则是应用的“黑匣子”,记录着系统运行的点点滴滴,为问题排查、性能优化提供宝贵的数据支持。而将这些数据接入第三方监控平台,更可以让我们实时掌握应用的健康状况,及时发现并解决潜在问题。
那么,如何将这三者完美结合,并与第三方监控平台对接呢?别急,接下来我会一步步为你揭晓。
一、 NestJS 中间件:请求的“守门员”
NestJS 的中间件机制非常强大,它允许你在请求到达控制器之前或响应返回给客户端之前执行自定义逻辑。常见的应用场景包括:
- 请求日志记录: 记录请求的 URL、方法、参数、来源 IP 等信息。
- 用户身份验证: 验证请求头中的 token,判断用户是否已登录。
- 请求参数校验: 校验请求参数的合法性,例如是否为空、格式是否正确等。
- 跨域资源共享 (CORS) 设置: 配置允许跨域访问的域名、方法等。
- 响应数据格式化: 将响应数据统一格式化为 JSON 格式。
1.1 创建中间件
NestJS 中间件可以是一个类,也可以是一个函数。类中间件需要实现 NestMiddleware
接口,该接口只有一个 use
方法,该方法接收三个参数:
req
: 请求对象,包含请求的所有信息。res
: 响应对象,用于向客户端发送响应。next
: 一个函数,调用该函数将控制权交给下一个中间件或路由处理程序。
// logger.middleware.ts import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`Request... ${req.method} ${req.originalUrl}`); next(); } }
函数中间件则更为简单,直接定义一个函数即可:
// logger.middleware.ts export function logger(req: Request, res: Response, next: NextFunction) { console.log(`Request... ${req.method} ${req.originalUrl}`); next(); };
1.2 应用中间件
中间件可以在模块级别应用,也可以在控制器级别应用。在模块级别应用中间件,需要在模块的 configure
方法中使用 apply
方法:
// app.module.ts import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleware } from './logger.middleware'; import { AppModule } from './app.module'; @Module({ imports: [AppModule], }) export class ApplicationModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('*'); // 对所有路由生效 } }
在控制器级别应用中间件,可以使用 @UseInterceptors
装饰器:
// app.controller.ts import { Controller, Get, UseInterceptors } from '@nestjs/common'; import { LoggerMiddleware } from './logger.middleware'; @Controller() export class AppController { @Get() @UseInterceptors(LoggerMiddleware) //仅对该路由生效 getHello(): string { return 'Hello World!'; } }
二、 NestJS 错误处理:系统的“安全网”
在应用运行过程中,难免会遇到各种各样的错误,例如数据库连接失败、文件读取错误、网络请求超时等。NestJS 提供了一套完善的错误处理机制,可以帮助我们捕获并处理这些错误,避免应用崩溃。
2.1 内置异常过滤器
NestJS 内置了一个全局异常过滤器 HttpExceptionFilter
,它可以捕获所有继承自 HttpException
的异常,并将其转换为 HTTP 响应。例如:
import { HttpException, HttpStatus } from '@nestjs/common'; throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
这段代码会抛出一个 HTTP 403 错误,并返回一个包含错误信息的 JSON 响应。
2.2 自定义异常过滤器
如果内置的异常过滤器无法满足需求,我们可以创建自定义异常过滤器。自定义异常过滤器需要实现 ExceptionFilter
接口,该接口只有一个 catch
方法,该方法接收两个参数:
exception
: 捕获到的异常对象。host
: 一个ArgumentsHost
对象,包含请求和响应对象。
// custom-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: exception instanceof HttpException ? exception.message : 'Internal server error', }); } }
然后,我们需要在应用中注册这个自定义异常过滤器:
// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { AllExceptionsFilter } from './custom-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new AllExceptionsFilter()); // 全局注册 await app.listen(3000); } bootstrap();
三、 NestJS 日志记录:应用的“黑匣子”
日志记录是应用开发中不可或缺的一部分,它可以帮助我们:
- 调试问题: 当应用出现错误时,可以通过日志快速定位问题的原因。
- 性能分析: 通过分析日志,可以了解应用的性能瓶颈,从而进行优化。
- 安全审计: 记录用户的操作行为,可以用于安全审计。
- 业务分析: 通过分析用户行为日志,可以了解用户的需求和偏好。
3.1 内置 Logger
NestJS 内置了一个简单的 Logger,可以直接在代码中使用:
import { Logger } from '@nestjs/common'; const logger = new Logger('MyClass'); logger.log('This is a log message'); logger.error('This is an error message'); logger.warn('This is a warning message'); logger.debug('This is a debug message'); logger.verbose('This is a verbose message');
3.2 使用第三方日志库
NestJS 内置的 Logger 功能比较简单,通常我们会使用更强大的第三方日志库,例如 Winston、Pino 等。以 Winston 为例:
npm install winston
// logger.service.ts import { Injectable, LoggerService } from '@nestjs/common'; import * as winston from 'winston'; @Injectable() export class WinstonLogger implements LoggerService { private logger: winston.Logger; constructor() { this.logger = winston.createLogger({ level: 'info', // 设置日志级别 format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.Console(), // 输出到控制台 new winston.transports.File({ filename: 'combined.log' }), // 输出到文件 ], }); } log(message: string, context?: string) { this.logger.log('info', message, { context }); } error(message: string, trace?: string, context?: string) { this.logger.error(message, { trace, context }); } warn(message: string, context?: string) { this.logger.warn(message, { context }); } debug(message: string, context?: string) { this.logger.debug(message, { context }); } verbose(message: string, context?: string) { this.logger.verbose(message, { context }); } }
然后在 AppModule 中提供这个 Logger:
// app.module.ts import { Module } from '@nestjs/common'; import { WinstonLogger } from './logger.service'; @Module({ providers: [WinstonLogger], exports: [WinstonLogger], }) export class AppModule {}
在其他地方就可以注入并使用这个 Logger 了:
// app.service.ts import { Injectable } from '@nestjs/common'; import { WinstonLogger } from './logger.service'; @Injectable() export class AppService { constructor(private readonly logger: WinstonLogger) {} getHello(): string { this.logger.log('Calling getHello method'); return 'Hello World!'; } }
四、对接第三方监控平台
将日志和错误信息发送到第三方监控平台,可以更方便地进行监控和告警。常见的监控平台有 Sentry、New Relic、Datadog 等。这里以 Sentry 为例,介绍如何将 NestJS 应用与 Sentry 集成。
4.1 安装 Sentry SDK
npm install @sentry/node @sentry/tracing
4.2 初始化 Sentry
// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as Sentry from '@sentry/node'; import * as Tracing from '@sentry/tracing'; async function bootstrap() { Sentry.init({ dsn: 'YOUR_SENTRY_DSN', // 替换为你的 Sentry DSN integrations: [ // enable HTTP calls tracing new Sentry.Integrations.Http({ tracing: true }), // enable Express.js middleware tracing new Tracing.Integrations.Express({ app }), ], // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. tracesSampleRate: 1.0, }); const app = await NestFactory.create(AppModule); // The request handler must be the first middleware on the app app.use(Sentry.Handlers.requestHandler()); // TracingHandler creates a trace for every incoming request app.use(Sentry.Handlers.tracingHandler()); // The error handler must be before any other error middleware and after all controllers app.use(Sentry.Handlers.errorHandler()); await app.listen(3000); } bootstrap();
4.3 在代码中捕获并上报异常
// app.service.ts import { Injectable } from '@nestjs/common'; import * as Sentry from '@sentry/node'; @Injectable() export class AppService { getHello(): string { try { // 可能会出错的代码 } catch (error) { Sentry.captureException(error); // 上报异常 } return 'Hello World!'; } }
通过以上步骤,我们就完成了 NestJS 应用与 Sentry 的集成。当应用发生错误时,Sentry 会自动捕获并上报异常信息,我们可以在 Sentry 的控制台中查看错误详情、堆栈信息等,方便我们快速定位和解决问题。
五、总结
本文详细介绍了 NestJS 中间件、错误处理和日志记录的最佳实践,以及如何将这些环节与第三方监控平台 Sentry 集成。希望通过本文的学习,你能够对 NestJS 的这些核心功能有更深入的理解,并将其应用到实际开发中,构建出更加健壮、可靠的应用程序。
记住,良好的中间件设计、完善的错误处理机制和详细的日志记录是构建高质量应用的三大基石。而将这些数据接入第三方监控平台,则可以让我们如虎添翼,更好地掌控应用的运行状态。 还在等什么?赶快动手实践起来吧! 如果你在实践过程中遇到任何问题,欢迎随时向我提问,小猿会尽力帮助你解答。加油!
补充说明和一些容易被忽略的细节:
- 中间件的执行顺序: 中间件的执行顺序非常重要. 按照在
configure
方法中注册的顺序执行. 如果你在全局注册了一个日志中间件, 它会在所有其他中间件之前执行. 如果你有多个中间件,确保他们的执行顺序符合你的预期。 - 异常过滤器的范围: 异常过滤器可以全局注册, 也可以在控制器或方法级别注册。
@Catch()
装饰器不带参数时,会捕获所有类型的异常. 如果只想捕获特定类型的异常,可以在@Catch()
装饰器中传入异常类, 例如@Catch(HttpException)
. - 日志级别: 谨慎选择日志级别. 过多的日志会影响性能, 过少的日志则可能导致关键信息丢失. 通常,生产环境使用
info
或warn
级别,开发环境使用debug
或verbose
级别. - 日志格式: 日志格式也很重要,良好的日志格式可以方便我们快速定位问题. 推荐使用 JSON 格式, 方便日志分析工具处理. 可以在 Winston 或 Pino 中配置自定义的日志格式.
- 日志轮转: 对于长期运行的应用, 日志文件会不断增大. 需要配置日志轮转, 定期归档旧的日志文件, 防止磁盘空间被占满. 可以使用
winston-daily-rotate-file
等库来实现日志轮转. - 异步操作中的错误处理: 在异步操作中 (例如
Promise
或async/await
), 必须使用try...catch
捕获异常, 否则异常可能会被吞掉, 导致应用崩溃. 如果使用async/await
, 建议在最外层使用try...catch
,避免遗漏。 - Sentry 的配置: Sentry 的配置有很多选项, 可以根据需要进行调整. 例如,
tracesSampleRate
控制事务采样的比例,environment
可以设置环境 (例如production
、staging
、development
),release
可以设置版本号. 详见 Sentry 官方文档。 - 错误分组和聚合: 监控平台 (如 Sentry) 通常会自动对错误进行分组和聚合. 了解其分组规则, 可以更有效地处理错误. Sentry 会根据错误的类型和堆栈信息进行分组.
- 告警: 配置告警规则, 当错误发生时及时通知相关人员. Sentry 支持多种告警方式, 例如邮件、Slack、Webhook 等。
- 性能监控: 除了错误监控, Sentry 也支持性能监控。 可以通过
@sentry/tracing
模块,追踪请求处理时间,数据库查询时间等, 帮助你发现性能瓶颈。 - 不要在日志中记录敏感信息: 比如密码, API 密钥等. 这些信息一旦泄露, 会造成严重的安全问题. 建议使用环境变量或配置管理工具来存储敏感信息.
这些细节往往容易被忽略,但它们对于构建一个稳定、可靠、易于维护的 NestJS 应用至关重要。希望这些补充说明能够帮助你更好地理解和应用 NestJS。