CLI

evlog map

规则评分
为你的应用提供一个静态可观测性评分,风格类似 Lighthouse。扫描 Nuxt、Nitro、Next.js 或 TanStack Start 项目中的每个入口点,评估宽事件覆盖率,并指出应优先修复的入口点。

evlog map 会读取你磁盘上的项目,并回答一个问题:如果今晚生产环境出了问题,这个应用的哪些部分能告诉你原因?

它会找到每个入口点——API 处理器、发起数据获取的页面、中间件、定时任务、服务端动作——检查它们是否具备宽事件覆盖,给出评分,并列出最值得优先修复的三个。不会运行任何东西,不会注入任何监控,也不需要流量:它只是对你的源码做静态分析。

evlog map·idle
scan
0/147
1api/auth/[...all].tsA
······
2…ment-declined.get.ts$
······
…owser-ingest.post.ts
······
3…i/auth/login.post.tsA
······
…st/wide-event.get.ts
······
··/100good
5 of 29 shown ▲ 76 → 86 by fixing the 3 above
Terminal
evlog map
早期阶段。 基础已经很稳固——基于 AST、经过测试、带版本化 JSON 合约——但规则集还很年轻,而且目前只有四个框架有适配器。某个版本如果优化了一条规则,可能会改变你未曾修改过的代码的判定,所以如果你要用分数来卡住拉取请求,请锁定 CLI 版本

提升我的 evlog map 评分

报告

这是一次针对 evlog playground 的真实运行,这是一个拥有 29 个入口点的 Nuxt 应用:

evlog map
▀▀█ █▀▀   分数 /100            evlog-playground · Nuxt
  █ █▀█   ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱    已扫描 29 个入口点
  ▀ ▀▀▀   良好                  ▂▂▂▃▃▃▃▃▄▆▆▆▆▆███████████████

覆盖范围
● API 处理器       ▰▰▰▰▰▰▰▰▱▱   79  28 个中有 13 个存在缺口
● 中间件与任务     ▰▰▰▰▰▱▱▱▱▱   45  运行时没有任何日志记录
● 金钱与认证       ▰▰▰▰▰▰▰▱▱▱   71  缺少审计轨迹

优先修复
1. ANY    /api/auth/:all* A — 涉及认证但没有任何日志
   server/api/auth/[...all].ts:1 · evlog.dev/learn/wide-events
2. GET    /api/test/catalog/payment-declined $ — 涉及资金流转但没有审计轨迹
   server/api/test/catalog/payment-declined.get.ts:11 · evlog.dev/use-cases/audit/overview
3. POST   /api/auth/login A — 缺少 log.audit
   server/api/auth/login.post.ts:1 · evlog.dev/use-cases/audit/overview

然后
· 为 /api/test/browser-ingest 添加 useLogger + log.set +4
· 为 /api/payment/process、/api/test/better-auth/whoami 添加 log.audit
· 为 /api/audit/deny 添加 log.set
· 为 /api/audit/with-audit 添加 log.set + createError({ why, fix })
· 为 /api/test/h3-error 添加 useLogger + log.set + createError({ why, fix })
· 为 /api/test/tail-sampling/error 添加 createError({ why, fix })

✓ 已经很稳:/api/audit/catalog/invoice-refund +14
▲ 76 → 86 通过修复上面的 3 项

────────────────────────────
evlog.map.json 已更新 · 这个分数如何计算 → evlog.dev/cli/scoring
▸ evlog map --all 每个入口点 · evlog map <file> 检查单个入口点
  --min-score 80 CI 门槛

从上到下阅读,每个区块都会回答不同的问题。

分数

最醒目的数字是全局分数,满分 100:这是每个入口点的加权平均值,其中金钱和认证路由的权重加倍,页面的权重减半。下面的词是它的等级——excellentgoodneeds workat risk

右侧的一排方块代表项目中的每个入口点,从左到右依次为最差到最好。右侧高方块成墙、左侧只有几个矮方块,说明应用整体健康,只存在少数盲点。一整排很低的方块说明应用根本没有任何埋点。在大型项目中,这一排会按终端宽度采样显示,每个桶里显示最差的入口点。

完整计算方式——每条规则的权重、路由权重和等级阈值——请见 评分

覆盖范围

分数的来源,会按照你理解应用的方式进行分组,而不是按规则分组。只有当项目中存在相应内容时,这些区域才会出现:一个没有 pages/ 的 Nuxt 应用不会显示 Pages 行。

区域覆盖内容
API 处理器服务端路由处理器
页面在服务端获取数据的页面
中间件与任务中间件、定时任务、服务端动作
金钱与认证敏感性分类器标记到的每个入口点,无论位于何处

