从崩溃到优雅:Egg.js错误处理机制完全指南
在Node.js应用开发中,错误处理往往是决定应用健壮性的关键因素。当用户操作触发异常却看到满屏堆栈信息时,不仅影响体验,更可能泄露敏感信息。Egg.js作为企业级Node.js框架,提供了一套完整的错误处理机制,让开发者能够优雅地捕获、处理和响应各类异常。本文将深入解析Egg.js的错误处理流程,从基础捕获到高级定制,帮助你构建更可靠的应用系统。
异常捕获:同步编码模型下的错误处理
Egg.js基于Koa框架构建,借助co库实现了异步代码的同步化编写,这使得异常捕获变得简单直接——所有异步操作中产生的错误都可以通过try/catch语法捕获。这种一致性的错误处理方式极大降低了开发者的心智负担。
// app/service/test.js
try {
const res = yield this.ctx.curl('http://eggjs.com/api/echo', { dataType: 'json' });
if (res.status !== 200) throw new Error('response status is not 200');
return res.data;
} catch (err) {
this.logger.error(err);
return {};
}
官方异常捕获文档中特别强调,为了保证异常可追踪,所有抛出的异常必须是Error类型,因为只有Error对象才会包含完整的堆栈信息,这对于问题定位至关重要。
异步链断裂问题与解决方案
在实际开发中,一个常见的陷阱是异步操作跳出了Egg.js的异步调用链,导致异常无法被捕获。例如使用setImmediate、setTimeout等API创建的异步操作,会脱离Egg.js的控制流。
// 错误示例:异常无法被捕获
exports.buy = function* (ctx) {
const request = {};
const config = yield ctx.service.trade.buy(request);
// 此异步操作跳出了调用链
setImmediate(() => {
ctx.service.trade.check(request).catch(err => ctx.logger.error(err));
});
}
为解决这个问题,Egg.js提供了ctx.runInBackground辅助方法,它会将异步操作重新包装到Egg.js的异步链中,确保异常能够被统一捕获:
// 正确示例:使用runInBackground
exports.buy = function* (ctx) {
const request = {};
const config = yield ctx.service.trade.buy(request);
ctx.runInBackground(function* () {
// 此处异常会被框架捕获并记录
yield ctx.service.trade.check(request);
});
}
框架层统一异常处理:onerror插件的工作机制
Egg.js通过内置的onerror插件实现了全局统一的异常处理。该插件会捕获应用中所有未处理的异常,并根据请求的Accept头信息自动返回合适的错误响应格式。
响应策略矩阵
onerror插件的响应行为由请求格式、运行环境和配置共同决定,具体规则如下:
| 请求需求格式 | 环境 | errorPageUrl配置 | 返回内容 |
|---|---|---|---|
| HTML & TEXT | local/unittest | - | 详细错误页面(含堆栈信息) |
| HTML & TEXT | 生产环境 | 已配置 | 重定向到errorPageUrl |
| HTML & TEXT | 生产环境 | 未配置 | 简化错误页面(不含敏感信息) |
| JSON | local/unittest | - | 含详细信息的JSON对象 |
| JSON | 生产环境 | - | 仅含基本信息的JSON对象 |
错误页面重定向配置
在生产环境中,为了避免向用户暴露详细的错误信息,通常会配置错误页面重定向。通过修改config/config.default.js文件,可以设置统一的错误页面URL:
// config/config.default.js
module.exports = {
onerror: {
// 线上环境发生异常时重定向到此页面
errorPageUrl: '/50x.html',
},
};
自定义异常处理:打造专属错误响应
尽管框架提供了默认的异常处理机制,但在实际项目中,我们常常需要根据业务需求定制错误响应格式。Egg.js允许通过中间件的方式完全掌控异常处理流程。
自定义错误处理中间件
创建一个位于app/middleware目录下的错误处理中间件,例如error_handler.js:
// app/middleware/error_handler.js
module.exports = () => {
return function* errorHandler(next) {
try {
yield next;
} catch (err) {
// 必须触发app的error事件,框架会统一记录错误日志
this.app.emit('error', err, this);
// 自定义错误响应格式
this.body = {
success: false,
errorCode: err.code || 'INTERNAL_ERROR',
message: this.app.config.env === 'prod'
? '服务器内部错误'
: err.message,
requestId: this.header['x-request-id'] || this.ctx.requestId,
};
// 设置状态码
this.status = err.status || 500;
}
};
};
配置中间件作用范围
创建好中间件后,需要在配置中启用并设置其作用范围。通过match或ignore配置,可以精确控制哪些路由使用自定义错误处理:
// config/config.default.js
module.exports = {
middleware: [ 'errorHandler' ],
errorHandler: {
// 只对/api前缀的请求生效
match: '/api',
// 也可以使用ignore排除某些路径
// ignore: '/admin',
},
};
404处理:资源不存在时的优雅响应
Egg.js将404状态视为正常的业务响应而非异常,但仍提供了灵活的定制机制。默认情况下,框架会根据请求的Accept头返回不同格式的404响应:
- JSON请求返回:
{ "message": "Not Found" } - HTML请求返回:
<h1>404 Not Found</h1>
404页面重定向配置
通过修改配置,可以将HTML请求的404响应重定向到自定义页面:
// config/config.default.js
module.exports = {
notfound: {
pageUrl: '/404.html',
},
};
404响应自定义中间件
类似于异常处理,404响应也可以通过中间件完全定制。创建app/middleware/notfound_handler.js文件:
// app/middleware/notfound_handler.js
module.exports = () => {
return function* (next) {
yield next;
if (this.status === 404 && !this.body) {
if (this.acceptJSON) {
this.body = {
error: 'Not Found',
path: this.path,
method: this.method
};
} else {
this.body = '<!DOCTYPE html><html><head><title>页面不存在</title></head>' +
'<body><h1>404 - 页面不存在</h1><p>您请求的资源不存在</p></body></html>';
}
}
};
};
然后在配置中启用该中间件:
// config/config.default.js
module.exports = {
middleware: [ 'notfoundHandler' ],
};
错误处理最佳实践
错误日志记录
Egg.js提供了强大的日志系统,建议在捕获异常时使用this.logger.error()方法记录错误详情,便于问题排查:
try {
// 业务逻辑
} catch (err) {
// 记录错误日志,包含堆栈信息
this.logger.error('交易处理失败', {
requestId: this.ctx.requestId,
userId: this.ctx.session.userId,
error: err
});
// 业务处理...
}
错误监控与告警
对于企业级应用,仅仅记录错误日志是不够的。建议结合Egg.js的日志系统和第三方监控服务(如Sentry、Datadog等),实现错误的实时监控和告警。通过监听app的error事件,可以集中处理错误上报:
// app.js
module.exports = app => {
app.on('error', (err, ctx) => {
// 忽略404错误
if (err.status === 404) return;
// 上报错误到监控系统
ctx.service.monitor.reportError({
err,
url: ctx.url,
userId: ctx.session.userId,
ip: ctx.ip,
timestamp: Date.now()
});
});
};
总结与实践建议
Egg.js的错误处理机制为开发者提供了从基础到高级的完整解决方案。在实际开发中,建议遵循以下原则:
- 统一错误格式:定义项目级的错误基类,包含错误码、消息、状态码等标准字段
- 分级日志策略:根据错误严重程度使用不同级别的日志方法(debug/info/warn/error)
- 环境差异化处理:开发环境展示详细错误信息,生产环境返回安全的用户提示
- 完善的监控告警:建立错误监控体系,及时发现并解决线上问题
通过合理利用Egg.js提供的错误处理能力,结合项目实际需求进行定制,可以显著提升应用的健壮性和用户体验。完整的错误处理机制不仅是应用质量的体现,也是开发团队工程能力的重要标志。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



