入门

为什么从 evlog 开始

添加结构化日志最省成本的时机是在第一次请求之前。在第 0 天选择 evlog,系统的其余部分都会继承它。

添加结构化日志最省成本的时机是在第一次请求之前。等你拥有 200 个路由、40 个后台任务,并且每个文件里都有一个 console.log 时,你就要开始为一个从未真正做出过的决定支付利息了。evlog 专为第 0 天的选择而设计:只需选择一次,系统的其余部分就会继承结构化日志、结构化错误、类型化目录、AI SDK 遥测、审计轨迹,以及无需日后自行构建的 drain 管道。

已经在使用 console.log 或 pino 发布了吗?evlog 依然更胜一筹,但理由有所不同。参见 evlog vs pino、winston、consola

从第一天起你就能得到什么

evlog 不是一个供你包装的小型原语。它是一个单一依赖,默认提供结构化接口、集成生态和有明确设计取舍的 drain 管道。

日志基础原语

自动脱敏

PII(邮箱、银行卡、IP、电话号码、JWT、Bearer 令牌、IBAN)会在控制台输出之前以及任何 drain 之前被掩码。参见 自动脱敏

结构化错误

每个错误都已经携带 whyfixlink,可直接给值班人员(以及你未来的 AI 代理)使用。参见 结构化错误

宽事件

在一次操作过程中累积上下文,并在操作结束时发出一个类型化事件,这是让请求、任务和工作流能够端到端查询的可观测性模式。参见 宽事件

类型化目录

defineErrorCatalogdefineAuditCatalog 为你提供类似枚举、可安全重构的代码和动作。可以从两个条目开始,扩展到一个已发布的包。参见 目录

头采样 + 尾采样

在发出时丢弃低重要性事件,对慢请求和错误强制保留。配置一次,长期受益。参见 采样

Drain 管道

内置批处理、带指数退避的重试,以及向多个目标进行扇出。无需自行编写 drain 管道。

不止是 logger

这些原语只是基本要求,每个现代 logger 都具备其中某种形式。evlog 在第 1 天就能占据一席之地,靠的是围绕它们连接起来的所有能力,用来解决那些你尚未遇到的问题。

想象一下你给应用添加 AI。 迟早你会把 Vercel AI SDK 接入某个路由。令牌成本让你感到意外,模型在流式传输中途卡住,工具返回垃圾结果。借助 AI SDK 集成,每次模型调用都会自动成为一个包含提示词、工具、令牌、延迟和成本的宽事件。而且如果你使用 Better Auth,evlog 会为你将操作者身份关联到这些事件,这样无需编写任何管道代码,你就能回答“刚才是哪个用户在一次对话中花掉了 14 美元?”

想象一下你的技术栈横跨多个框架。 一个 Nuxt 前端、一个 Hono 内部服务、一个 AWS Lambda webhook:大多数团队最终都会形成这样的组合。evlog 拥有 13+ 个框架集成,每个集成都提供相同的日志原语(useLoggerlog.setcreateError)。处理器本身仍然保持框架的形态,这部分由你负责,但每次跨越一个运行时,你无需重新学习一个 logger,它们背后的 drain 管道也始终保持一致。

想象一下你希望开发环境中的日志比生产环境更嘈杂。 在本地开发期间,你会添加一些 log.debug 调用(完整的请求体、每次重试、每个守卫),以便真正看清发生了什么。这些内容不应该被发布到线上。Vite 插件 会在构建时移除选定的日志级别,因此开发环境拥有你想要的详细上下文,而生产环境保持简洁。除此之外,每个保留下来的调用都会自动注入其源代码位置(file.ts:42),因此当事件进入你的仪表板时,你能准确知道是哪一行发出的。

想象一下用户从浏览器报告了一个 bug。 错误发生在用户的会话中,深藏在一个你无法复现的 fetch 内部。evlog 的浏览器 logger 会将客户端事件发送到你的服务器,在那里它们会与请求的其余部分合并成同一个宽事件:无论错误从哪里产生,最终都是一个类型化事件。将它与内置增强器结合使用,你还可以免费获得附加的 UA、GeoIP 和 W3C trace context。

想象一下你的技术栈更换了供应商。 你最初使用 stdout,后来签约 Axiom 进行查询,然后你的团队又希望使用 Sentry 处理错误、使用 PostHog 进行产品分析。借助 9+ 个 drain 适配器和内置扇出,这些事件会并行发送到所有地方,而应用代码无需改动。通过文件系统NuxtHub适配器,也可以直接切换为自行托管。

这些都不是“v2 功能”。从第 1 天起,它们就是同一个包、同一个 log API 的一部分。

目录会随着你一起成长

最小且有用的目录只需要两项:

src/errors.ts
import { defineErrorCatalog } from 'evlog'

export const errors = defineErrorCatalog('billing', {
  PAYMENT_DECLINED: { status: 402, message: '支付被拒绝' },
  INVOICE_NOT_FOUND: { status: 404, message: '未找到发票' },
})

