evlog map 会读取你磁盘上的项目,并回答一个问题:如果今晚生产环境出了问题,这个应用的哪些部分能告诉你原因?
它会找到每个入口点——API 处理器、发起数据获取的页面、中间件、定时任务、服务端动作——检查它们是否具备宽事件覆盖,给出评分,并列出最值得优先修复的三个。不会运行任何东西,不会注入任何监控,也不需要流量:它只是对你的源码做静态分析。
evlog map
提升我的 evlog map 评分
报告
这是一次针对 evlog playground 的真实运行,这是一个拥有 29 个入口点的 Nuxt 应用:
▀▀█ █▀▀ 分数 /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:这是每个入口点的加权平均值,其中金钱和认证路由的权重加倍,页面的权重减半。下面的词是它的等级——excellent、good、needs work 或 at risk。
右侧的一排方块代表项目中的每个入口点,从左到右依次为最差到最好。右侧高方块成墙、左侧只有几个矮方块,说明应用整体健康,只存在少数盲点。一整排很低的方块说明应用根本没有任何埋点。在大型项目中,这一排会按终端宽度采样显示,每个桶里显示最差的入口点。
覆盖范围
分数的来源,会按照你理解应用的方式进行分组,而不是按规则分组。只有当项目中存在相应内容时,这些区域才会出现:一个没有 pages/ 的 Nuxt 应用不会显示 Pages 行。
| 区域 | 覆盖内容 |
|---|---|
| API 处理器 | 服务端路由处理器 |
| 页面 | 在服务端获取数据的页面 |
| 中间件与任务 | 中间件、定时任务、服务端动作 |
| 金钱与认证 | 敏感性分类器标记到的每个入口点,无论位于何处 |
金钱与认证这一行会刻意与其他行重叠。它是缺失事件代价最高的分组,因此它单独成行,并且当其得分低于整个应用时会带有 ⚠ 标记。
优先修复
最有收益的三个入口点,先敏感项,再按最差分数排序。每个条目都会给出方法、路径、敏感性标记、说明实际问题的一句话,以及文件、行号和失败规则的文档链接。
$ 表示金钱,A 表示认证,@ 表示 PII。这些来自 敏感性分类器,被标记的入口点会额外满足一项要求:审计轨迹。
然后
其余存在缺口的内容,按修复方式批量列出,而不是每个入口点单独占一行。为 /api/test/browser-ingest 添加 useLogger + log.set +4 表示有 5 个入口点需要同样的两行代码,因此你可以一次性处理它们。
进一步提升
只有在项目已经使用了某个 evlog 功能、但某些入口点还没有受益时,才会出现的独立部分:
进一步提升
你已经在用这些——你的应用还能从中获得更多收益
+ 这些重复错误应该变成目录条目吗?——2 个入口点
server/api/orders/[id].get.ts:4 · evlog.dev/learn/catalogs
建议不会改变分数。
这些是建议,不是缺口。它们以项目中某处已经使用该功能作为前提,绝不会被计为失败,而且 --min-score 门槛也不可能因为它们而失败。请参见 机会。
最后两行
✓ 已经很稳 会列出已经没有任何可修复内容的入口点,因此一个优秀的应用也会被明确告知。▲ 76 → 86 通过修复上面的 3 项 是如果你正好修复 优先修复 下的内容,分数会达到的结果——这也是为什么应该先从这里开始,而不是别处。
三种视图
默认报告回答“我做得怎么样”。另外两个旗标回答你还会有的另外两个问题。
每个入口点
--all 会打印检查矩阵:每个入口点一行,按最差情况优先,并按目录分组。
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
每一列对应一条规则,顺序就是它们消耗分数的顺序。点表示该规则在这里不适用——一个什么都不抛出的处理器,不会被问它的错误是否携带 why 和 fix,这是一枚点,而不是一次放过。空心 ○ 表示你通过注释禁用了某项检查。每一列都在 规则 中有说明。
单个入口点
传入路由或文件路径,即可获得单个入口点的完整说明:
evlog map server/api/auth/login.post.ts
# 或按路由
evlog map /api/auth/login
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/** → 定时任务
routes/** → API 处理程序,路径按原样写入
api/** → API 处理程序,路径以前缀 /api 开头
middleware/** → 中间件
app/**/route.ts → 每个导出的 HTTP 方法对应一个 API 处理程序(GET、POST,…)
app/**/page.tsx → 页面
middleware.ts → 中间件
"use server" file → 每个导出的函数对应一个服务器操作
src/routes/** → 当路由声明了 server handlers 或 createServerFn 时为 API 处理程序,
否则为页面。会跳过 __root 文件。
Next.js 同时支持 app/ 和 src/app/,以及 src/middleware.ts 与 middleware.ts。没有导出任何 HTTP 方法的 route.ts 会被列出一次,方法为 ANY。
类型在报告中显示为 ANY、PAGE、MID、ACT(服务器操作)、CRON 和 WS,或者在入口点具有 HTTP 方法时显示为该 HTTP 方法。
n/a,而不是失败。当裁决有误时
你不同意的检查应该只让你多写一条评论,而不该卡住你的 CI 门禁:
// evlog-map-disable-next-line wide-event, context -- 存活探针,特意静默
export default defineEventHandler(() => ({ ok: true }))
该检查会变成附带你理由的 n/a,因此它不再计入评分——而且报告会显示项目已禁用多少个检查,所以高分绝不会掩盖一个什么都不记录的应用。完整语法见 规则。
标志
| 标志 | 默认值 | 作用 |
|---|---|---|
<entry> | — | 按路由路径或文件路径检查一个入口点 |
--all | off | 将每个入口点作为一个检查矩阵 |
--min-score <n> | off | 当全局分数低于 n 时退出 1 |
--baseline [ref] | off | 针对已提交的映射(路径,或 git:<ref>)发生回归时退出 1 |
--framework <name> | detected | 强制使用 nuxt、nitro、next 或 tanstack-start |
--no-write | writes | 跳过写入 evlog.map.json |
--verbose | off | 显示每个文件的解析警告 |
--cwd <dir> | current | 扫描另一个目录 |
--json | off | 以 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>总会显示原因,所以错误的调用是可见的,而不会悄然发生。