错误处理策略

学习目标

  • 理解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. 如何处理第三方服务的错误?

解决方案

  • 封装第三方服务调用,统一错误处理
  • 转换第三方错误为内部错误格式
  • 实现重试和容错机制
  • 监控第三方服务的错误率
  • 提供降级方案,当第三方服务不可用时

互动问答

  1. 什么是异常过滤器?它在NestJS中的作用是什么?

    异常过滤器是NestJS中用于捕获和处理异常的组件。它的作用是:

    • 捕获应用中抛出的异常
    • 统一错误响应格式
    • 记录错误日志
    • 转换内部错误为用户友好的错误
    • 处理不同类型的异常,提供不同的错误响应
    • 实现全局或局部的错误处理策略
  2. 如何创建和使用自定义异常?

    创建和使用自定义异常的步骤:

    • 创建错误码枚举,定义错误类型
    • 创建自定义异常基类,继承NestJS的HttpException
    • 创建具体的自定义异常类,如BusinessException、NotFoundException等
    • 在服务或控制器中抛出自定义异常
    • 在异常过滤器中处理自定义异常
    • 确保自定义异常包含足够的上下文信息
  3. 错误监控和日志记录的最佳实践是什么?

    错误监控和日志记录的最佳实践:

    • 结构化日志:使用JSON格式记录日志,便于分析
    • 适当的日志级别:根据错误严重程度使用不同的日志级别
    • 上下文信息:记录错误发生的上下文,如用户ID、请求ID等
    • 错误聚合:对相似错误进行聚合,避免日志爆炸
    • 实时监控:实时监控错误率和异常模式
    • 告警机制:当错误率超过阈值时发出告警
    • 日志存储:使用专门的日志存储和分析系统
    • 日志保留:根据法规和业务需求设置合理的日志保留期限
  4. 什么是熔断机制?它如何帮助提高系统的可靠性?

    熔断机制是一种容错设计模式,用于防止系统在依赖服务故障时被拖垮。它的工作原理:

    • 关闭状态:正常处理请求
    • 打开状态:当错误率超过阈值时,拒绝所有请求
    • 半开状态:经过一段时间后,尝试恢复,允许部分请求通过

    熔断机制的好处:

    • 防止级联故障
    • 给故障服务时间恢复
    • 提高系统的整体可靠性
    • 减少资源浪费
    • 提供更快的错误响应
  5. 如何实现错误的自动恢复?

    实现错误自动恢复的方法:

    • 重试机制:对临时错误进行自动重试
    • 指数退避:重试间隔逐渐增加,避免雪崩
    • 熔断机制:当错误率过高时暂时停止尝试
    • 降级策略:使用备用方案或默认值
    • 自动恢复检测:定期检查故障服务是否恢复
    • 优雅启动:服务恢复后逐渐增加流量
    • 健康检查:定期检查系统组件的健康状态

总结

错误处理是NestJS应用开发中的重要组成部分,通过本教程的学习,你应该能够:

  1. 理解NestJS的错误处理机制和异常过滤器
  2. 创建和使用自定义异常,设计合理的错误码系统
  3. 实现错误监控和日志记录,及时发现和处理错误
  4. 应用错误恢复和容错机制,提高系统可靠性
  5. 处理不同类型的错误,如数据库错误、网络错误等
  6. 遵循错误处理的最佳实践,构建健壮的应用

通过合理的错误处理策略,你可以构建更加可靠、用户友好的NestJS应用,提高系统的可用性和可维护性。错误处理不仅仅是捕获和记录错误,更是一种系统设计理念,需要在整个应用开发过程中持续关注和优化。

« 上一篇 Monorepo管理 下一篇 » NestJS 多租户架构设计与实现