错误处理策略
学习目标
- 理解NestJS的错误处理机制
- 掌握异常过滤器的创建和使用
- 学习如何创建和使用自定义异常
- 理解错误分类和错误码设计
- 掌握错误监控和日志记录的实现
- 学习错误恢复和容错机制
- 了解错误处理的最佳实践
核心知识点
错误处理基础
NestJS错误处理的基本概念和机制:
- 异常:程序执行过程中发生的错误情况
- 异常过滤器:捕获和处理异常的组件
- 错误响应:返回给客户端的错误信息
- 错误码:标识错误类型的代码
- 错误堆栈:错误发生的调用栈信息
异常过滤器
NestJS的异常过滤器机制:
- 全局异常过滤器:处理应用中所有未捕获的异常
- 控制器级别异常过滤器:处理特定控制器的异常
- 方法级别异常过滤器:处理特定方法的异常
- 内置异常:NestJS提供的内置异常类
- 自定义异常过滤器:根据需要创建自定义异常过滤器
自定义异常
创建和使用自定义异常:
- 自定义异常类:继承NestJS的HttpException类
- 错误码设计:设计合理的错误码系统
- 错误消息:提供清晰、一致的错误消息
- 错误元数据:附加错误相关的元数据
- 错误转换:将内部错误转换为用户友好的错误
错误分类
错误的分类和管理:
- 业务错误:业务逻辑相关的错误
- 系统错误:系统级别的错误
- 网络错误:网络请求相关的错误
- 数据库错误:数据库操作相关的错误
- 验证错误:输入验证相关的错误
- 认证错误:身份验证相关的错误
- 授权错误:权限相关的错误
错误监控
错误监控和分析:
- 错误跟踪:跟踪错误的发生和传播
- 错误统计:统计错误的类型和频率
- 错误告警:当错误率超过阈值时发出告警
- 错误分析:分析错误的根本原因
- 错误可视化:通过图表可视化错误数据
日志记录
错误日志的记录和管理:
- 日志级别:根据错误严重程度设置日志级别
- 日志格式:统一的日志格式
- 日志存储:日志的存储和管理
- 日志查询:快速查询和检索日志
- 日志分析:分析日志数据,发现问题
错误恢复
错误恢复和容错机制:
- 重试机制:当遇到临时错误时进行重试
- 降级策略:当服务不可用时使用备用方案
- 熔断机制:当错误率过高时暂时停止服务
- 隔离舱模式:将系统分为多个隔离舱,防止错误扩散
- 优雅降级:在部分功能不可用时保持核心功能可用
实践案例
创建全局异常过滤器
步骤1:创建异常过滤器
// src/common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { Request, Response } from 'express';
export interface ErrorResponse {
statusCode: number;
timestamp: string;
path: string;
error: string;
message: string;
errors?: any;
stack?: string;
}
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const exceptionResponse = exception.getResponse();
// 构建错误响应
const errorResponse: ErrorResponse = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
error: HttpStatus[status],
message: typeof exceptionResponse === 'string'
? exceptionResponse
: (exceptionResponse as any).message || 'Unknown error',
errors: typeof exceptionResponse === 'object'
? (exceptionResponse as any).errors
: undefined,
};
// 在开发环境中添加堆栈信息
if (process.env.NODE_ENV === 'development') {
errorResponse.stack = exception.stack;
}
// 记录错误日志
this.logger.error(
`${status} ${request.method} ${request.url}`,
exception.stack,
HttpExceptionFilter.name,
);
// 发送错误响应
response.status(status).json(errorResponse);
}
}步骤2:创建全局异常过滤器
// src/common/filters/all-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus, Logger } from '@nestjs/common';
import { Request, Response } from 'express';
import { ErrorResponse } from './http-exception.filter';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger(AllExceptionsFilter.name);
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
// 确定错误状态码
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Internal server error';
let error = 'Internal Server Error';
// 处理不同类型的异常
if (exception instanceof Error) {
message = exception.message;
error = exception.name;
}
if (exception.status) {
status = exception.status;
error = HttpStatus[status];
}
// 构建错误响应
const errorResponse: ErrorResponse = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
error,
message,
};
// 在开发环境中添加堆栈信息
if (process.env.NODE_ENV === 'development') {
errorResponse.stack = exception.stack;
}
// 记录错误日志
this.logger.error(
`${status} ${request.method} ${request.url} - ${message}`,
exception.stack,
AllExceptionsFilter.name,
);
// 发送错误响应
response.status(status).json(errorResponse);
}
}步骤3:注册全局异常过滤器
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { AllExceptionsFilter } from './common/filters/all-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 注册全局异常过滤器
app.useGlobalFilters(
new HttpExceptionFilter(),
new AllExceptionsFilter(),
);
await app.listen(3000);
}
bootstrap();创建自定义异常
步骤1:创建错误码枚举
// src/common/enums/error-code.enum.ts
export enum ErrorCode {
// 系统错误
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
// 认证错误
UNAUTHORIZED = 'UNAUTHORIZED',
INVALID_TOKEN = 'INVALID_TOKEN',
EXPIRED_TOKEN = 'EXPIRED_TOKEN',
// 授权错误
FORBIDDEN = 'FORBIDDEN',
// 验证错误
VALIDATION_ERROR = 'VALIDATION_ERROR',
// 资源错误
RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
RESOURCE_ALREADY_EXISTS = 'RESOURCE_ALREADY_EXISTS',
// 业务错误
BUSINESS_ERROR = 'BUSINESS_ERROR',
INVALID_OPERATION = 'INVALID_OPERATION',
// 网络错误
NETWORK_ERROR = 'NETWORK_ERROR',
// 数据库错误
DATABASE_ERROR = 'DATABASE_ERROR',
}步骤2:创建自定义异常基类
// src/common/exceptions/base.exception.ts
import { HttpException, HttpStatus } from '@nestjs/common';
import { ErrorCode } from '../enums/error-code.enum';
export class BaseException extends HttpException {
constructor(
public readonly errorCode: ErrorCode,
message: string,
statusCode: HttpStatus = HttpStatus.BAD_REQUEST,
public readonly errors?: any,
public readonly metadata?: Record<string, any>,
) {
super(
{
errorCode,
message,
errors,
metadata,
},
statusCode,
);
}
}步骤3:创建具体的自定义异常
// src/common/exceptions/business.exception.ts
import { HttpStatus } from '@nestjs/common';
import { BaseException } from './base.exception';
import { ErrorCode } from '../enums/error-code.enum';
export class BusinessException extends BaseException {
constructor(
message: string,
errorCode: ErrorCode = ErrorCode.BUSINESS_ERROR,
errors?: any,
metadata?: Record<string, any>,
) {
super(errorCode, message, HttpStatus.BAD_REQUEST, errors, metadata);
}
}
// src/common/exceptions/not-found.exception.ts
import { HttpStatus } from '@nestjs/common';
import { BaseException } from './base.exception';
import { ErrorCode } from '../enums/error-code.enum';
export class NotFoundException extends BaseException {
constructor(
message: string = 'Resource not found',
errorCode: ErrorCode = ErrorCode.RESOURCE_NOT_FOUND,
errors?: any,
metadata?: Record<string, any>,
) {
super(errorCode, message, HttpStatus.NOT_FOUND, errors, metadata);
}
}
// src/common/exceptions/unauthorized.exception.ts
import { HttpStatus } from '@nestjs/common';
import { BaseException } from './base.exception';
import { ErrorCode } from '../enums/error-code.enum';
export class UnauthorizedException extends BaseException {
constructor(
message: string = 'Unauthorized',
errorCode: ErrorCode = ErrorCode.UNAUTHORIZED,
errors?: any,
metadata?: Record<string, any>,
) {
super(errorCode, message, HttpStatus.UNAUTHORIZED, errors, metadata);
}
}
// src/common/exceptions/forbidden.exception.ts
import { HttpStatus } from '@nestjs/common';
import { BaseException } from './base.exception';
import { ErrorCode } from '../enums/error-code.enum';
export class ForbiddenException extends BaseException {
constructor(
message: string = 'Forbidden',
errorCode: ErrorCode = ErrorCode.FORBIDDEN,
errors?: any,
metadata?: Record<string, any>,
) {
super(errorCode, message, HttpStatus.FORBIDDEN, errors, metadata);
}
}
// src/common/exceptions/validation.exception.ts
import { HttpStatus } from '@nestjs/common';
import { BaseException } from './base.exception';
import { ErrorCode } from '../enums/error-code.enum';
export class ValidationException extends BaseException {
constructor(
message: string = 'Validation error',
errors?: any,
metadata?: Record<string, any>,
) {
super(ErrorCode.VALIDATION_ERROR, message, HttpStatus.BAD_REQUEST, errors, metadata);
}
}步骤4:使用自定义异常
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { BusinessException } from '../common/exceptions/business.exception';
import { NotFoundException } from '../common/exceptions/not-found.exception';
import { ErrorCode } from '../common/enums/error-code.enum';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
// 检查用户是否已存在
const existingUser = await this.usersRepository.findOne({
where: { email: createUserDto.email },
});
if (existingUser) {
throw new BusinessException(
'User with this email already exists',
ErrorCode.RESOURCE_ALREADY_EXISTS,
{ email: createUserDto.email },
);
}
// 创建用户
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
async findOne(id: number): Promise<User> {
const user = await this.usersRepository.findOneBy({ id });
if (!user) {
throw new NotFoundException(`User with id ${id} not found`);
}
return user;
}
}错误监控和日志记录
步骤1:创建错误监控服务
// src/common/services/error-monitor.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { ErrorCode } from '../enums/error-code.enum';
interface ErrorRecord {
id: string;
timestamp: Date;
errorCode: ErrorCode;
message: string;
path?: string;
method?: string;
statusCode?: number;
stack?: string;
metadata?: Record<string, any>;
}
@Injectable()
export class ErrorMonitorService {
private readonly logger = new Logger(ErrorMonitorService.name);
private errorRecords: ErrorRecord[] = [];
private errorCounts: Map<ErrorCode, number> = new Map();
// 记录错误
recordError(
errorCode: ErrorCode,
message: string,
options?: {
path?: string;
method?: string;
statusCode?: number;
stack?: string;
metadata?: Record<string, any>;
},
) {
const errorRecord: ErrorRecord = {
id: this.generateId(),
timestamp: new Date(),
errorCode,
message,
...options,
};
this.errorRecords.push(errorRecord);
// 更新错误计数
const currentCount = this.errorCounts.get(errorCode) || 0;
this.errorCounts.set(errorCode, currentCount + 1);
// 检查错误率,超过阈值时发出告警
this.checkErrorRate(errorCode, currentCount + 1);
// 记录错误日志
this.logger.error(
`${errorCode}: ${message}`,
options?.stack,
ErrorMonitorService.name,
);
return errorRecord.id;
}
// 检查错误率
private checkErrorRate(errorCode: ErrorCode, count: number) {
// 这里可以实现更复杂的错误率检查逻辑
// 例如:检查最近5分钟内的错误率
if (count > 10) {
this.logger.warn(
`High error rate detected for ${errorCode}: ${count} errors`,
ErrorMonitorService.name,
);
// 这里可以集成告警系统,如发送邮件、短信等
}
}
// 生成唯一ID
private generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 获取错误统计
getErrorStats() {
return Array.from(this.errorCounts.entries()).map(([errorCode, count]) => ({
errorCode,
count,
}));
}
// 获取最近的错误记录
getRecentErrors(limit: number = 10) {
return this.errorRecords
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
.slice(0, limit);
}
// 清空错误记录
clearErrorRecords() {
this.errorRecords = [];
this.errorCounts.clear();
}
}步骤2:创建日志服务
// src/common/services/logger.service.ts
import { Injectable, Logger, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.TRANSIENT })
export class LoggerService {
private logger: Logger;
constructor(context?: string) {
this.logger = new Logger(context || LoggerService.name);
}
// 记录调试信息
debug(message: string, context?: string) {
this.logger.debug(message, context);
}
// 记录信息
log(message: string, context?: string) {
this.logger.log(message, context);
}
// 记录警告
warn(message: string, context?: string) {
this.logger.warn(message, context);
}
// 记录错误
error(message: string, trace?: string, context?: string) {
this.logger.error(message, trace, context);
}
// 记录详细信息
verbose(message: string, context?: string) {
this.logger.verbose(message, context);
}
// 记录请求
logRequest(method: string, url: string, statusCode: number, responseTime: number) {
const statusColor = this.getStatusColor(statusCode);
this.logger.log(
`${statusColor}${statusCode}${'\x1b[0m'} ${method} ${url} - ${responseTime}ms`,
'RequestLogger',
);
}
// 获取状态码颜色
private getStatusColor(statusCode: number): string {
if (statusCode >= 500) {
return '\x1b[31m'; // 红色
} else if (statusCode >= 400) {
return '\x1b[33m'; // 黄色
} else if (statusCode >= 300) {
return '\x1b[36m'; // 青色
} else {
return '\x1b[32m'; // 绿色
}
}
}步骤3:创建请求拦截器
// src/common/interceptors/request-logger.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { LoggerService } from '../services/logger.service';
@Injectable()
export class RequestLoggerInterceptor implements NestInterceptor {
constructor(private readonly logger: LoggerService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
const request = context.switchToHttp().getRequest();
const method = request.method;
const url = request.url;
return next
.handle()
.pipe(
tap(() => {
const response = context.switchToHttp().getResponse();
const statusCode = response.statusCode;
const responseTime = Date.now() - now;
this.logger.logRequest(method, url, statusCode, responseTime);
}),
);
}
}步骤4:注册全局服务和拦截器
// src/app.module.ts
import { Module, Global } from '@nestjs/common';
import { APP_FILTER, APP_INTERCEPTOR, APP_SERVICE }
from '@nestjs/core';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { AllExceptionsFilter } from './common/filters/all-exception.filter';
import { RequestLoggerInterceptor } from './common/interceptors/request-logger.interceptor';
import { LoggerService } from './common/services/logger.service';
import { ErrorMonitorService } from './common/services/error-monitor.service';
import { UsersModule } from './users/users.module';
@Global()
@Module({
imports: [UsersModule],
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: RequestLoggerInterceptor,
},
LoggerService,
ErrorMonitorService,
],
exports: [LoggerService, ErrorMonitorService],
})
export class AppModule {}错误恢复和容错机制
步骤1:创建重试装饰器
// src/common/decorators/retry.decorator.ts
import { ExecutionContext, Injectable, NestInterceptor, CallHandler } from '@nestjs/common';
import { Observable, throwError, timer } from 'rxjs';
import { catchError, retryWhen, mergeMap } from 'rxjs/operators';
export function Retry(
maxAttempts: number = 3,
delay: number = 1000,
maxDelay: number = 10000,
) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
let attempts = 0;
async function attempt() {
try {
attempts++;
return await originalMethod.apply(this, args);
} catch (error) {
if (attempts < maxAttempts) {
// 指数退避策略
const backoffDelay = Math.min(delay * Math.pow(2, attempts - 1), maxDelay);
await new Promise(resolve => setTimeout(resolve, backoffDelay));
return attempt();
}
throw error;
}
}
return attempt();
};
return descriptor;
};
}
@Injectable()
export class RetryInterceptor implements NestInterceptor {
constructor(
private readonly maxAttempts: number = 3,
private readonly delay: number = 1000,
private readonly maxDelay: number = 10000,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
retryWhen(errors => {
return errors.pipe(
mergeMap((error, index) => {
if (index >= this.maxAttempts - 1) {
return throwError(error);
}
const backoffDelay = Math.min(this.delay * Math.pow(2, index), this.maxDelay);
return timer(backoffDelay);
}),
);
}),
);
}
}步骤2:创建熔断装饰器
// src/common/decorators/circuit-breaker.decorator.ts
interface CircuitState {
state: 'CLOSED' | 'OPEN' | 'HALF_OPEN';
failureCount: number;
lastFailureTime: number;
recoveryTimeout: number;
failureThreshold: number;
}
const circuitStates = new Map<string, CircuitState>();
export function CircuitBreaker(
failureThreshold: number = 5,
recoveryTimeout: number = 30000, // 30秒
) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const key = `${target.constructor.name}.${propertyKey}`;
// 初始化熔断状态
if (!circuitStates.has(key)) {
circuitStates.set(key, {
state: 'CLOSED',
failureCount: 0,
lastFailureTime: 0,
recoveryTimeout,
failureThreshold,
});
}
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const circuitState = circuitStates.get(key);
if (!circuitState) {
return originalMethod.apply(this, args);
}
// 检查是否可以尝试恢复
if (circuitState.state === 'OPEN') {
const now = Date.now();
if (now - circuitState.lastFailureTime > circuitState.recoveryTimeout) {
// 进入半开状态,尝试恢复
circuitState.state = 'HALF_OPEN';
} else {
// 熔断状态,直接抛出错误
throw new Error('Service temporarily unavailable (circuit open)');
}
}
try {
const result = await originalMethod.apply(this, args);
// 调用成功,重置状态
circuitState.state = 'CLOSED';
circuitState.failureCount = 0;
return result;
} catch (error) {
// 调用失败,更新状态
circuitState.failureCount++;
circuitState.lastFailureTime = Date.now();
if (circuitState.state === 'HALF_OPEN' ||
circuitState.failureCount >= circuitState.failureThreshold) {
// 进入熔断状态
circuitState.state = 'OPEN';
}
throw error;
}
};
return descriptor;
};
}步骤3:使用错误恢复机制
// src/common/services/external-api.service.ts
import { Injectable, HttpService } from '@nestjs/common';
import { Retry } from '../decorators/retry.decorator';
import { CircuitBreaker } from '../decorators/circuit-breaker.decorator';
@Injectable()
export class ExternalApiService {
constructor(private readonly httpService: HttpService) {}
// 使用重试机制
@Retry(3, 1000, 5000)
async callExternalApi(url: string) {
try {
const response = await this.httpService.get(url).toPromise();
return response.data;
} catch (error) {
console.error('Error calling external API:', error.message);
throw error;
}
}
// 使用熔断机制
@CircuitBreaker(5, 30000)
async callUnreliableService() {
try {
// 模拟调用不可靠的服务
const random = Math.random();
if (random > 0.7) {
throw new Error('Service failed');
}
return 'Service response';
} catch (error) {
console.error('Error calling unreliable service:', error.message);
throw error;
}
}
}代码示例
错误处理中间件
// src/common/middleware/error-handler.middleware.ts
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class ErrorHandlerMiddleware implements NestMiddleware {
private readonly logger = new Logger(ErrorHandlerMiddleware.name);
use(req: Request, res: Response, next: NextFunction) {
// 捕获同步错误
try {
next();
} catch (error) {
this.logger.error(
`Error in middleware: ${error.message}`,
error.stack,
ErrorHandlerMiddleware.name,
);
res.status(500).json({
statusCode: 500,
timestamp: new Date().toISOString(),
path: req.url,
error: 'Internal Server Error',
message: 'An unexpected error occurred',
});
}
}
}健康检查和错误监控端点
// src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, HttpHealthIndicator, DiskHealthIndicator, MemoryHealthIndicator } from '@nestjs/terminus';
import { ErrorMonitorService } from '../common/services/error-monitor.service';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
private disk: DiskHealthIndicator,
private memory: MemoryHealthIndicator,
private errorMonitor: ErrorMonitorService,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
() => this.disk.checkStorage('storage', {
path: '/',
threshold: 250 * 1024 * 1024 * 1024, // 250GB
}),
() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024), // 150MB
() => this.memory.checkRSS('memory_rss', 300 * 1024 * 1024), // 300MB
]);
}
@Get('errors')
getErrorStats() {
return {
stats: this.errorMonitor.getErrorStats(),
recentErrors: this.errorMonitor.getRecentErrors(10),
};
}
}错误处理工具类
// src/common/utils/error.util.ts
import { ErrorCode } from '../enums/error-code.enum';
import { BaseException } from '../exceptions/base.exception';
export class ErrorUtil {
// 格式化错误消息
static formatErrorMessage(error: any): string {
if (typeof error === 'string') {
return error;
}
if (error.message) {
return error.message;
}
if (error.error) {
return error.error;
}
return 'Unknown error';
}
// 提取错误码
static extractErrorCode(error: any): ErrorCode {
if (error instanceof BaseException) {
return error.errorCode;
}
if (error.errorCode) {
return error.errorCode;
}
return ErrorCode.INTERNAL_SERVER_ERROR;
}
// 构建错误响应
static buildErrorResponse(
error: any,
statusCode: number,
path: string,
) {
const message = this.formatErrorMessage(error);
const errorCode = this.extractErrorCode(error);
return {
statusCode,
timestamp: new Date().toISOString(),
path,
error: errorCode,
message,
errors: error.errors,
metadata: error.metadata,
};
}
// 处理数据库错误
static handleDatabaseError(error: any): { errorCode: ErrorCode; message: string } {
if (error.code === 'ER_DUP_ENTRY') {
return {
errorCode: ErrorCode.RESOURCE_ALREADY_EXISTS,
message: 'Resource already exists',
};
}
if (error.code === 'ER_NO_SUCH_TABLE') {
return {
errorCode: ErrorCode.DATABASE_ERROR,
message: 'Database table not found',
};
}
return {
errorCode: ErrorCode.DATABASE_ERROR,
message: 'Database error occurred',
};
}
// 处理网络错误
static handleNetworkError(error: any): { errorCode: ErrorCode; message: string } {
if (error.code === 'ECONNREFUSED') {
return {
errorCode: ErrorCode.NETWORK_ERROR,
message: 'Connection refused',
};
}
if (error.code === 'ETIMEDOUT') {
return {
errorCode: ErrorCode.NETWORK_ERROR,
message: 'Connection timeout',
};
}
return {
errorCode: ErrorCode.NETWORK_ERROR,
message: 'Network error occurred',
};
}
}集成第三方错误监控服务
// src/common/services/external-monitor.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { ErrorCode } from '../enums/error-code.enum';
@Injectable()
export class ExternalMonitorService {
private readonly logger = new Logger(ExternalMonitorService.name);
// 集成Sentry
async captureException(
error: any,
context?: {
user?: any;
tags?: Record<string, any>;
extra?: Record<string, any>;
},
) {
try {
// 这里集成Sentry等错误监控服务
// 示例代码:
// Sentry.captureException(error, {
// user: context?.user,
// tags: context?.tags,
// extra: context?.extra,
// });
this.logger.log('Error captured by external monitor', ExternalMonitorService.name);
return true;
} catch (monitorError) {
this.logger.error(
`Error in external monitor: ${monitorError.message}`,
monitorError.stack,
ExternalMonitorService.name,
);
return false;
}
}
// 集成Datadog
async trackError(
errorCode: ErrorCode,
message: string,
duration?: number,
tags?: Record<string, any>,
) {
try {
// 这里集成Datadog等APM服务
// 示例代码:
// datadog.trackError(errorCode, {
// message,
// duration,
// tags,
// });
this.logger.log('Error tracked by APM', ExternalMonitorService.name);
return true;
} catch (trackError) {
this.logger.error(
`Error in APM tracking: ${trackError.message}`,
trackError.stack,
ExternalMonitorService.name,
);
return false;
}
}
}常见问题与解决方案
1. 如何处理异步错误?
解决方案:
- 使用try-catch捕获异步错误
- 使用Promise的catch方法处理Promise错误
- 使用async/await时确保正确处理错误
- 使用rxjs的错误处理操作符处理Observable错误
- 确保所有异步操作都有错误处理机制
2. 如何设计合理的错误码系统?
解决方案:
- 分类设计:按错误类型分类(系统、认证、授权、业务等)
- 分层设计:使用前缀标识错误类型
- 唯一性:确保每个错误码唯一
- 可读性:使用有意义的错误码
- 扩展性:预留足够的错误码空间
- 文档化:维护错误码文档
3. 如何平衡错误详细度和安全性?
解决方案:
- 开发环境:提供详细的错误信息和堆栈
- 生产环境:提供简洁的错误信息,避免泄露敏感信息
- 错误日志:在服务器端记录详细错误信息
- 错误跟踪:使用错误ID关联客户端错误和服务器日志
- 安全审计:定期检查错误响应是否泄露敏感信息
4. 如何实现错误的国际化?
解决方案:
- 使用i18n模块管理错误消息
- 错误消息使用键值对,支持多语言
- 根据用户语言偏好返回对应语言的错误消息
- 错误码保持不变,仅错误消息国际化
- 提供默认语言的错误消息作为后备
5. 如何处理第三方服务的错误?
解决方案:
- 封装第三方服务调用,统一错误处理
- 转换第三方错误为内部错误格式
- 实现重试和容错机制
- 监控第三方服务的错误率
- 提供降级方案,当第三方服务不可用时
互动问答
什么是异常过滤器?它在NestJS中的作用是什么?
异常过滤器是NestJS中用于捕获和处理异常的组件。它的作用是:
- 捕获应用中抛出的异常
- 统一错误响应格式
- 记录错误日志
- 转换内部错误为用户友好的错误
- 处理不同类型的异常,提供不同的错误响应
- 实现全局或局部的错误处理策略
如何创建和使用自定义异常?
创建和使用自定义异常的步骤:
- 创建错误码枚举,定义错误类型
- 创建自定义异常基类,继承NestJS的HttpException
- 创建具体的自定义异常类,如BusinessException、NotFoundException等
- 在服务或控制器中抛出自定义异常
- 在异常过滤器中处理自定义异常
- 确保自定义异常包含足够的上下文信息
错误监控和日志记录的最佳实践是什么?
错误监控和日志记录的最佳实践:
- 结构化日志:使用JSON格式记录日志,便于分析
- 适当的日志级别:根据错误严重程度使用不同的日志级别
- 上下文信息:记录错误发生的上下文,如用户ID、请求ID等
- 错误聚合:对相似错误进行聚合,避免日志爆炸
- 实时监控:实时监控错误率和异常模式
- 告警机制:当错误率超过阈值时发出告警
- 日志存储:使用专门的日志存储和分析系统
- 日志保留:根据法规和业务需求设置合理的日志保留期限
什么是熔断机制?它如何帮助提高系统的可靠性?
熔断机制是一种容错设计模式,用于防止系统在依赖服务故障时被拖垮。它的工作原理:
- 关闭状态:正常处理请求
- 打开状态:当错误率超过阈值时,拒绝所有请求
- 半开状态:经过一段时间后,尝试恢复,允许部分请求通过
熔断机制的好处:
- 防止级联故障
- 给故障服务时间恢复
- 提高系统的整体可靠性
- 减少资源浪费
- 提供更快的错误响应
如何实现错误的自动恢复?
实现错误自动恢复的方法:
- 重试机制:对临时错误进行自动重试
- 指数退避:重试间隔逐渐增加,避免雪崩
- 熔断机制:当错误率过高时暂时停止尝试
- 降级策略:使用备用方案或默认值
- 自动恢复检测:定期检查故障服务是否恢复
- 优雅启动:服务恢复后逐渐增加流量
- 健康检查:定期检查系统组件的健康状态
总结
错误处理是NestJS应用开发中的重要组成部分,通过本教程的学习,你应该能够:
- 理解NestJS的错误处理机制和异常过滤器
- 创建和使用自定义异常,设计合理的错误码系统
- 实现错误监控和日志记录,及时发现和处理错误
- 应用错误恢复和容错机制,提高系统可靠性
- 处理不同类型的错误,如数据库错误、网络错误等
- 遵循错误处理的最佳实践,构建健壮的应用
通过合理的错误处理策略,你可以构建更加可靠、用户友好的NestJS应用,提高系统的可用性和可维护性。错误处理不仅仅是捕获和记录错误,更是一种系统设计理念,需要在整个应用开发过程中持续关注和优化。