Skip to content

错误处理

概述

健壮的错误处理对于 Node.js 应用程序至关重要。本章介绍错误类型、处理策略、日志记录、监控和恢复模式,以构建弹性应用程序。

错误类型和模式

基础错误处理

javascript
// basic-error-handling.js

// Synchronous error handling
function divideNumbers(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('Both arguments must be numbers');
  }
  
  if (b === 0) {
    throw new Error('Division by zero is not allowed');
  }
  
  return a / b;
}

// Try-catch for synchronous operations
try {
  const result = divideNumbers(10, 2);
  console.log('Result:', result);
} catch (error) {
  console.error('Error:', error.message);
}

// Asynchronous error handling with callbacks
function readFileCallback(filename, callback) {
  const fs = require('fs');
  
  fs.readFile(filename, 'utf8', (error, data) => {
    if (error) {
      return callback(error, null);
    }
    callback(null, data);
  });
}

// Promise-based error handling
function readFilePromise(filename) {
  const fs = require('fs').promises;
  
  return fs.readFile(filename, 'utf8')
    .catch(error => {
      throw new Error(`Failed to read file ${filename}: ${error.message}`);
    });
}

// Async/await error handling
async function processFile(filename) {
  try {
    const data = await readFilePromise(filename);
    return data.toUpperCase();
  } catch (error) {
    console.error('File processing error:', error.message);
    throw error;
  }
}

自定义错误类

javascript
// custom-errors.js

// Base application error
class AppError extends Error {
  constructor(message, statusCode = 500, isOperational = true) {
    super(message);
    this.name = this.constructor.name;
    this.statusCode = statusCode;
    this.isOperational = isOperational;
    
    Error.captureStackTrace(this, this.constructor);
  }
}

// Specific error types
class ValidationError extends AppError {
  constructor(message, field = null) {
    super(message, 400);
    this.field = field;
  }
}

class NotFoundError extends AppError {
  constructor(resource = 'Resource') {
    super(`${resource} not found`, 404);
  }
}

class UnauthorizedError extends AppError {
  constructor(message = 'Unauthorized access') {
    super(message, 401);
  }
}

class DatabaseError extends AppError {
  constructor(message, originalError = null) {
    super(message, 500);
    this.originalError = originalError;
  }
}

// Usage examples
function validateUser(userData) {
  if (!userData.email) {
    throw new ValidationError('Email is required', 'email');
  }
  
  if (!userData.email.includes('@')) {
    throw new ValidationError('Invalid email format', 'email');
  }
  
  return true;
}

function findUser(id) {
  // Simulate database lookup
  if (id !== '1') {
    throw new NotFoundError('User');
  }
  
  return { id: '1', name: 'John Doe' };
}

module.exports = {
  AppError,
  ValidationError,
  NotFoundError,
  UnauthorizedError,
  DatabaseError
};

Express 错误处理

集中式错误处理器

javascript
// express-error-handler.js
const express = require('express');
const { AppError } = require('./custom-errors');

const app = express();
app.use(express.json());

