CLI

evlog init

将 evlog 接入现有应用。在交互式设置中安装软件包、注册集成,并接入你选择的目标。

evlog map 会告诉你哪些入口点是黑暗的。evlog init 就是在此之前运行的命令。它会在你当前所在的项目中,执行框架指南所描述的设置。

Terminal
npx @evlog/cli init

在终端中,它会读取你的项目,询问那些它无法推断的信息,并在采取任何操作前向你展示计划:

Interactive
┌  evlog init  checkout
│
◇  检测到 Nuxt
│
◇  每个广域事件上的服务名称
│  checkout
│
◇  在开发环境中,事件应该发送到哪里?
│  本地文件
│
◇  生产环境中呢?
│  Axiom, Sentry
│
◇  还有别的吗?
│   Context
│    ◻ Request enrichers
│   Delivery
│    ◻ Batching and retry
│    ◻ Sampling
│   Catalogs
│    ◼ Error catalog · 发现 3 个重复错误
│    ◻ Audit actions · 2 条敏感路由没有审计轨迹
│
◇  计划 ───────────────────────────────────╮
│                                          │
│  run  pnpm add evlog                     │
│  update  nuxt.config.ts                  │
│  create  server/plugins/evlog-drain.ts   │
│  create  server/plugins/evlog-enrich.ts  │
│                                          │
├──────────────────────────────────────────╯
│
◇  应用?
│  是
│
◇  4 ok · 1 warn · 0 fail
│
◇  在接收到任何内容之前先设置这些 ─────────────╮
│                                                     │
│  AXIOM_DATASET    要写入的数据集                  │
│  AXIOM_API_KEY    具有 ingest 权限的 API token   │
│                                                     │
├─────────────────────────────────────────────────────╯
│
│  verify  evlog doctor
│  score   evlog map
│
└  Nuxt 已接入 · evlog.dev/integrate/frameworks/nuxt

生产目标选择器是模糊搜索(输入 ax 可查找 Axiom),并且可以选择多个目标:同一个事件会分发到每个目标。附加项会分组显示,并且只列出你的项目实际可以使用的选项,同时说明使它们出现的依据。

它的作用

  1. 读取你的项目,使用与 evlog map 相同的分析:框架、已安装的软件包、重复出现的错误、没有审计轨迹的入口点。这些就是生成选项的依据。
  2. 安装 evlog,使用锁文件所表明的软件包管理器,除非它已经存在。--no-install 会打印命令而不是运行它。
  3. 注册集成:Nuxt 模块、Nitro 模块,或 Next.js instrumentation 文件。
  4. 接入你的目标,在同一位置区分开发和生产环境;如果你选择了批处理、enrichers 和采样,也会一并接入。
  5. 教会你的 AI agents,如果你允许它这样做。将 evlog 约定作为代码块写入 AGENTS.md,创建一个指向它的 CLAUDE.md,并通过 npx skills add 添加 agent skills。这与 evlog agents 执行的操作相同;这些写入会与接入操作一起规划,因此你只需确认一次。已经安装的 Skills 会留给 npx skills update 处理。--no-agents 会跳过此步骤。
  6. 运行 evlog doctor,然后再结束,这样运行结果会直接回答“是否成功”,而不是告诉你自行检查。

它写入的每一项都会逐行报告,包括那些已经就绪的部分。

它只提供能够支持的内容

不适用的选项不会显示,显示出来的选项会说明原因:

提供项出现条件
错误目录扫描发现同一个 createError 出现在多个文件中
审计操作map 标记了敏感入口点,但没有 log.audit()
AI SDK 日志记录ai 是一个依赖项
认证身份better-auth 是一个依赖项
批处理与重试选择了生产目标——否则没有任何内容可批处理
Vite 插件该框架基于 Vite

将这些中的某一项作为标志传给一个无法使用它的项目时,会将其丢弃并给出说明,而不是连接任何无效内容。

非交互式

只要有任何迹象表明没有人在关注,提示就会被跳过,每个答案都来自标志和默认值:

条件原因
--yes这是你说的
--json负载就是契约;前面再套一层半吊子的 TUI 帮不上忙
stdin 或 stdout 不是 TTY被管道传递、重定向,或由工具启动
CI 被设置为除 false / 0 之外的任何值工作流运行器没有键盘
Terminal
evlog init --yes --prod-drain axiom,sentry --extras pipeline,enrichers
evlog init --yes --extras sampling --sampling high
evlog init --json --drain none          # 将计划和结果以 JSON 输出,无需阅读
也为 agents 编写。 非交互式路径可以表达提示所能表达的每个选择,因此 agent 可以准确复现人刚刚执行的操作,并且永远不会因为等待一个不会到来的按键而卡住。未知的 --drain--extras 值会终止运行,而不是回退到默认值,因为悄悄接入错误的目标比命令失败更糟糕。

它不会覆盖你的代码

