You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AdonisJS ExceptionHandler:定位报错方法与记录完整错误日志

问题描述

我需要把错误存储到logs表,已在Handler.ts中编写相关代码,但存在两个问题:

  1. 如何确定报错所属的方法,以填充Log.create的type字段;
  2. 当前仅能记录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包含当前请求的路由信息,可从中提取控制器和方法名:

  1. 写一个工具函数从路由中解析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。

  1. 非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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 22:50:38