金钱与认证这一行会刻意与其他行重叠。它是缺失事件代价最高的分组,因此它单独成行,并且当其得分低于整个应用时会带有 标记。

优先修复

最有收益的三个入口点,先敏感项,再按最差分数排序。每个条目都会给出方法、路径、敏感性标记、说明实际问题的一句话,以及文件、行号和失败规则的文档链接。

$ 表示金钱,A 表示认证,@ 表示 PII。这些来自 敏感性分类器,被标记的入口点会额外满足一项要求:审计轨迹。

然后

其余存在缺口的内容,按修复方式批量列出,而不是每个入口点单独占一行。为 /api/test/browser-ingest 添加 useLogger + log.set +4 表示有 5 个入口点需要同样的两行代码,因此你可以一次性处理它们。

进一步提升

只有在项目已经使用了某个 evlog 功能、但某些入口点还没有受益时,才会出现的独立部分:

Excerpt
进一步提升
你已经在用这些——你的应用还能从中获得更多收益
+ 这些重复错误应该变成目录条目吗?——2 个入口点
   server/api/orders/[id].get.ts:4 · evlog.dev/learn/catalogs
建议不会改变分数。

这些是建议,不是缺口。它们以项目中某处已经使用该功能作为前提,绝不会被计为失败,而且 --min-score 门槛也不可能因为它们而失败。请参见 机会

最后两行

✓ 已经很稳 会列出已经没有任何可修复内容的入口点,因此一个优秀的应用也会被明确告知。▲ 76 → 86 通过修复上面的 3 项 是如果你正好修复 优先修复 下的内容,分数会达到的结果——这也是为什么应该先从这里开始,而不是别处。

三种视图

默认报告回答“我做得怎么样”。另外两个旗标回答你还会有的另外两个问题。

每个入口点

--all 会打印检查矩阵:每个入口点一行,按最差情况优先,并按目录分组。

evlog map --all (trimmed)
evlog-playground · Nuxt · 76/100 · 29 个入口点,最差情况优先

                                            log   ctx   err   audit catch fetch
server/
├─ api/auth/[...all].ts   ▰▰▱▱▱▱▱▱▱▱  20 A  ●     ●     ·     ●     ·     ·
├─ …ment-declined.get.ts  ▰▰▱▱▱▱▱▱▱▱  20 $  ●     ●     ●     ●     ·     ·
├─ …test/h3-error.get.ts  ▰▰▰▱▱▱▱▱▱▱  25    ●     ●     ●     ·     ·     ·
├─ …owser-ingest.post.ts  ▰▰▰▰▰▱▱▱▱▱  45    ●     ●     ·     ·     ·     ·
├─ …t/with-audit.post.ts  ▰▰▰▰▰▰▰▱▱▱  65    ●     ●     ●     ·     ●     ·
├─ …i/auth/login.post.ts  ▰▰▰▰▰▰▰▰▱▱  75 A  ●     ●     ·     ●     ·     ·
├─ …ampling/error.get.ts  ▰▰▰▰▰▰▰▰▱▱  80    ●     ●     ●     ·     ·     ·
└─ …st/wide-event.get.ts  ▰▰▰▰▰▰▰▰▰▰ 100    ●     ●     ·     ·     ·     ·

● 已覆盖   ● 缺口   ·  不适用   $ 金额   A 认证   @ 个人身份信息
每一列检查什么 → evlog.dev/cli/rules

每一列对应一条规则,顺序就是它们消耗分数的顺序。点表示该规则在这里不适用——一个什么都不抛出的处理器,不会被问它的错误是否携带 whyfix,这是一枚点,而不是一次放过。空心 表示你通过注释禁用了某项检查。每一列都在 规则 中有说明。

单个入口点

传入路由或文件路径,即可获得单个入口点的完整说明:

Terminal
evlog map server/api/auth/login.post.ts
# 或按路由
evlog map /api/auth/login
evlog map server/api/auth/login.post.ts
POST   /api/auth/login A   ▰▰▰▰▰▰▰▰▱▱ 75/100
server/api/auth/login.post.ts · Nuxt

扫描此文件的原因
▍ POST /api/auth/login — 服务端处理器

被标记为敏感的原因
▍ auth:路径包含“auth”

检查项
✓ useLogger  每个请求都会发出宽事件
✓ log.set    已通过 log.set() 附加上下文
✗ log.audit  敏感操作但没有审计轨迹  evlog.dev/use-cases/audit/overview

建议结构 — Nuxt
│ export default defineEventHandler(async (event) => {
│   log.audit({
│     action: 'auth.login',
│     actor: { type: 'user', id: user.id },
│   })
│ })