六个月后,它会有三十项条目,类型增强会让你在任何地方对 createError({ code }) 拥有自动补全,而你会把它作为一个私有 npm 包发布到整个 monorepo。同一种模式,无需重写。

审计目录(defineAuditCatalog)也是如此:从一个动作开始,逐步成长为你的合规地图。参见目录

为 AI 编码代理时代而构建

越来越多的应用是使用 AI 编码代理构建的:Cursor、Codex、Claude Code、Copilot。它们擅长编写处理器,却不太擅长调试处理器。它们需要的是上下文。

  • 带有 why / fix / link 的结构化错误。 模糊的 Error: failed 让人无法理解;createError({ message, why, fix, link }) 则是代理可以读取、总结或呈现给用户的内容,而无需你连接一个翻译层。
  • 作为每个请求单一事实来源的宽事件。 代理可以端到端推理一个类型化事件,而不是在日志行之间进行 grep。
  • 作为类似枚举接口的类型化目录。 代理不会自行发明错误代码,而是从 errors.PAYMENT_DECLINEDaudit.INVOICE_REFUND 等选项中选择,并通过目录获得自动补全。
  • 每次 LLM 调用都具备 AI SDK 遥测。 当代理自己的模型调用失败、产生幻觉或消耗预算时,宽事件会告诉你使用了哪个提示词、哪些工具、多少令牌以及花费了多少成本。
  • 内置代理技能。 evlog 提供了代理技能,因此 Cursor / Claude 已经知道如何接入,无需手动进行提示词工程。
如果你团队的效率来自 AI 编码代理,那么日志的质量 就是 它们上下文窗口的质量。第 0 天就是你设定这个上限的时候。

审计与合规:现在便宜,以后昂贵

每个产品最终都会遇到以下之一:GDPR 数据导出请求、SOC 2 准备、医疗健康领域的 HIPAA、支付领域的 PCI,或者仅仅是一场事故复盘,有人会问 “是谁删了那个?”。获取一条审计轨迹有两种方式:

  1. 第 1000 天的方式。 搭建一个并行系统。确定一个架构。尽可能回填数据。从请求头中反向推断操作者身份。在截止期限的压力下发布。希望没有遗漏任何内容。
  2. 第 0 天的方式。 添加 auditEnricher(),并在任何会修改状态的处理器中调用 log.audit({ action, actor, target })。在你原本就会发出的宽事件之上,evlog 提供哈希链完整性、保留策略,以及超越采样机制的强制保留。

evlog 的审计层不是一个并行系统。它就是你已经在使用的同一个 log,只是带有一个保留的 audit 字段。参见审计日志

受监管行业(金融科技、健康科技、B2B SaaS)中的团队应将第 0 天的审计日志视为基本要求,而不是 v2 功能。

从第一天起就与 drain 无关

你的应用代码从不依赖某个供应商。它只向 drain 管道发出事件。第 0 天,这在开发环境中是 stdout,在 CI 中则是文件系统 drain。当你决定查询日志的那一天:

nuxt.config.ts
import { createAxiomDrain } from 'evlog/axiom'
import { createSentryDrain } from 'evlog/sentry'

export default defineNuxtConfig({
  evlog: {
    drain: {
      adapters: [
        createAxiomDrain({ token: '...', dataset: 'app' }),
        createSentryDrain({ dsn: '...' }),
      ],
    },
  },
})

无需修改任何处理器。同样的事件会通过扇出进入 AxiomDatadogPostHogSentryBetter StackHyperDXOTLP,也可以进入它们全部。

“以后”到底要付出什么成本

当团队先用 console.log 发版,然后打算“等需要的时候”再补上正规日志时,账单就会找上门:

  • 事后统一字段命名约定。userIduser_iduid,还是 actor.id?一旦有三十个服务都用不同方式记录,你就只能在观测平台里写迁移脚本了。
  • 事故后再补脱敏。 当还没有日志时,自动脱敏很简单;但如果六个月的日志里已经包含了 PII,那就是一次 P1 级审计事件。
  • 在压力下选择日志落地端。 当你已经着火了,再去比较 Datadog、Axiom、Sentry,根本不是评估供应商的最佳时机。
  • 事后补上可操作的错误上下文。 你写过的每一个 throw new Error('failed'),都少了一个 why、少了一个 fix、少了一个你的值班工程师(或者 AI 代理)可以使用的链接。

evlog 完全消除了“以后”:从第 1 天起,结构化接口、宽事件生命周期和 drain 管道就全部可用。

决策日采用 evlog 的成本
第 0 天(新项目)添加框架模块。完成。
第 30 天(小型应用)切换日志接口——大约一天工作量。
第 365 天(生产应用)通读代码库替换日志器,统一字段命名约定,补入审计与脱敏。成本与在任意两个结构化日志器之间迁移相同。

这种不对称正是关键。一开始就用 evlog,因为成本为零。继续用 evlog,因为离开它的成本比自己把这些能力都搭出来还高。

第 0 天,实践中

从第一次提交开始就接入 evlog,开启一个新项目

下一步