用例

Node.js 中的结构化日志记录

构建一个 Node.js HTTP 处理程序,记录结构化请求日志、明确的结果、错误上下文,以及一个可供查询的 JSON 事件。

一次结账失败了。你从客户端获得了请求 ID。你的日志应该让你能够同时找到支付金额、结果和拒付原因。

构建一个小型 Node.js HTTP 服务器,在每个请求中将这些详细信息记录到一个 JSON 宽事件中。你将运行一次成功的结账和一次被拒付的支付,然后将每个响应与其日志进行匹配。示例使用 Node 内置的 HTTP 服务器,因此你可以看到完整的日志记录生命周期。

运行完整的 HTTP 处理程序

安装 evlog,然后保存下面的服务器代码。它使用 Node.js HTTP 服务器,不依赖任何框架。Node.js 24 可以直接运行此 TypeScript 文件:

Terminal
npm install evlog
node server.ts

/checkout 路由会模拟一次成功的支付。添加 ?decline=1 以执行错误路径。支付 ID 是 fixture 数据,因此不需要支付提供商或凭据。

server.ts
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)

从另一个终端发送两个请求:

Terminal
curl -i -X POST http://localhost:3000/checkout
curl -i -X POST 'http://localhost:3000/checkout?decline=1'

读取结果

响应中的 x-request-id 标头与事件的 requestId 相匹配。成功事件包含以下字段,以及环境元数据和计时字段:

Successful event, selected fields
{
  "level": "info",
  "service": "checkout",
  "method": "POST",
  "path": "/checkout",
  "orderId": "order-123",
  "payment": { "amount": 2999, "currency": "EUR", "chargeId": "ch_123" },
  "outcome": "paid",
  "status": 200
}

被拒付的请求会发出一个错误事件,其中包含 status: 402outcome: 'declined',以及错误的消息、原因和建议的修复措施。两条路径都包含 durationMs,它从创建日志记录器到发出事件进行测量。此示例测量的是处理程序工作时间,而不是确认客户端已收到响应的时间。

从示例迁移到你的应用程序

选择有助于调试此操作的字段,例如订单 ID、支付结果和计时信息。不要在上下文中保留凭据和不必要的个人数据,并对需要保留的敏感字段使用脱敏

finally 代码块会在此处理程序中正常返回和发生异常时发出日志。若进程被终止,它无法保证日志送达。

pretty: false 会在此示例中强制使用 JSON。没有此覆盖设置时,evlog 会在开发环境中选择 pretty 输出,在生产环境中选择 JSON。

对于 Express、Fastify、NestJS 或 Hono,请使用框架集成来管理请求日志记录。对于远程传送,请选择一个排空适配器及其批处理/重试配置。短生命周期进程必须按照独立 TypeScript中的说明等待待处理的传送完成。

为了在各个处理程序之间保持字段名称和值的一致性,请继续阅读类型化字段日志记录生命周期解释了框架集成如何为你收集上下文并发出事件。