Node.js 中的结构化日志记录
一次结账失败了。你从客户端获得了请求 ID。你的日志应该让你能够同时找到支付金额、结果和拒付原因。
构建一个小型 Node.js HTTP 服务器,在每个请求中将这些详细信息记录到一个 JSON 宽事件中。你将运行一次成功的结账和一次被拒付的支付,然后将每个响应与其日志进行匹配。示例使用 Node 内置的 HTTP 服务器,因此你可以看到完整的日志记录生命周期。
运行完整的 HTTP 处理程序
安装 evlog,然后保存下面的服务器代码。它使用 Node.js HTTP 服务器,不依赖任何框架。Node.js 24 可以直接运行此 TypeScript 文件:
npm install evlog
node server.ts
/checkout 路由会模拟一次成功的支付。添加 ?decline=1 以执行错误路径。支付 ID 是 fixture 数据,因此不需要支付提供商或凭据。
import { randomUUID } from 'node:crypto'
import { createServer } from 'node:http'
import { createError, createLogger, initLogger } from 'evlog'
initLogger({ env: { service: 'checkout' }, pretty: false })
createServer((req, res) => {
const url = new URL(req.url ?? '/', 'http://localhost')
const requestId = randomUUID()
const log = createLogger({ requestId, method: req.method, path: url.pathname })
res.setHeader('x-request-id', requestId)
res.setHeader('content-type', 'application/json')
try {
if (req.method !== 'POST' || url.pathname !== '/checkout') {
res.statusCode = 404
log.set({ outcome: 'not-found' })
res.end(JSON.stringify({ error: 'Not found', requestId }))
return
}
log.set({ orderId: 'order-123', payment: { amount: 2999, currency: 'EUR' } })
if (url.searchParams.get('decline') === '1') {
throw createError({
message: 'Payment declined',
status: 402,
why: 'The simulated card has insufficient funds',
fix: 'Try another payment method',
})
}
log.set({ payment: { chargeId: 'ch_123' }, outcome: 'paid' })
res.statusCode = 200
res.end(JSON.stringify({ orderId: 'order-123', requestId }))
} catch (error) {
log.error(error instanceof Error ? error : new Error(String(error)))
log.set({ outcome: 'declined' })
res.statusCode = 402
res.end(JSON.stringify({ error: 'Payment declined', requestId }))
} finally {
log.emit({ status: res.statusCode })
}
}).listen(3000)
从另一个终端发送两个请求:
curl -i -X POST http://localhost:3000/checkout
curl -i -X POST 'http://localhost:3000/checkout?decline=1'
读取结果
响应中的 x-request-id 标头与事件的 requestId 相匹配。成功事件包含以下字段,以及环境元数据和计时字段:
{
"level": "info",
"service": "checkout",
"method": "POST",
"path": "/checkout",
"orderId": "order-123",
"payment": { "amount": 2999, "currency": "EUR", "chargeId": "ch_123" },
"outcome": "paid",
"status": 200
}
被拒付的请求会发出一个错误事件,其中包含 status: 402、outcome: 'declined',以及错误的消息、原因和建议的修复措施。两条路径都包含 durationMs,它从创建日志记录器到发出事件进行测量。此示例测量的是处理程序工作时间,而不是确认客户端已收到响应的时间。
从示例迁移到你的应用程序
选择有助于调试此操作的字段,例如订单 ID、支付结果和计时信息。不要在上下文中保留凭据和不必要的个人数据,并对需要保留的敏感字段使用脱敏。
finally 代码块会在此处理程序中正常返回和发生异常时发出日志。若进程被终止,它无法保证日志送达。
pretty: false 会在此示例中强制使用 JSON。没有此覆盖设置时,evlog 会在开发环境中选择 pretty 输出,在生产环境中选择 JSON。
对于 Express、Fastify、NestJS 或 Hono,请使用框架集成来管理请求日志记录。对于远程传送,请选择一个排空适配器及其批处理/重试配置。短生命周期进程必须按照独立 TypeScript中的说明等待待处理的传送完成。
为了在各个处理程序之间保持字段名称和值的一致性,请继续阅读类型化字段。日志记录生命周期解释了框架集成如何为你收集上下文并发出事件。