本指南介绍你在使用 evlog 时最常用的核心 API。更喜欢通过运行一个完整的服务器来学习?请按照 Node.js 中的结构化日志记录进行操作,然后回到这里查看 API 概览。
useLogger、log 和 parseError。createError 从 evlog 中导入:自动导入的那个是 Nuxt 自带的,并且它会丢弃 why、fix 和 link。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')
10:23:45.612 [auth] User logged in
10:23:45.613 ERROR [my-app] action=payment error=card_declined
10:23:45.614 [cache] Cache miss
两种调用方式:
- 标签式:
log.info('tag', 'message'),适合快速、易读的控制台输出 - 结构化:
log.info({ key: value }),适合传入 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()
10:23:45.612 信息 [sync-worker] 1204ms
├─ jobId: job_abc123
├─ queue: emails
└─ batch: size=50 processed=50
createLogger() 接受一个纯对象作为初始上下文。它会返回一个带有 set、error、info、warn、emit 和 getContext 方法的日志记录器。
对于 HTTP 请求上下文,请使用 createRequestLogger(),它会自动预填充 method、path 和 requestId:
import { createRequestLogger } from 'evlog'
const log = createRequestLogger({ method: 'POST', path: '/api/checkout' })
createLogger 和 createRequestLogger 时,必须手动调用 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 }
})
10:23:45.612 INFO [my-app] POST /api/checkout 200 in 234ms
├─ user: id=1 plan=pro
├─ cart: items=3 total=9999
└─ orderId: ord_abc123
useLogger 不会创建日志记录器;框架中间件已经创建好了。它只是从事件上下文中检索出来,以便你可以通过 set() 添加数据。何时使用什么
使用 log | 使用 createLogger() / createRequestLogger() | 使用 useLogger(event) |
|---|---|---|
| 快速的一次性事件 | 脚本、作业、工作者、队列、无框架的 HTTP 操作 | 使用框架集成的 API 路由 |
| 无需累积上下文 | 在操作过程中累积上下文 | 获取自动管理的广泛事件 |
| 客户端日志记录 | 每个操作一个广泛事件 | 访问自动管理的广泛事件 |
服务识别
在多服务架构中,使用以下方式区分日志属于哪个服务:
基于路由的配置
在 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
显式服务参数
使用第二个参数覆盖特定路由的服务名称:
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',
})
{
"statusCode": 402,
"message": "支付失败",
"data": {
"why": "卡被发卡行拒绝",
"fix": "尝试其他支付方式",
"link": "https://docs.example.com/payments/declined"
}
}
错误字段
| 字段 | 必填 | 描述 |
|---|---|---|
message | 是 | 发生了什么(面向用户) |
status | 否 | HTTP 状态码(默认:500) |
why | 否 | 技术原因(用于调试) |
fix | 否 | 可操作的解决方案 |
link | 否 | 更多信息文档 URL |
cause | 否 | 原始错误(如果包装) |
internal | 否 | 仅用于日志和广泛事件的后端字段 — 永远不会包含在 HTTP JSON 或 parseError() 中 |
前端集成
使用 parseError() 在客户端提取所有错误字段:
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>
export function useAnalytics() {
function trackEvent(event: string, data?: Record<string, unknown>) {
log.info('analytics', `事件: ${event}`)
if (data) {
log.debug({ event, ...data })
}
}
return { trackEvent }
}
第一天需要用到其中多少内容?
两个调用。此页面上的其他所有内容都可以在你需要时使用,在你还没有使用它们时也不会破坏任何东西。
log.info() 的行为类似于 console.log,因此第一次提交只是查找并替换,不会改变行为。从这里开始,最能立即带来价值的是两个调用:log.set() 会向已经在处理中的事件添加字段,而 createError() 会为失败附加 why 和 fix,而不是只有一段堆栈信息。
一次迁移一个处理程序。一个仍在调用 console.log 的路由与另一个使用 log.set() 的路由并存,是正常的过渡状态,并不是错误状态:它们写入同一个位置,彼此也互不知晓。选择你上次在生产环境中调试过的处理程序,从那里开始,或者让 evlog map 为你进行排序。
类型化字段、目录、采样和 drains 都是可选启用的;当你感到缺少它们时再使用它们,而不是在安装它们的那一天就全部采用,每一项都更值得如此使用。