init 只会追加;它绝不会重写。具体来说:

  • 配置文件会在它要添加到的节点的精确偏移位置进行修改。你的注释、引号样式和格式都会保留。不会从 AST 重新打印任何内容。
  • 已经存在的文件会保持不变,并报告为已经存在。没有 --force
  • 任何无法安全执行的操作都会变成一个手动步骤,其中包含要粘贴的代码片段以及它停止的原因。由变量构建的 modules 键是常见情况:CLI 无法看见数组中的内容,因此不存在将字符串插入其中的正确位置。

这使得它可以安全地运行两次。第二次运行会报告哪些内容已经接入,并且不会写入任何东西:

Output
· nuxt.config.ts already registers evlog/nuxt
· nuxt.config.ts already has an evlog block
· server/plugins/evlog-drain.ts already exists

如果你更愿意先查看计划再对任何内容进行修改,请先使用 --dry-run

按框架

Nuxt

'evlog/nuxt' 添加到 modules,并添加包含服务名称的 evlog 配置块。useLoggerparseError 会从那里自动导入,createError 则从 evlog 导入。

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/ui', 'evlog/nuxt'],
  evlog: {
    env: { service: 'checkout' },
  },
})

Nitro

添加 import 和模块调用,并选择与主版本匹配的子路径:Nitro 3 使用 evlog/nitro/v3nitropack 使用 evlog/nitro。如果没有 nitro.config.ts,则创建它。

nitro.config.ts
import { defineConfig } from 'nitro'
import evlog from 'evlog/nitro/v3'

export default defineConfig({
  modules: [
    evlog({
      env: { service: 'api' },
    }),
  ],
})

Next.js

创建 instrumentation.tslib/evlog.ts;如果你的应用位于 src/ 下,则会在该目录下创建,因为 Next 只会加载与之匹配的文件。

包装处理器仍然由你负责:Next 没有环境级的请求日志器,因此每个路由都通过 withEvlog() 显式启用。init 会打印代码片段。

lib/evlog.ts
import { createEvlog } from 'evlog/next'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'web',
})

TanStack Start

写入启用 experimental.asyncContext 的 Nitro v3 配置。如果没有它,useRequest() 会返回空值,你得到的会是一个看起来完整、却不记录任何业务上下文的安装结果。

根路由上的 evlogErrorHandler 中间件是一个手动步骤:__root.tsx 是组件文件,直接往里插入内容只能靠猜。

Hono

创建 src/evlog.ts(如果没有 src/,则创建 evlog.ts),其中包含 initLogger 和配置好的中间件导出,因为 Hono 没有可供追加的配置文件。drains 和 enrichers 会写入中间件选项,采样则写入 initLogger

注册由你负责:你的应用入口属于应用代码,因此 init 会打印出需要粘贴的两行代码。

src/evlog.ts
import { initLogger } from 'evlog'
import { evlog } from 'evlog/hono'

initLogger({
  env: { service: 'api' },
})

/** Register once, before your routes: `app.use(evlogMiddleware)`. */
export const evlogMiddleware = evlog({
  // drains and enrichers, from your answers
})

目标

这是两个问题,而不是一张列表:没人会把本地开发流量发送到 Axiom,也没人会从服务器的文件系统中读取生产日志。--drain 设置开发环境的 drain(fsnone);--prod-drain 接受一个或多个托管目标。

Id目标所需内容
fs.evlog/logs 下的本地文件(开发默认)
axiomAxiomAXIOM_DATASET, AXIOM_API_KEY
otlp任何 OpenTelemetry 收集器OTEL_EXPORTER_OTLP_ENDPOINT
posthogPostHogPOSTHOG_API_KEY
sentrySentrySENTRY_DSN
better-stackBetter StackBETTER_STACK_API_KEY
datadogDatadogDATADOG_API_KEY, DATADOG_SITE
hyperdxHyperDXHYPERDX_API_KEY
none仅输出漂亮的控制台内容

选择多个生产目标会把同一事件分发到每一个目标。生成的插件只在一个地方基于环境分支,因此“哪个 drain 在哪里运行”从来不需要事后推断:

server/plugins/evlog-drain.ts
const drains = import.meta.dev
  ? [createFsDrain()]
  : [pipeline(createAxiomDrain()), pipeline(createSentryDrain())]

批处理只包装网络发送,因为缓冲本地文件写入会给你希望事件立即显示在屏幕上的那个循环增加延迟。

init 从不索要密钥。 它会打印出适配器会读取哪些环境变量,然后把它们留给你自己处理。一个会提示你输入 API token 的设置命令,实际上是在把凭据写入它自己选择的文件,而这个答案在途中还会进入你的 shell 历史记录。 ::。

附加项