// Async wrapper to catch errors
const asyncHandler = (fn) => {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

// Sample routes with errors
app.get('/error/sync', (req, res, next) => {
  throw new Error('Synchronous error');
});

app.get('/error/async', asyncHandler(async (req, res, next) => {
  throw new Error('Asynchronous error');
}));

app.get('/error/validation', (req, res, next) => {
  const error = new AppError('Validation failed', 400);
  next(error);
});

// 404 handler (must be before error handler)
app.use('*', (req, res, next) => {
  const error = new AppError(`Route ${req.originalUrl} not found`, 404);
  next(error);
});

// Global error handler (must be last)
app.use((error, req, res, next) => {
  // Set default error properties
  error.statusCode = error.statusCode || 500;
  error.status = error.status || 'error';

  // Log error
  console.error('Error occurred:', {
    message: error.message,
    stack: error.stack,
    url: req.originalUrl,
    method: req.method,
    ip: req.ip,
    userAgent: req.get('User-Agent')
  });

  // Send error response
  if (process.env.NODE_ENV === 'development') {
    res.status(error.statusCode).json({
      status: error.status,
      error: error,
      message: error.message,
      stack: error.stack
    });
  } else {
    // Production error response
    if (error.isOperational) {
      res.status(error.statusCode).json({
        status: error.status,
        message: error.message
      });
    } else {
      // Programming error - don't leak details
      res.status(500).json({
        status: 'error',
        message: 'Something went wrong'
      });
    }
  }
});

app.listen(3000);

日志和监控

高级日志系统

javascript
// logging-system.js
const winston = require('winston');
const path = require('path');

class Logger {
  constructor(options = {}) {
    this.logger = this.createLogger(options);
  }

  createLogger(options) {
    const logFormat = winston.format.combine(
      winston.format.timestamp(),
      winston.format.errors({ stack: true }),
      winston.format.printf(({ timestamp, level, message, stack, ...meta }) => {
        let log = `${timestamp} [${level.toUpperCase()}]: ${message}`;
        
        if (Object.keys(meta).length > 0) {
          log += ` ${JSON.stringify(meta)}`;
        }
        
        if (stack) {
          log += `\n${stack}`;
        }
        
        return log;
      })
    );

    const transports = [
      new winston.transports.File({
        filename: path.join('logs', 'error.log'),
        level: 'error',
        maxsize: 5242880, // 5MB
        maxFiles: 5
      }),
      new winston.transports.File({
        filename: path.join('logs', 'combined.log'),
        maxsize: 5242880,
        maxFiles: 5
      })
    ];

    if (process.env.NODE_ENV !== 'production') {
      transports.push(
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.colorize(),
            winston.format.simple()
          )
        })
      );
    }

    return winston.createLogger({
      level: options.level || 'info',
      format: logFormat,
      transports
    });
  }

  info(message, meta = {}) {
    this.logger.info(message, meta);
  }

  error(message, error = null, meta = {}) {
    const logData = { ...meta };
    
    if (error) {
      logData.error = {
        message: error.message,
        stack: error.stack,
        name: error.name
      };
    }
    
    this.logger.error(message, logData);
  }

  warn(message, meta = {}) {
    this.logger.warn(message, meta);
  }

  debug(message, meta = {}) {
    this.logger.debug(message, meta);
  }
}

module.exports = Logger;

恢复和弹性模式

熔断器实现

javascript
// circuit-breaker.js

class CircuitBreaker {
  constructor(fn, options = {}) {
    this.fn = fn;
    this.failureThreshold = options.failureThreshold || 5;
    this.resetTimeout = options.resetTimeout || 60000;
    this.monitoringPeriod = options.monitoringPeriod || 10000;
    
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.failureCount = 0;
    this.lastFailureTime = null;
    this.successCount = 0;
    
    this.stats = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      rejectedRequests: 0
    };
  }

  async call(...args) {
    this.stats.totalRequests++;
    
    if (this.state === 'OPEN') {
      if (this.shouldAttemptReset()) {
        this.state = 'HALF_OPEN';
        this.successCount = 0;
      } else {
        this.stats.rejectedRequests++;
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await this.fn(...args);
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.stats.successfulRequests++;
    this.failureCount = 0;
    
    if (this.state === 'HALF_OPEN') {
      this.successCount++;
      if (this.successCount >= 3) {
        this.state = 'CLOSED';
      }
    }
  }

  onFailure() {
    this.stats.failedRequests++;
    this.failureCount++;
    this.lastFailureTime = Date.now();
    
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }

  shouldAttemptReset() {
    return Date.now() - this.lastFailureTime >= this.resetTimeout;
  }

  getStats() {
    return {
      ...this.stats,
      state: this.state,
      failureCount: this.failureCount,
      successRate: this.stats.totalRequests > 0 
        ? (this.stats.successfulRequests / this.stats.totalRequests * 100).toFixed(2) + '%'
        : '0%'
    };
  }

  reset() {
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.successCount = 0;
    this.lastFailureTime = null;
  }
}

module.exports = CircuitBreaker;

下一步

在下一章中,我们将探索文件处理操作,包括读取、写入、流和文件系统管理。

关键要点

  • 适当的错误处理防止应用程序崩溃
  • 自定义错误类提供更好的错误分类
  • 集中式错误处理程序提高可维护性
  • 日志记录有助于调试和监控
  • 熔断器防止级联故障
  • 恢复模式提高应用程序的弹性

本站内容仅供学习和研究使用。