Fastify+PostgreSQL后端中JS错误对象的字段访问异常问题
Fastify+PostgreSQL错误对象字段访问异常问题解析
问题背景
基于Fastify+PostgreSQL开发后端时,用户注册功能向数据库写入数据阶段,优化错误处理时发现:pg库抛出的错误对象在特定日志调用方式下,会出现字段丢失的异常,尤其是type字段无法在日志中正常输出。
错误对象结构
pg库抛出的DatabaseError对象完整结构如下:
err: { "type": "DatabaseError", "message": "duplicate key value violates unique constraint \"users_login_key\"", "stack": "error: duplicate key value violates unique constraint \"users_login_key\"\n at C:\\Users\\galec\\Dev\\Lottery\\backend-lottery\\node_modules\\pg\\lib\\client.js:526:17\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async file:///C:/Users/galec/Dev/Lottery/backend-lottery/src/routes/register.js:46:13", "length": 202, "name": "error", "severity": "ERROR", "code": "23505", "detail": "Key (login)=(alaverdyan) already exists.", "schema": "public", "table": "users", "constraint": "users_login_key", "file": "nbtinsert.c", "line": "649", "routine": "_bt_check_unique" }
错误捕获代码
当前捕获错误并输出日志的代码片段:
try { // 数据库事务逻辑 } catch (err) { app.log.error({ msg: `Error adding user '${login}' to table 'users'`, 'Error code': err.code, 'Error type': err.type, 'Message': err.message, 'Detail': err.detail, 'DB table': err.table, 'DB constraint': err.constraint, 'Stack': err.stack, }) }
日志输出差异
不同日志调用方式的结果存在明显差异:
app.log.error(err):输出包含type字段的完整err对象app.log.error({ err: err }):输出包含type字段的完整err对象app.log.error({ error: err }):输出的err对象丢失type、message、stack等字段
问题原因
核心原因是Pino日志库对键名为error的对象有特殊序列化逻辑:
- Pino内部会自动识别键名为
error的Error实例,并使用内置的错误序列化器处理 - 内置序列化器仅保留标准Error对象的属性(
name、message、stack),而pg库抛出的DatabaseError属于自定义Error,其扩展的type、code、detail等属性不在Pino默认序列化白名单中,因此被过滤 - 当使用
err作为键名或直接传入err对象时,Pino不会触发这个特殊序列化逻辑,因此能保留所有字段
解决方案
针对该问题,可采用以下几种解决方式:
- 避免使用
error作为键名:将日志对象中的键名改为dbError或其他名称,比如app.log.error({ dbError: err }) - 自定义Pino序列化器:配置Fastify时添加自定义错误序列化逻辑,保留需要的字段:
const fastify = require('fastify')({ logger: { serializers: { error: (err) => ({ type: err.type, message: err.message, stack: err.stack, code: err.code, detail: err.detail, table: err.table, constraint: err.constraint }) } } }) - 手动提取字段:在日志输出前手动提取需要的错误字段,再传入日志对象,比如:
app.log.error({ msg: `Error adding user '${login}' to table 'users'`, error: { type: err.type, code: err.code, message: err.message, detail: err.detail } })
内容的提问来源于stack exchange,提问作者Georgii Galechyan
相关产品推荐
相关产品推荐

