6-5 作业全局异常过滤器:配合winston记录日志(全局Filters)
1. 日志模块选型与痛点分析
1.1 Winston与Pino对比
详细对比表格
特性 | Winston | Pino |
---|---|---|
配置灵活性 | ⭐⭐⭐⭐ 支持多传输器(文件、控制台、数据库等),可自定义日志格式和级别 | ⭐⭐ 基础配置简单,适合快速集成,但高级功能需插件扩展 |
日志详细度 | ⭐⭐⭐⭐ 支持daily-rotate-file ,可按日期分割日志文件,适合长期存储和分析 | ⭐⭐ 默认输出为JSON格式,适合结构化日志,但文件轮转需额外配置 |
性能 | ⭐⭐ 同步写入,性能较低,适合对实时性要求不高的场景 | ⭐⭐⭐⭐ 异步I/O优化,性能极高,适合高并发场景 |
上手难度 | ⭐⭐⭐ 需要手动配置传输器和格式,适合有一定经验的开发者 | ⭐ 开箱即用,适合快速开发和轻量级应用 |
推荐场景 | 需要详细日志分析、长期存储或自定义日志格式的项目 | 高性能轻量级应用,或需要与ELK等日志分析系统集成的场景 |
补充说明
- Winston:
- 适合需要复杂日志管理的场景,例如企业级应用或需要长期存储日志的项目。
- 支持多种日志传输器(如文件、数据库、HTTP等),灵活性高。
- 可以通过
winston-daily-rotate-file
插件实现按日期分割日志文件,便于管理和分析。
- Pino:
- 默认输出为JSON格式,非常适合与ELK(Elasticsearch、Logstash、Kibana)等日志分析系统集成。
- 异步I/O设计使其性能极佳,适合高并发或对性能敏感的应用。
- 轻量级,适合微服务或无服务器架构(Serverless)。
💡 扩展知识:
- 如果项目需要同时使用Winston和Pino,可以通过
pino-winston
适配器将两者结合,发挥各自优势。 - 对于需要实时日志监控的场景,可以集成
winston-elasticsearch
或pino-elasticsearch
插件,直接将日志发送到Elasticsearch。
1.2 当前痛点
详细痛点分析
- 手动添加try-catch:
- 每个接口都需要显式捕获异常并记录日志,导致代码冗余和维护成本高。
- 示例代码:
async getUser() { try { // 业务逻辑 } catch (error) { this.logger.error(`GetUser失败: ${error.message}`, error.stack); throw new InternalServerErrorException(); } }
typescript - 问题:重复代码多,容易遗漏异常捕获。
- 错误日志分散:
- 日志可能分散在不同文件或服务中,难以集中分析和排查问题。
- 例如:用户模块的日志在
user.log
,订单模块的日志在order.log
,排查跨模块问题需查看多个文件。
- 未捕获异常处理不足:
- 未显式捕获的异常(如未处理的Promise拒绝)可能导致应用崩溃,且无日志记录。
- 传统方式无法全局捕获这类异常。
解决方案
- 全局异常过滤器:
- 通过NestJS的
ExceptionFilter
统一捕获所有异常,减少重复代码。 - 示例:
@Catch() export class GlobalExceptionFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); this.logger.error(`[${request.method}] ${request.url}`, exception.stack); response.status(500).json({ message: 'Internal Server Error', }); } }
typescript - 优势:只需注册一次,即可覆盖所有路由和控制器。
- 通过NestJS的
💡 最佳实践:
- 结合Winston或Pino的上下文日志功能,记录请求ID、用户信息等,便于追踪问题。
- 使用
process.on('unhandledRejection')
和process.on('uncaughtException')
捕获未处理的异常。
1.3 扩展:日志记录的最佳实践
- 结构化日志:
- 使用JSON格式记录日志,包含时间戳、日志级别、请求ID、错误堆栈等字段。
- 示例(Pino):
{ "level": "error", "time": "2025-06-18T16:00:00.000Z", "msg": "GetUser失败", "stack": "Error: User not found...", "requestId": "abc123", "userId": "user-456" }
json
- 日志分级:
- 根据重要性分级:
error
(错误)、warn
(警告)、info
(信息)、debug
(调试)。 - 示例(Winston):
logger.error('Critical error', { error }); logger.warn('Deprecated API called'); logger.info('User logged in');
typescript
- 根据重要性分级:
- 敏感信息过滤:
- 避免记录密码、令牌等敏感信息。
- 示例:
const sanitizedRequest = { ...request, headers: { ...request.headers, authorization: '***' }, body: { ...request.body, password: '***' }, }; logger.error('Request failed', sanitizedRequest);
typescript
- 日志轮转与归档:
- 使用
winston-daily-rotate-file
或pino-rotating-file
实现日志按日期或大小分割。 - 配置定期清理旧日志,避免磁盘空间不足。
- 使用
💡 工具推荐:
- 日志分析:ELK Stack(Elasticsearch + Logstash + Kibana)或Grafana Loki。
- 日志监控:Sentry(错误监控)或Datadog(全栈监控)。
2. 异常过滤器核心原理
2.1 异常处理流程详解
关键流程说明:
- 请求进入:客户端发起HTTP请求到NestJS应用
- 路由处理:NestJS路由系统将请求分发到对应控制器方法
- 异常检测:
- 业务代码正常执行 → 返回正常响应
- 业务代码抛出异常 → 进入异常过滤器
- 过滤器处理:
- 调用日志模块记录错误详情(包括调用栈、请求参数等)
- 格式化错误响应(统一错误码、错误消息格式)
- 响应返回:将格式化后的错误响应返回客户端
💡 深度理解:
- 过滤器本质上是一个拦截器(Interceptor),在NestJS的AOP(面向切面编程)体系中工作
- 整个过程是同步执行的,确保错误能立即被捕获和处理
- 可以通过
host.switchToHttp()
获取底层平台(Express/Fastify)的请求/响应对象
2.2 过滤器层级结构深入
1. 路由级过滤器
@Post('login')
@UseFilters(new LoginExceptionFilter())
async login() {
// 业务逻辑
}
typescript
- 特点:
- 通过
@UseFilters()
装饰器绑定到单个路由 - 仅处理该路由方法内抛出的异常
- 适合需要特殊错误处理的敏感接口(如支付、登录)
- 通过
2. 控制器级过滤器
@Controller('users')
@UseFilters(UsersExceptionFilter)
export class UsersController {
// 所有方法共享同一个过滤器
}
typescript
- 特点:
- 作用于整个控制器的所有路由方法
- 适合按业务模块统一错误处理(如用户模块、订单模块)
- 可以通过构造函数注入服务(如Logger)
3. 全局级过滤器
// main.ts
app.useGlobalFilters(new GlobalExceptionFilter(logger));
typescript
- 特点:
- 捕获整个应用的未处理异常(包括未显式绑定的路由)
- 适合作为最后一道防线,确保所有错误都被记录
- 只能注册一个全局过滤器(可通过复合模式扩展)
执行顺序示例:
💡 最佳实践建议:
- 优先使用全局过滤器保证基础错误处理
- 对特殊业务模块使用控制器级过滤器
- 仅在极少数需要定制化处理的场景使用路由级过滤器
- 通过
extends HttpException
创建自定义业务异常类 - 在过滤器中区分处理业务异常(4xx)和系统异常(5xx)
2.3 扩展:过滤器与拦截器的区别
特性 | 异常过滤器(Exception Filter) | 拦截器(Interceptor) |
---|---|---|
触发时机 | 在异常抛出后执行 | 在请求处理前/后执行 |
主要用途 | 错误处理和日志记录 | 请求/响应转换、附加元数据 |
可访问内容 | 异常对象、请求上下文 | 请求/响应对象、执行上下文 |
执行顺序 | 最后执行(异常时) | 在管道之后、控制器之前 |
典型场景 | 统一错误响应格式 | 接口耗时统计、权限预处理 |
// 典型组合使用示例
@UseInterceptors(LoggingInterceptor)
@UseFilters(ValidationExceptionFilter)
@Controller()
export class AppController {
// ...
}
typescript
3. HTTP异常实践
3.1 基础异常抛出详解
核心用法
import { HttpException, HttpStatus } from '@nestjs/common';
// 基本形式:消息+状态码
throw new HttpException('用户无权限', HttpStatus.FORBIDDEN);
// 扩展形式:响应体对象
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'Forbidden',
message: '用户无权限',
timestamp: new Date().toISOString()
}, HttpStatus.FORBIDDEN);
typescript
最佳实践建议
- 消息国际化:
throw new HttpException( this.i18n.translate('ERROR.FORBIDDEN'), HttpStatus.FORBIDDEN );
typescript - 错误码标准化:
throw new HttpException({ code: 'AUTH_403', // 自定义错误码 message: '用户无权限' }, HttpStatus.FORBIDDEN);
typescript - 错误元数据:
throw new HttpException({ message: '参数校验失败', errors: validationErrors // 包含详细校验错误 }, HttpStatus.BAD_REQUEST);
typescript
💡 调试技巧:
- 使用
HttpException
的getResponse()
方法获取完整错误对象 - 开发环境可通过
process.env.NODE_ENV
显示完整错误堆栈
3.2 内置异常类深度解析
完整异常类列表
异常类 | 状态码 | 典型场景 |
---|---|---|
BadRequestException | 400 | 请求参数缺失/格式错误 |
UnauthorizedException | 401 | JWT令牌过期/无效 |
ForbiddenException | 403 | 用户角色权限不足 |
NotFoundException | 404 | 请求资源不存在 |
MethodNotAllowedException | 405 | 不支持的HTTP方法 |
RequestTimeoutException | 408 | 服务端处理超时 |
ConflictException | 409 | 资源冲突(如重复创建) |
GoneException | 410 | 资源已被永久删除 |
PayloadTooLargeException | 413 | 请求体过大 |
UnsupportedMediaTypeException | 415 | 不支持的媒体类型 |
InternalServerErrorException | 500 | 未捕获的服务器错误 |
NotImplementedException | 501 | 未实现的功能 |
BadGatewayException | 502 | 上游服务不可用 |
ServiceUnavailableException | 503 | 服务临时不可用 |
GatewayTimeoutException | 504 | 网关超时 |
高级用法示例
- 自定义异常扩展:
export class CustomBusinessException extends HttpException { constructor(message: string, code: string) { super({ message, code }, HttpStatus.BAD_REQUEST); } }
typescript - 异常链式处理:
try { // 业务逻辑 } catch (error) { throw new InternalServerErrorException({ originalError: error.message, reference: 'DOC-1001' // 错误文档编号 }); }
typescript - 异常元数据注入:
throw new BadRequestException('参数错误', { cause: validationErrors, description: '详细校验失败信息' });
typescript
3.3 异常响应标准化
推荐响应格式
{
"success": false,
"timestamp": "2025-06-18T16:00:00.000Z",
"code": "AUTH_403",
"message": "用户无权限",
"path": "/api/users",
"details": {
"requiredRole": "ADMIN",
"currentRole": "USER"
}
}
json
实现方式
// 在全局过滤器中格式化
response.status(status).json({
success: false,
timestamp: new Date().toISOString(),
code: exception.code || 'UNKNOWN',
message: exception.message,
path: request.url,
details: exception.details || null
});
typescript
3.4 实战:电商场景异常设计
- 库存不足异常:
export class OutOfStockException extends HttpException { constructor(productId: string) { super({ code: 'STOCK_001', message: `商品 ${productId} 库存不足`, productId }, HttpStatus.CONFLICT); } }
typescript - 支付失败异常:
throw new HttpException({ code: 'PAYMENT_402', message: '信用卡余额不足', paymentMethod: 'VISA-****-1234', amount: 199.00 }, HttpStatus.PAYMENT_REQUIRED);
typescript - 限流异常:
throw new TooManyRequestsException({ retryAfter: 60, // 60秒后重试 quota: '10次/分钟' });
typescript
💡 监控建议:
- 使用Sentry/Bugsnag捕获异常时,附加
code
字段便于分类统计 - 对4xx错误设置不同的告警级别(如403比404更紧急)
- 通过
exception.getStatus()
在日志中记录状态码分布
4. 自定义异常过滤器实现
4.1 基础过滤器结构详解
核心架构解析
import { Catch, ExceptionFilter, ArgumentsHost, HttpException } from '@nestjs/common';
@Catch() // 捕获所有类型异常
export class HttpExceptionFilter implements ExceptionFilter {
/**
* 异常处理方法
* @param exception 捕获到的异常对象
* @param host 执行上下文宿主
*/
catch(exception: unknown, host: ArgumentsHost) {
// 异常处理逻辑
}
}
typescript
关键设计要点:
- @Catch()装饰器:
- 不传参数时捕获所有异常
- 可指定特定异常类型:
@Catch(HttpException, TypeError)
- 支持继承关系:捕获父类异常时会同时捕获所有子类异常
- ExceptionFilter接口:
- 强制实现
catch(exception: T, host: ArgumentsHost)
方法 T
可以是任意类型,但通常使用HttpException | Error
- 强制实现
- 类型安全处理:
if (!(exception instanceof HttpException)) {
// 处理非HTTP异常情况
}
typescript
💡 扩展知识:
- 可通过
@Injectable()
将过滤器作为可注入服务 - 使用
APP_FILTER
令牌实现模块化注册
4.2 上下文获取深度解析
多协议支持
const ctx = host.switchToHttp(); // HTTP协议
// const wsCtx = host.switchToWs(); // WebSocket协议
// const rpcCtx = host.switchToRpc(); // RPC协议
const response = ctx.getResponse(); // Express.Response或FastifyReply
const request = ctx.getRequest(); // Express.Request或FastifyRequest
typescript
增强型上下文信息
// 获取完整请求信息
const requestInfo = {
method: request.method,
url: request.originalUrl,
headers: request.headers,
ip: request.ip,
params: request.params,
query: request.query,
body: request.body
};
// 获取异常详细信息
const exceptionDetails = {
name: exception.name,
message: exception.message,
stack: process.env.NODE_ENV === 'development' ? exception.stack : undefined,
...(exception.getResponse?.() || {})
};
typescript
状态码处理最佳实践
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
typescript
4.3 定制响应格式高级方案
标准化响应结构
interface ErrorResponse {
success: false;
timestamp: string;
code: string;
message: string;
path: string;
details?: any;
}
const responseBody: ErrorResponse = {
success: false,
timestamp: new Date().toISOString(),
code: `ERR-${status}`,
message,
path: request.url,
details: exception.details || undefined
};
response.status(status).json(responseBody);
typescript
生产环境优化
// 敏感信息过滤
const safeException = {
...exception,
stack: process.env.NODE_ENV === 'production' ? undefined : exception.stack,
config: undefined // 移除可能包含敏感信息的配置
};
// 错误分类处理
if (status >= 500) {
this.logger.error('Server Error', safeException);
} else {
this.logger.warn('Client Error', safeException);
}
typescript
4.4 完整实现示例
import {
Catch,
ExceptionFilter,
ArgumentsHost,
HttpException,
HttpStatus,
Logger
} from '@nestjs/common';
@Catch()
export class EnhancedHttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(EnhancedHttpExceptionFilter.name);
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof Error ? exception.message : 'Unknown error occurred';
const responseBody = {
success: false,
timestamp: new Date().toISOString(),
code: `ERR-${status}`,
message,
path: request.url,
...(process.env.NODE_ENV === 'development' && {
stack: exception instanceof Error ? exception.stack : undefined
})
};
// 记录日志
this.logger.error(
`[${request.method}] ${request.url} - ${status}`,
exception instanceof Error ? exception.stack : JSON.stringify(exception)
);
response.status(status).json(responseBody);
}
}
typescript
4.5 高级功能扩展
1. 异常分类处理
if (exception instanceof QueryFailedError) {
// 处理数据库错误
status = HttpStatus.BAD_REQUEST;
message = 'Database operation failed';
} else if (exception instanceof TypeError) {
// 处理类型错误
status = HttpStatus.INTERNAL_SERVER_ERROR;
message = 'Type validation error';
}
typescript
2. 多语言支持
import { I18nService } from 'nestjs-i18n';
constructor(private readonly i18n: I18nService) {}
const message = await this.i18n.translate(`errors.${exception.message}`, {
lang: request.headers['accept-language'] || 'en'
});
typescript
3. 性能监控集成
import { MetricsService } from '../monitoring/metrics.service';
constructor(private readonly metrics: MetricsService) {}
this.metrics.increment('http_errors', 1, {
status_code: status,
path: request.route?.path || 'unknown'
});
typescript
💡 生产建议:
- 为不同HTTP状态码配置不同的日志级别
- 实现异常采样机制,避免高流量时日志爆炸
- 集成APM工具(如NewRelic、Datadog)进行错误追踪
- 对4xx和5xx错误设置不同的告警策略
5. 集成Winston日志记录
5.1 注入Logger服务增强实现
完整依赖注入方案
import { LoggerService } from '@nestjs/common';
import { WinstonLogger } from 'nest-winston';
export class HttpExceptionFilter implements ExceptionFilter {
constructor(
@Inject(WINSTON_MODULE_PROVIDER)
private readonly logger: WinstonLogger,
private readonly correlationIdService: CorrelationIdService
) {}
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest();
this.logger.error({
message: `[${request.method}] ${request.url}`,
exception: exception.stack,
correlationId: this.correlationIdService.getId(), // 请求追踪ID
context: {
params: request.params,
query: request.query,
body: this.sanitizeBody(request.body) // 敏感信息过滤
}
});
}
private sanitizeBody(body: any) {
const sanitized = {...body};
if (sanitized.password) sanitized.password = '***';
if (sanitized.token) sanitized.token = '***';
return sanitized;
}
}
typescript
关键增强点:
- 结构化日志:使用对象而非字符串,便于日志分析系统处理
- 请求追踪:集成correlation ID实现全链路追踪
- 敏感信息过滤:自动屏蔽密码/token等敏感字段
- 上下文丰富:记录请求参数和body内容
💡 扩展配置:
- 通过
winston.format.cli()
添加彩色控制台输出 - 使用
winston-daily-rotate-file
实现日志自动轮转
5.2 全局注册最佳实践
生产级注册方案
// main.ts
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import { utilities } from 'nest-winston/dist/winston.utilities';
const logger = WinstonModule.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike('Global', {
colors: true,
prettyPrint: true
})
)
}),
new winston.transports.DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d'
})
]
});
async function bootstrap() {
const app = await NestFactory.create(AppModule, { logger });
app.useGlobalFilters(
new HttpExceptionFilter(
logger,
app.get(CorrelationIdService)
)
);
}
typescript
关键配置说明:
配置项 | 作用说明 | 推荐值 |
---|---|---|
format.timestamp() | 添加ISO时间戳 | 必选 |
utilities.format.nestLike | 兼容Nest日志风格 | 开发环境推荐 |
DailyRotateFile | 按日期分割日志文件 | 生产环境必配 |
zippedArchive | 自动压缩旧日志 | true |
maxSize | 单个日志文件大小限制 | 20m |
5.3 日志输出效果增强
结构化日志示例
{
"level": "error",
"timestamp": "2025-06-18T15:42:31.000Z",
"context": "Global",
"correlationId": "cid-123456",
"message": "[POST] /api/users",
"exception": "Error: User is not admin\n at UserController.getAllUsers (...)",
"request": {
"method": "POST",
"path": "/api/users",
"query": {},
"params": {},
"body": {
"username": "test",
"password": "***"
}
},
"response": {
"statusCode": 403,
"error": "Forbidden"
}
}
json
日志分析建议:
- ELK集成:通过Filebeat将日志发送到Elasticsearch
- 错误报警:配置Kibana Alerting对ERROR日志触发通知
- 性能监控:分析日志中的响应时间指标
5.4 高级场景扩展
1. 多日志分类存储
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
format: winston.format.json()
}),
new winston.transports.File({
filename: 'logs/combined.log',
format: winston.format.combine(
winston.format.errors({ stack: true }),
winston.format.json()
)
})
typescript
2. 日志采样配置
const samplingRate = process.env.NODE_ENV === 'production' ? 0.7 : 1.0;
new winston.transports.Console({
format: winston.format.simple(),
sampleRate: samplingRate // 生产环境采样70%日志
})
typescript
3. 云原生集成
// AWS CloudWatch集成
import { CloudWatchTransport } from 'winston-aws-cloudwatch';
new CloudWatchTransport({
logGroupName: 'my-app',
logStreamName: 'exceptions',
awsConfig: {
region: process.env.AWS_REGION
}
})
typescript
5.5 调试技巧
开发环境日志增强
if (process.env.NODE_ENV === 'development') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.printf(({ level, message, stack }) => {
return `${level}: ${message}\n${stack || ''}`;
})
}));
}
typescript
日志级别动态调整
// 通过API端点动态修改日志级别
@Post('loglevel')
setLogLevel(@Body() { level }: { level: string }) {
logger.transports.forEach(transport => {
transport.level = level;
});
}
typescript
💡 性能优化提示:
- 高频日志使用
logger.debug
避免生产环境I/O压力 - 对文件传输使用
winston.transports.Http
批量发送 - 考虑使用
pino
替换winston
应对超高并发场景
6. 高级扩展与限制
6.1 全局过滤器限制与解决方案
单实例限制详解
- 核心问题:NestJS的
useGlobalFilters()
仅允许注册一个过滤器实例 - 影响范围:
- 无法为不同异常类型注册专用处理器
- 难以实现分模块的错误处理策略
复合过滤器实现方案
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
@Catch()
export class CompositeFilter implements ExceptionFilter {
constructor(private readonly filters: ExceptionFilter[]) {}
catch(exception: unknown, host: ArgumentsHost) {
for (const filter of this.filters) {
try {
filter.catch(exception, host);
} catch (innerError) {
console.error('Filter processing failed:', innerError);
}
}
}
}
// 注册示例
const compositeFilter = new CompositeFilter([
new HttpExceptionFilter(logger),
new DatabaseExceptionFilter(dbLogger),
new ValidationExceptionFilter()
]);
app.useGlobalFilters(compositeFilter);
typescript
复合模式优势
特性 | 说明 |
---|---|
多过滤器并行 | 同时处理业务异常、数据库异常、验证异常等不同类型 |
责任链模式 | 支持按优先级排序过滤器执行顺序 |
模块化扩展 | 新增异常类型时只需添加新过滤器,无需修改现有代码 |
执行流程
💡 生产建议:
- 为每个过滤器添加
try-catch
防止单个处理器崩溃影响整体 - 使用
reflect-metadata
标注过滤器优先级顺序 - 对高优先级过滤器实现短路逻辑(处理成功后终止链式调用)
6.2 WebSocket异常处理深度实践
核心实现增强版
import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
@Catch(WsException)
export class EnhancedWsExceptionFilter implements ExceptionFilter {
constructor(private readonly logger: LoggerService) {}
catch(exception: WsException, host: ArgumentsHost) {
const ctx = host.switchToWs();
const client = ctx.getClient<Socket>();
const data = ctx.getData();
// 结构化日志记录
this.logger.error({
event: 'WS_EXCEPTION',
error: exception.getError(),
payload: data,
socketId: client.id,
timestamp: new Date().toISOString()
});
// 标准化错误响应
client.emit('exception', {
status: 'error',
code: 'WS_' + exception.getStatus(),
message: exception.message,
requestId: data?.requestId || null
});
// 重要错误断开连接
if (exception.getStatus() >= 500) {
client.disconnect(true);
}
}
}
typescript
关键功能点
- 元数据记录:
- 记录完整的Socket连接信息
- 保存触发异常的事件载荷
- 连接管理:
- 对严重错误(5xx)主动断开连接
- 保留业务请求ID用于追踪
- 日志集成:
- 与HTTP请求共享同一日志系统
- 使用相同correlation ID实现全链路追踪
注册方式
// 网关级注册
@UseFilters(new EnhancedWsExceptionFilter(logger))
@WebSocketGateway()
export class AppGateway {}
// 全局注册(需要适配器支持)
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new WsAdapter(app));
app.useGlobalFilters(new EnhancedWsExceptionFilter(logger));
typescript
6.3 作业:WebSocket异常过滤器集成日志
作业要求
- 实现一个可记录以下信息的过滤器:
- 异常发生时间
- 客户端IP地址
- 触发的事件名称
- 异常堆栈信息
- 将日志输出到:
- 控制台(开发环境)
- 文件
logs/ws-error.log
(生产环境)
- 支持动态日志级别切换
参考实现
import { Logger } from 'winston';
@Catch()
export class WsLoggingFilter implements ExceptionFilter {
private logger: Logger;
constructor() {
this.logger = createLogger({
transports: [
new transports.Console({ level: 'debug' }),
new transports.File({
filename: 'logs/ws-error.log',
level: 'error'
})
]
});
}
catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToWs();
const client = ctx.getClient<Socket>();
const event = ctx.getPattern();
this.logger.error({
timestamp: new Date(),
clientIp: client.handshake.address,
event,
error: exception.message,
stack: exception.stack
});
client.emit('error', {
status: 'error',
message: 'Internal server error'
});
}
}
typescript
作业扩展
- 增强功能:
// 添加请求载荷过滤 const sanitizedData = this.sanitizeData(ctx.getData()); // 添加性能监控 const duration = Date.now() - data.timestamp; this.metrics.observe('ws_request_duration', duration);
typescript - 测试用例:
it('should log connection errors', () => { const mockClient = { emit: jest.fn(), id: 'test', handshake: { address: '127.0.0.1' } }; const filter = new WsLoggingFilter(); filter.catch(new Error('test'), { switchToWs: () => ({ getClient: () => mockClient, getPattern: () => 'test-event' }) } as any); expect(mockClient.emit).toHaveBeenCalledWith('error', expect.anything()); });
typescript
6.4 前沿技术:Serverless异常处理
无服务架构适配方案
// AWS Lambda示例
export const handler = async (event) => {
try {
// 业务逻辑
} catch (error) {
const logger = new LambdaLogger();
logger.error('Lambda execution failed', {
event,
error: error.stack
});
return {
statusCode: error.statusCode || 500,
body: JSON.stringify({
message: error.message,
requestId: event.requestContext.requestId
})
};
}
};
typescript
关键差异点
特性 | 传统应用 | Serverless |
---|---|---|
日志存储 | 文件/ELK | 云服务日志(CloudWatch等) |
错误追踪 | 基于请求ID | 集成X-Ray追踪 |
冷启动问题 | 无 | 需处理初始化阶段的异常 |
💡 最佳实践:
- 为Lambda配置死信队列(DLQ)捕获未处理异常
- 使用
lambda-log
库实现结构化日志 - 对冷启动错误添加特定重试逻辑
↑