入门

为什么从 evlog 开始

添加结构化日志的最便宜时机是在第一条请求到来之前。Day 0 就选择 evlog,你的应用就能继承结构化表面、类型化目录、审计轨迹、AI SDK 遥测,以及一个你以后无需自己搭建的 drain 管道。

添加结构化日志的最便宜时机是在第一条请求之前。等你已经有 200 条路由、40 个后台任务,以及每个文件里一个 console.log 时,你其实是在为一个你从未做出的决定支付利息。evlog 专为 day-zero 选择而设计——一次选定,系统其余部分就能继承结构化日志、结构化错误、类型化目录、AI SDK 遥测、审计轨迹,以及一个你以后无需自己搭建的 drain 管道。

已经在用 console.log 或 pino 上线了?evlog 仍然更胜一筹,但适用场景不同——请参见 evlog vs pino、winston、consola

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

evlog 不是一个要你再包一层的小型基础原语。它是一个单一依赖,开箱即带有结构化表面、丰富的集成生态,以及一个带有明确意见的 drain 管道——全部默认启用。

日志基础原语

自动脱敏

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

结构化错误

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

宽事件

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

类型化目录

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

头采样 + 尾采样

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

Drain 管道

内置批处理、指数退避重试、向多个目标分发。无需你自己编写传输胶水层。

不止是 logger

这些基础原语只是基本门槛——每个现代 logger 都有某种形式的它们。evlog 在 day 1 能站稳脚跟的原因,是围绕它们编排好的一切,专门解决你目前还没遇到的问题。

想象你给应用接入了 AI。 迟早你会把 Vercel AI SDK 接到某个路由上。token 成本会出乎意料,某个模型在流式传输中卡住,一个工具返回了垃圾结果。借助 AI SDK 集成,每次模型调用都会自动变成一个包含 prompt、tools、tokens、延迟和成本的宽事件。若你还在使用 Better Auth,evlog 还能帮你把 actor 身份关联到这些事件上,这样你就能回答“到底是谁在一次对话里烧掉了 14 美元?”而无需写一行胶水代码。

想象你的技术栈不止一个框架。 Nuxt 前端、Hono 内部服务、AWS Lambda webhook——大多数团队最后都会变成这种混搭。evlog 提供 13+ 框架集成,而且每一种都暴露相同的日志基础原语(useLoggerlog.setcreateError)。处理器本身仍然保持框架风格——那部分得靠你——但你不会在每次跨运行时环境时都重新学习一套 logger,而且它们背后的 drain 管道始终一致。

想象你希望开发环境的日志比生产环境更吵。 在本地开发时,你会到处加上 log.debug 调用——完整请求体、每次重试、每个守卫——以便真正看到发生了什么。那些内容都不应该被部署出去。Vite 插件 会在构建时剥离选定的日志级别,因此开发环境保留你需要的详细上下文,而生产环境保持干净。额外福利是,每个保留下来的调用都会自动注入源位置(file.ts:42),所以当某个事件进入你的仪表盘时,你能准确知道是哪个位置发出的。

想象用户从浏览器里报了一个 bug。 错误发生在他们的会话中,深藏在一次你无法复现的 fetch 里。evlog 的 浏览器 logger 会把客户端事件发送到你的服务器,然后它们会与请求的其他部分合并成同一个宽事件——一个类型化事件,无论错误最初发生在哪里。再结合 内置 enrichers,你还会免费获得 UA、GeoIP 和 W3C trace 上下文附加信息。

想象你的技术栈更换了供应商。 你最初用 stdout,后来为了查询签了 Axiom,再后来团队又想要 Sentry 做错误监控、PostHog 做产品分析。借助 9+ drain 适配器 和内置的 fan-out,这些事件会并行送达各处——应用代码完全不用改。通过 filesystemNuxtHub 适配器,迁移到自托管同样是直接替换即可。

这些都不是“v2 功能”——它们就是同一个包、同一个 log API、在 day 1 就具备的能力。

目录会随着你一起成长

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

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

export const errors = defineErrorCatalog('billing', {
  PAYMENT_DECLINED: { status: 402, message: 'Payment declined' },
  INVOICE_NOT_FOUND: { status: 404, message: 'Invoice not found' },
})

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

审计目录(defineAuditCatalog)也是同样如此——从一个动作开始,逐步扩展成你的合规地图。参见 目录

为 AI 编码代理时代而构建

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

  • 带有 why / fix / link 的结构化错误。 一个含糊的 Error: failed 是不透明的;而 createError({ message, why, fix, link }) 则是代理可以读取、总结,或直接展示给用户的内容,而无需你搭建翻译层。
  • 将宽事件作为每次请求的单一事实来源。 让代理能够端到端推理的一个类型化事件——而不是要跨多个日志行去 grep。
  • 把类型化目录当作类似枚举的表面。 代理不会自己发明错误码;它会从 errors.PAYMENT_DECLINEDaudit.INVOICE_REFUND 等中选择,并借助目录自动补全。
  • 每次 LLM 调用都带 AI SDK 遥测。 当代理自己的模型调用失败、幻觉或烧预算时,宽事件会告诉你是哪一个 prompt、哪些 tools、多少 token、花了多少钱。
  • 内置 agent skills。 evlog 自带 agent skills,因此 Cursor / Claude / Windsurf 已经知道该怎么接入——无需手写提示工程。
如果你团队的效率来自 AI 编码代理,那么日志的质量 就是 它们上下文窗口的质量。day 0 就是你设定这个上限的时候。

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

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

  1. day-1000 方式。 搭建一个并行系统。决定 schema。能回填多少就回填多少。从请求头里反向推导 actor 身份。在截止日期压力下上线。祈祷没有遗漏任何内容。
  2. day-0 方式。 添加 auditEnricher(),并在任何触碰状态的处理器中调用 log.audit({ action, actor, target })。evlog 提供 hash-chain 完整性、保留策略,以及在采样之后强制保留——这一切都建立在你原本就已经在发出的宽事件之上。

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

受监管行业中的团队——金融科技、医疗科技、B2B SaaS——应把 day-0 审计日志视为基本门槛,而不是 v2 功能。

从第一天起就与 drain 无关

你的应用代码永远不依赖某个特定供应商——它只会向 drain 管道发出事件。day 0 时,在开发环境里它是 stdout,在 CI 中则是 filesystem drain。到了你决定查询日志的那天:

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

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

处理器零改动。同样的事件会进入 AxiomDatadogPostHogSentryBetter StackHyperDXOTLP——或者通过 fan-out 同时送达所有目标。

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

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

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

evlog 直接消除了“以后”这一步——结构化表面、完整的事件生命周期,以及落地管道,从第 1 天就已经具备。

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

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

第 0 天,实践中

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

下一步