Id你将获得什么
enrichers从用户代理、地理位置、请求大小、追踪上下文中选择(--enrichers
pipeline批处理和重试,而不是每个请求一次 HTTP 调用
sampling一个速率配置文件,见下文(--sampling
error-catalog一个带类型的目录,预先填充你已经反复出现的错误
audit-catalog为没有审计轨迹的敏感路由提供带类型的审计动作
aiAI SDK 生成中的令牌使用量、工具调用和成本
better-auth每个事件中的已登录用户
vite从生产构建中剥离 log.debug() 的 Vite 插件
不适用的附加项会被丢弃并报告,而不是直接拒绝,因此在由不同应用组成的单仓库中,--extras vite,enrichers 也会正确执行。

采样预设

根据应用承受的流量命名。移动的是 Info:它占据大部分流量,也占据大部分账单;Warnings 只在顶部让步,即使将其减半,整体形状仍然保留。
IdInfoWarnings适用于
all全部全部在流量或成本说不之前,这是正确答案
low50%100%一个开始不断重复自身的小型应用
medium25%100%稳定流量,账单值得关注
high10%100%Info 是你付费的主要部分
very-high1%50%看趋势,而不是单个请求
Errors 在每个层级中都保持 100%。 会丢弃 errors 的采样配置会隐藏那些凌晨三点唯一有人会读取的事件。生成的配置会明确写出这一点,因此这条不变量会出现在原本可能有人疑惑的地方。debug 从不被采样,也不会出现在配置中。 未指定的级别会被完整保留,而 debug 事件之所以存在,只是因为有人为了追查某些问题而把它们打开了:对你为排查问题而开启的日志做 5% 采样,意味着只有 5% 的概率看到你真正需要的那一行。生产构建反正也会移除 log.debug(),所以那里通常没有多少量值得采样。
nuxt.config.ts
evlog: {
  env: { service: 'shop' },
  sampling: {
    rates: { info: 10, warn: 100, error: 100 },
  },
}

目录是预先填充的,不是脚手架生成的

error-catalog 不会写出一个把名称填进去的模板。它会写出你代码里已经反复出现的错误,以及你已经写好的说明文字:
server/utils/errors.ts
export const shopErrors = defineErrorCatalog('shop', {
  /** 目前内联写在 server/api/checkout.post.ts、server/api/refund.post.ts 中 */
  CARD_DECLINED: {
    status: 402,
    message: '银行卡被拒绝',
    why: '发卡机构拒绝了它',
    fix: '待办:他们应该如何处理它',
  },
})
audit-catalog 的工作方式相同,为每个被标记为敏感且未跟踪的入口点 map 命名一个动作。两者都会以待办清单的形式落地,并且类型已经写好,因此迁移更像是查找和替换,而不是设计工作。

单仓库

从工作区根目录运行时,init 会设置各个应用,而不是设置不提供流量的根软件包。它会列出能够检测到框架的工作区软件包。共享的 utils 软件包没有可插桩的入口点,并会询问要接入哪些内容:
Terminal
evlog init                            # 从列表中选择
evlog init --yes --apps apps/web,apps/api
每个应用都会保留自己的服务名称,该名称取自其 package.json

本地 drain

对于基于 Nitro 的应用,init 会写入一个 drain 插件;对于 Next.js,它会将 drain 添加到 lib/evlog.ts。文件系统 drain,且只有它,会限制为开发环境
server/plugins/evlog-drain.ts
import { createFsDrain } from 'evlog/fs'

const drain = createFsDrain()

export default defineNitroPlugin((nitroApp) => {
  // 本地文件是开发时的便利——绝不能作为生产环境的 sink。
  if (!import.meta.dev) return
  nitroApp.hooks.hook('evlog:drain', drain)
})
写入文件的 drain 会把文件写到处理请求的那台机器上。未经请求就在生产环境中启用它,并不是一个设置命令应该替你做出的决定。每一种托管目标在生成时都不会带这个保护,因为这正是你选择它的原因。它写入的目录是 .evlog/logs,而且 evlog 会在首次写入时把它加入 gitignore。evlog doctor 也会查找这个目录。

标志

标志作用
--yes, -y跳过所有问题并采用默认值
--framework <name>覆盖检测结果(nuxtnitronexttanstack-starthono
--service <name>每个广域事件上的服务名称(默认:你的软件包名称,不含作用域)
--drain <id>开发环境 drain:fs(默认)或 none
--prod-drain <a,b>生产环境目标——见上表
--extras <a,b>逗号分隔,见“附加项”
--enrichers <a,b>user-agentgeorequest-sizetrace-context(默认:全部)
--sampling <id>alllowmedium(默认)、highvery-high
--apps <a,b>要设置的工作区软件包(仅限单仓库根目录)
--dry-run打印计划,不写入任何内容
--no-install不运行软件包管理器,而是打印命令
--no-agents全部跳过——不写入 AGENTS.md 代码块、不创建 CLAUDE.md、不添加 agent skills
--cwd <dir>在工作区中设置其他应用
--json将计划和结果以 JSON 输出到 stdout(隐含非交互式)

下一步

  • evlog agents:单独重新运行 agent 指南,无论何时 Skills 有所更新
  • evlog doctor:确认接入配置能够解析,并且日志正在写入
  • evlog map:既然 evlog 已经就位,就评估仍然黑暗的部分
  • 快速开始:接入背后的概念