Appearance
Koa错误处理
错误处理是Koa应用开发中的关键部分。Koa提供了多层次的错误处理机制,从中间件内部错误处理到全局错误处理,确保应用的稳定性和可靠性。
Koa错误处理概述
Koa的错误处理基于async/await,使得错误处理更加直观和可控。Koa应用会捕获中间件中抛出的错误并提供全局错误处理机制。
中间件内错误处理
基础错误捕获
javascript
app.use(async (ctx, next) => {
try {
await next(); // 将控制权传递给下一个中间件
} catch (err) {
// 处理错误
ctx.status = err.status || 500;
ctx.body = {
error: err.message
};
// 记录错误
console.error('Middleware error:', err);
}
});
具体错误处理示例
javascript
app.use(async (ctx, next) => {
try {
// 业务逻辑
const user = await findUser(ctx.params.id);
if (!user) {
// 抛出自定义错误
const err = new Error('User not found');
err.status = 404;
throw err;
}
ctx.body = user;
await next();
} catch (err) {
// 根据错误类型进行不同处理
if (err.status === 404) {
ctx.status = 404;
ctx.body = { error: 'User not found' };
} else if (err.status === 400) {
ctx.status = 400;
ctx.body = { error: 'Bad request', details: err.message };
} else {
// 未知错误
ctx.status = 500;
ctx.body = { error: 'Internal server error' };
}
console.error('Request error:', err);
}
});
全局错误处理
应用级错误事件
javascript
const app = new Koa();
// 监听应用错误事件
app.on('error', (err, ctx) => {
console.error('Application error:', err);
// 发送错误到监控服务
if (process.env.NODE_ENV === 'production') {
// 发送到错误监控服务,如Sentry
// errorReportingService.captureException(err, { context: ctx });
}
});
app.use(async (ctx, next) => {
await next();
});
app.listen(3000);
统一错误处理中间件
javascript
const errorHandler = async (ctx, next) => {
try {
await next();
} catch (err) {
// 设置错误状态
ctx.status = err.status || 500;
// 根据环境返回不同错误信息
if (ctx.app.env === 'development') {
// 开发环境返回详细错误信息
ctx.body = {
error: err.message,
stack: err.stack,
status: ctx.status
};
} else {
// 生产环境返回通用错误信息
ctx.body = {
error: ctx.status === 500 ? 'Internal Server Error' : err.message,
status: ctx.status
};
}
// 记录错误
ctx.app.emit('error', err, ctx);
}
};
app.use(errorHandler);
自定义错误类
创建应用错误类
javascript
class AppError extends Error {
constructor(message, statusCode, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = isOperational; // 标识是否为可操作错误
Error.captureStackTrace(this, this.constructor);
}
}
// 具体错误类型
class ValidationError extends AppError {
constructor(message) {
super(message, 400);
this.type = 'VALIDATION_ERROR';
}
}
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404);
this.type = 'NOT_FOUND_ERROR';
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(message, 401);
this.type = 'UNAUTHORIZED_ERROR';
}
}
class ForbiddenError extends AppError {
constructor(message = 'Forbidden') {
super(message, 403);
this.type = 'FORBIDDEN_ERROR';
}
}
使用自定义错误
javascript
app.use(async (ctx, next) => {
try {
if (ctx.path === '/protected' && !ctx.state.user) {
throw new UnauthorizedError('Authentication required');
}
if (ctx.path.startsWith('/admin') && ctx.state.user.role !== 'admin') {
throw new ForbiddenError('Admin access required');
}
await next();
} catch (err) {
if (err instanceof AppError) {
ctx.status = err.statusCode;
ctx.body = {
status: err.status,
message: err.message,
...(ctx.app.env === 'development' && { stack: err.stack })
};
} else {
// 未知错误
ctx.status = 500;
ctx.body = { status: 'error', message: 'Something went wrong' };
}
ctx.app.emit('error', err, ctx);
}
});
异步操作错误处理
异步中间件错误
javascript
// 正确的异步错误处理
app.use(async (ctx, next) => {
try {
// 异步操作
const data = await asyncOperation();
ctx.body = data;
await next();
} catch (err) {
// 异步错误会被正确捕获
ctx.status = 500;
ctx.body = { error: err.message };
}
});
// 错误的做法 - 会导致未处理的Promise拒绝
app.use((ctx, next) => {
asyncOperation()
.then(data => {
ctx.body = data;
})
.catch(err => {
// 这个错误可能不会被正确处理
ctx.status = 500;
ctx.body = { error: err.message };
});
});
数据库操作错误处理
javascript
const handleDbError = (err) => {
if (err.name === 'ValidationError') {
return new ValidationError('Data validation failed');
}
if (err.name === 'CastError') {
return new ValidationError('Invalid ID format');
}
if (err.code === 11000) {
return new ValidationError('Duplicate key error');
}
// 返回原始错误
return err;
};
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
// 处理数据库错误
const processedErr = handleDbError(err);
if (processedErr !== err) {
ctx.status = processedErr.statusCode;
ctx.body = { error: processedErr.message };
} else {
// 未处理的错误
ctx.status = 500;
ctx.body = { error: 'Database error occurred' };
}
ctx.app.emit('error', err, ctx);
}
});
错误日志和监控
结构化错误日志
javascript
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// 错误处理中间件集成日志
const loggingErrorHandler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
// 记录错误日志
logger.error({
message: err.message,
stack: err.stack,
status: ctx.status,
method: ctx.method,
url: ctx.url,
ip: ctx.ip,
userAgent: ctx.get('User-Agent'),
timestamp: new Date().toISOString()
});
// 返回错误响应
ctx.body = {
error: ctx.status === 500 ? 'Internal Server Error' : err.message,
status: ctx.status
};
// 发出错误事件
ctx.app.emit('error', err, ctx);
}
};
app.use(loggingErrorHandler);
404错误处理
404处理中间件
javascript
// 404处理应该在所有路由之后
app.use(async (ctx, next) => {
await next(); // 让其他中间件先处理
// 如果状态仍然是404且没有响应体,则是未找到路由
if (ctx.status === 404 && !ctx.body) {
ctx.status = 404;
ctx.body = {
error: 'Not Found',
path: ctx.path,
method: ctx.method
};
}
});
// 或者使用更具体的404处理
app.use(async (ctx, next) => {
try {
await next();
// 如果没有找到路由
if (ctx.status === 404) {
ctx.throw(404, `Route ${ctx.path} not found`);
}
} catch (err) {
if (err.status === 404) {
ctx.status = 404;
ctx.body = {
error: 'Resource not found',
requestedPath: ctx.path
};
} else {
throw err; // 重新抛出其他错误
}
}
});
错误处理最佳实践
1. 分层错误处理
javascript
// 应用层错误处理
const appErrorHandler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
status: err.status >= 500 ? 'error' : 'fail',
message: err.expose ? err.message : 'Internal server error'
};
ctx.app.emit('error', err, ctx);
}
};
// 业务逻辑层错误处理
const businessErrorHandler = async (ctx, next) => {
try {
await next();
} catch (err) {
if (err.type === 'BUSINESS_ERROR') {
ctx.status = err.statusCode;
ctx.body = {
status: 'fail',
message: err.message,
code: err.code
};
} else {
throw err; // 让上层处理其他错误
}
}
};
// 按顺序应用错误处理中间件
app.use(appErrorHandler);
app.use(businessErrorHandler);
2. 环境特定错误处理
javascript
const createErrorHandler = (env) => {
return async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
if (env === 'development') {
// 开发环境返回详细错误
ctx.body = {
error: {
message: err.message,
stack: err.stack,
status: ctx.status
}
};
} else {
// 生产环境返回安全错误信息
ctx.body = {
error: {
message: ctx.status < 500 ? err.message : 'Internal server error',
status: ctx.status
}
};
// 在生产环境中记录完整错误
ctx.app.emit('error', err, ctx);
}
}
};
};
app.use(createErrorHandler(app.env));
3. 错误分类处理
javascript
const errorTypes = {
VALIDATION: 'VALIDATION_ERROR',
AUTHENTICATION: 'AUTHENTICATION_ERROR',
AUTHORIZATION: 'AUTHORIZATION_ERROR',
NOT_FOUND: 'NOT_FOUND_ERROR',
DATABASE: 'DATABASE_ERROR',
NETWORK: 'NETWORK_ERROR'
};
const categorizedErrorHandler = async (ctx, next) => {
try {
await next();
} catch (err) {
// 根据错误类型进行处理
switch (err.type) {
case errorTypes.VALIDATION:
ctx.status = 400;
ctx.body = {
error: 'Validation failed',
details: err.details,
status: 'fail'
};
break;
case errorTypes.AUTHENTICATION:
ctx.status = 401;
ctx.body = {
error: 'Authentication required',
status: 'fail'
};
break;
case errorTypes.AUTHORIZATION:
ctx.status = 403;
ctx.body = {
error: 'Insufficient permissions',
status: 'fail'
};
break;
case errorTypes.NOT_FOUND:
ctx.status = 404;
ctx.body = {
error: err.message || 'Resource not found',
status: 'fail'
};
break;
default:
ctx.status = 500;
ctx.body = {
error: 'Internal server error',
status: 'error'
};
}
ctx.app.emit('error', err, ctx);
}
};
app.use(categorizedErrorHandler);
未捕获异常处理
进程级错误处理
javascript
// 处理未捕获的异常
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1); // 退出进程
});
// 处理未处理的Promise拒绝
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1); // 退出进程
});
// SIGTERM信号处理
process.on('SIGTERM', () => {
console.log('Received SIGTERM, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
});
});
通过完善的错误处理机制,可以确保Koa应用在面对各种错误情况时都能做出适当的响应,提供良好的用户体验和系统稳定性。