入门

快速开始

几分钟内上手 evlog。学习日志 API、为广泛事件创建 createLogger、处理请求的 useLogger,以及结构化错误。

本指南介绍你在使用 evlog 时最常用的核心 API。更喜欢通过运行一个完整的服务器来学习?请按照 Node.js 中的结构化日志记录进行操作,然后回到这里查看 API 概览。

在 Nuxt 中,evlog 会自动导入useLoggerlogparseErrorcreateErrorevlog 中导入:自动导入的那个是 Nuxt 自带的,并且它会丢弃 whyfixlink

2 分钟内让 evlog 运行起来

log(简单日志记录)

使用 evlog 最简单的方式。随时随地发送结构化日志,无需担心日志处理:

import { log } from 'evlog'

log.info('auth', 'User logged in')
log.error({ action: 'payment', error: 'card_declined' })
log.warn('cache', 'Cache miss')

两种调用方式:

  • 标签式log.info('tag', 'message'),适合快速、易读的控制台输出
  • 结构化log.info({ key: value }),适合传入 drain 管道的丰富事件
查看完整的简单日志记录指南,了解所有模式和 drain 集成。

createLogger(广泛事件)

当你需要在操作的多个步骤中累积上下文时,无论是在脚本、后台作业、队列工作者还是工作流中,请使用 createLogger

import { initLogger, createLogger } from 'evlog'

initLogger({ env: { service: 'sync-worker' } })

const log = createLogger({ jobId: job.id, queue: 'emails' })

log.set({ batch: { size: 50 } })
log.set({ batch: { processed: 50 } })
log.emit()

createLogger() 接受一个纯对象作为初始上下文。它会返回一个带有 seterrorinfowarnemitgetContext 方法的日志记录器。

对于 HTTP 请求上下文,请使用 createRequestLogger(),它会自动预填充 methodpathrequestId

src/worker.ts
import { createRequestLogger } from 'evlog'

const log = createRequestLogger({ method: 'POST', path: '/api/checkout' })
使用 createLoggercreateRequestLogger 时,必须手动调用 log.emit()。在框架集成中,此操作会自动完成。

useLogger(获取请求日志记录器)

在使用框架集成(Nuxt、Hono、Express 等)时,中间件会在请求开始时自动创建一个广泛事件日志记录器,并在响应结束时发出它。useLogger(event) 从请求上下文中获取该日志记录器:

import { useLogger } from 'evlog'

export default defineEventHandler(async (event) => {
  const log = useLogger(event)

  log.set({ user: { id: 1, plan: 'pro' } })
  log.set({ cart: { items: 3, total: 9999 } })

  const order = await processCheckout()
  log.set({ orderId: order.id })

  return { success: true, orderId: order.id }
})
useLogger 不会创建日志记录器;框架中间件已经创建好了。它只是从事件上下文中检索出来,以便你可以通过 set() 添加数据。

何时使用什么

使用 log使用 createLogger() / createRequestLogger()使用 useLogger(event)
快速的一次性事件脚本、作业、工作者、队列、无框架的 HTTP 操作使用框架集成的 API 路由
无需累积上下文在操作过程中累积上下文获取自动管理的广泛事件
客户端日志记录每个操作一个广泛事件访问自动管理的广泛事件

服务识别

在多服务架构中,使用以下方式区分日志属于哪个服务:

基于路由的配置

nuxt.config.ts 中按路由模式配置服务名称:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],

  evlog: {
    env: {
      service: 'default-service',
    },
    routes: {
      '/api/auth/**': { service: 'auth-service' },
      '/api/payment/**': { service: 'payment-service' },
      '/api/booking/**': { service: 'booking-service' },
    },
  },
})

匹配这些路由模式的日志会自动包含配置的服务名称:

输出
21:57:10.442 INFO [auth-service] POST /api/auth/login 200 in 1ms
  ├─ requestId: 88ced16a-bef2-4483-86cb-2b4fb677ea52
  ├─ user: id=user_123 [email protected]
  └─ action: login

显式服务参数

使用第二个参数覆盖特定路由的服务名称:

server/api/legacy/process.post.ts
import { useLogger } from 'evlog'

export default defineEventHandler((event) => {
  const log = useLogger(event, 'legacy-service')

  log.set({ action: 'process_legacy_request' })

  return { success: true }
})
优先级顺序: 显式 useLogger 参数 > 路由配置 > env.service > 自动检测自环境

createError(结构化错误)

使用 createError() 抛出带有可操作上下文的错误:

import { createError } from 'evlog'

throw createError({
  message: '支付失败',
  status: 402,
  why: '卡被发卡行拒绝',
  fix: '尝试其他支付方式',
  link: 'https://docs.example.com/payments/declined',
})

错误字段

字段必填描述
message发生了什么(面向用户)
statusHTTP 状态码(默认:500)
why技术原因(用于调试)
fix可操作的解决方案
link更多信息文档 URL
cause原始错误(如果包装)
internal仅用于日志和广泛事件的后端字段 — 永远不会包含在 HTTP JSON 或 parseError()

前端集成

使用 parseError() 在客户端提取所有错误字段:

composables/useCheckout.ts
import { parseError } from 'evlog'

export async function checkout(cart: Cart) {
  try {
    await $fetch('/api/checkout', { method: 'POST', body: cart })
  } catch (err) {
    const error = parseError(err)

    toast.add({
      title: error.message,
      description: error.why,
      color: 'error',
      actions: error.link
        ? [{ label: '了解更多', onClick: () => window.open(error.link) }]
        : undefined,
    })

    if (error.fix) {
      console.info(`修复:${error.fix}`)
    }
  }
}

log(客户端)

相同的 log API 在客户端也适用,输出到浏览器控制台:

<script setup lang="ts">
async function handleCheckout() {
  log.info('checkout', '用户已发起结算')

  try {
    await $fetch('/api/checkout', { method: 'POST' })
    log.info({ action: 'checkout', status: 'success' })
  } catch (err) {
    log.error({ action: 'checkout', error: 'failed' })
  }
}
</script>
参阅客户端日志记录,了解 drain 配置、身份上下文和浏览器设置。

第一天需要用到其中多少内容?

两个调用。此页面上的其他所有内容都可以在你需要时使用,在你还没有使用它们时也不会破坏任何东西。

log.info() 的行为类似于 console.log,因此第一次提交只是查找并替换,不会改变行为。从这里开始,最能立即带来价值的是两个调用:log.set() 会向已经在处理中的事件添加字段,而 createError() 会为失败附加 whyfix,而不是只有一段堆栈信息。

一次迁移一个处理程序。一个仍在调用 console.log 的路由与另一个使用 log.set() 的路由并存,是正常的过渡状态,并不是错误状态:它们写入同一个位置,彼此也互不知晓。选择你上次在生产环境中调试过的处理程序,从那里开始,或者让 evlog map 为你进行排序。

类型化字段、目录、采样和 drains 都是可选启用的;当你感到缺少它们时再使用它们,而不是在安装它们的那一天就全部采用,每一项都更值得如此使用。

后续步骤

  • evlog map:查看你刚刚解锁的内容。评估覆盖率,并找到仍然未覆盖的入口(npx @evlog/cli map
  • 日志概览:了解全部三种日志记录模式
  • 广泛事件:学习如何设计有效的广泛事件
  • 类型化字段:为广泛事件添加编译时类型安全
  • 结构化错误:掌握使用 evlog 进行错误处理的方法
  • 最佳实践:安全指南和生产环境技巧。