错误处理
概述
健壮的错误处理对于 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;下一步
在下一章中,我们将探索文件处理操作,包括读取、写入、流和文件系统管理。
关键要点
- 适当的错误处理防止应用程序崩溃
- 自定义错误类提供更好的错误分类
- 集中式错误处理程序提高可维护性
- 日志记录有助于调试和监控
- 熔断器防止级联故障
- 恢复模式提高应用程序的弹性