▲ 修复此入口点:75 → 100

有四点值得注意:

  • 扫描此文件的原因 说明 CLI 认为这个文件是什么。如果这不对,那么下面的一切都不对,而这里就是你能看出来的地方。
  • 被标记为敏感的原因 显示了准确原因,因此误报只需一眼就能识别,而不是一个谜团。
  • 建议结构 由实际失败的规则组合而成,采用你所用框架的惯用写法。审计动作是根据路由推断出来的——/api/auth/login 会提示 auth.login,而不是一个占位符。
  • 当所有要求都通过,但仍有建议项时,结果会分成两部分:没有需要修复的内容,以及还可以提升的 n 项。

什么算作入口点

检测按框架进行,依据文件布局。--framework 在检测猜错时会覆盖它。

server/api/**             → API 处理程序,路径以前缀 /api 开头,方法来自文件名后缀
server/routes/**          → API 处理程序,路径按原样写入
app/pages/**/*.vue        → 页面(Nuxt 4 默认;也包括 pages/ 和 src/pages/)
server/middleware/**      → 中间件
server/tasks/**           → 定时任务

Next.js 同时支持 app/src/app/,以及 src/middleware.tsmiddleware.ts。没有导出任何 HTTP 方法的 route.ts 会被列出一次,方法为 ANY

类型在报告中显示为 ANYPAGEMIDACT(服务器操作)、CRONWS,或者在入口点具有 HTTP 方法时显示为该 HTTP 方法。

没有任何可供插桩内容的入口点会被豁免:evlog 自己的客户端日志摄取端点,它们属于基础设施而不是应用代码,以及什么都不抓取的页面。对它们来说,每条规则都会报告 n/a,而不是失败。

当裁决有误时

你不同意的检查应该只让你多写一条评论,而不该卡住你的 CI 门禁:

server/api/health.get.ts
// evlog-map-disable-next-line wide-event, context -- 存活探针,特意静默
export default defineEventHandler(() => ({ ok: true }))

该检查会变成附带你理由的 n/a,因此它不再计入评分——而且报告会显示项目已禁用多少个检查,所以高分绝不会掩盖一个什么都不记录的应用。完整语法见 规则

标志

标志默认值作用
<entry>按路由路径或文件路径检查一个入口点
--alloff将每个入口点作为一个检查矩阵
--min-score <n>off当全局分数低于 n 时退出 1
--baseline [ref]off针对已提交的映射(路径,或 git:<ref>)发生回归时退出 1
--framework <name>detected强制使用 nuxtnitronexttanstack-start
--no-writewrites跳过写入 evlog.map.json
--verboseoff显示每个文件的解析警告
--cwd <dir>current扫描另一个目录
--jsonoff以 JSON 形式在 stdout 输出完整映射

evlog.map.json

每次运行都会将 evlog.map.json 写入项目根目录:包括分数、框架、生成该文件的 CLI 版本和规则集版本,以及每个入口点、其检查项、建议、敏感性和自身分数。这与 --json 输出的数据相同。

如果不希望生成该文件,可使用 --no-write,适用于 CI 运行或快速查看他人的项目。是否提交它取决于你的门禁方式:

  • 不对 CI 设置门禁,或只使用 --min-score:该文件是构建产物,因此请将其加入 gitignore。它包含与 --json 相同的数据,所以不会丢失任何信息。
  • 使用 --baseline 设置门禁:请像跟踪锁文件一样跟踪它。渐进检查会与已提交的副本进行比较,因此 git:<ref> 只能读取已提交的文件。使用 evlog map 重新生成,在差异中检查它,绝不要手动编辑。

它不会告诉你什么

静态分析有其局限性,了解这些局限性,决定了你是信任这个评分,还是被它惹恼。

  • 它读取的是代码,不是流量。 一个入口点即使评分 100,如果它附加的上下文有误,在运行时仍可能发出毫无用处的事件。这个分数只说明结构是对的。
  • 它只追踪一跳的导入。 通过本地模块重新导出的辅助函数会被解析——import { useLogger } from '@/lib/evlog' 会被计入——但一个经过你自己三层抽象传递的 logger 可能不会被识别。
  • 四个框架。 Nuxt、Nitro、Next.js App Router 和 TanStack Start 有适配器。evlog 集成的其他框架目前还不会被扫描。
  • 敏感度是一种启发式。 它会读取导入和路由路径。evlog map <file> 总会显示原因,所以错误的调用是可见的,而不会悄然发生。

下一步

  • 规则 — 每个检查、满足条件以及精确的修复方法
  • 评分 — 权重、等级以及敏感度分类器
  • CI — 根据分数对拉取请求进行门禁控制。