AdonisJS ExceptionHandler:定位报错方法与记录完整错误日志
问题描述
我需要把错误存储到logs表,已在Handler.ts中编写相关代码,但存在两个问题:
- 如何确定报错所属的方法,以填充
Log.create的type字段; - 当前仅能记录Insomnia返回的简化错误信息,VS终端显示的包含堆栈、数据库详情的完整错误无法记录,怎么实现完整错误日志的存储?
当前Handler.ts代码:
export default class ExceptionHandler extends HttpExceptionHandler { constructor() { super(Logger) } public async handle(error: any, ctx: HttpContextContract) { console.log(error.messages) await Log.create({ type: 'menus.store', // if we get error in menu store function message: error.messages, }) return super.handle(error, ctx) } }
Insomnia返回的简化错误示例:
{"errors":[{"rule":"number","field":"menuId","message":"number validation failed on menuId"},{"rule":"exists","field":"menuId","message":"exists validation failed on menuId"}]}
VS终端显示的完整错误示例:
FATAL [17-09-2022, 9:07:53 AM] (projectName/30718 on system): "exists" validation rule failed err: { "type": "DatabaseError", "message": "select 1 from \"menus\" where \"id\" = $1 limit $2 - invalid input syntax for type integer: \"sd\"", "stack": error: select 1 from "menus" where "id" = $1 limit $2 - invalid input syntax for type integer: "sd" at Parser.parseErrorMessage (/home/biping/Development/projectName/backend/node_modules/pg-protocol/src/parser.ts:369:69) at Parser.handlePacket (/home/biping/Development/projectName/backend/node_modules/pg-protocol/src/parser.ts:188:21) at Parser.parse (/home/biping/Development/projectName/backend/node_modules/pg-protocol/src/parser.ts:103:30) at Socket.<anonymous> (/home/biping/Development/projectName/backend/node_modules/pg-protocol/src/index.ts:7:48) at Socket.emit (node:events:513:28) at addChunk (node:internal/streams/readable:315:12) at readableAddChunk (node:internal/streams/readable:289:9) at Socket.Readable.push (node:internal/streams/readable:228:10) at TCP.onStreamRead (node:internal/stream_base_commons:190:23) at TCP.callbackTrampoline (node:internal/async_hooks:130:17) "length": 103, "name": "error", "severity": "ERROR", "code": "22P02", "file": "numutils.c", "line": "256", "routine": "pg_strtoint32" }
解决方案
问题1:获取报错所属方法,填充type字段
AdonisJS的HttpContextContract包含当前请求的路由信息,可从中提取控制器和方法名:
- 写一个工具函数从路由中解析type:
private getErrorType(ctx: HttpContextContract, error: any): string { // 优先取错误元数据里手动设置的type(非HTTP场景可用) if (error.meta?.type) { return error.meta.type } // 从路由handler提取控制器和方法 if (!ctx.route?.handler) return 'unknown' const [controller, method] = ctx.route.handler.split('.') const controllerName = controller.replace('Controller', '').toLowerCase() return `${controllerName}.${method}` }
比如路由是Route.post('/menus', 'MenusController.store'),会返回menus.store。
- 非HTTP场景(比如队列任务、服务类),抛错时手动附加type:
// 在服务类里抛错 throw new Error('数据库查询失败').withMeta({ type: 'menus.query' })
问题2:记录完整错误日志
不要只存error.messages,要把错误的堆栈、数据库编码等关键信息都序列化存储:
方案1:存储结构化的完整错误信息
修改Log.create的参数,把错误的核心字段打包成JSON:
public async handle(error: any, ctx: HttpContextContract) { const errorType = this.getErrorType(ctx, error) const fullError = { userMessage: error.messages || error.message, stack: error.stack, errorCode: error.code, // 数据库错误码如22P02 severity: error.severity, errorType: error.type, // 如DatabaseError timestamp: new Date().toISOString() } await Log.create({ type: errorType, message: JSON.stringify(fullError), }) return super.handle(error, ctx) }
方案2:直接序列化整个错误对象(注意循环引用)
如果需要保留所有错误细节,可直接序列化整个error对象,处理循环引用的话可以用Object.getOwnPropertyNames筛选字段:
await Log.create({ type: errorType, message: JSON.stringify(error, Object.getOwnPropertyNames(error)), })
修改后的完整Handler.ts代码
export default class ExceptionHandler extends HttpExceptionHandler { constructor() { super(Logger) } private getErrorType(ctx: HttpContextContract, error: any): string { if (error.meta?.type) { return error.meta.type } if (!ctx.route?.handler) return 'unknown' const [controller, method] = ctx.route.handler.split('.') const controllerName = controller.replace('Controller', '').toLowerCase() return `${controllerName}.${method}` } public async handle(error: any, ctx: HttpContextContract) { const errorType = this.getErrorType(ctx, error) const fullError = { userMessage: error.messages || error.message, stack: error.stack, errorCode: error.code, severity: error.severity, errorType: error.type, meta: error.meta, timestamp: new Date().toISOString() } await Log.create({ type: errorType, message: JSON.stringify(fullError), }) return super.handle(error, ctx) } }
内容的提问来源于stack exchange,提问作者Boychik
相关产品推荐
相关产品推荐

