CLI

evlog map

规则评分
扫描受支持的入口点中的 evlog 日志模式,检查静态可观测性评分,并在不运行处理器的情况下确定修复优先级

evlog map 会显示哪些受支持的入口点缺少 evlog 日志模式。它会检查你的源代码中是否存在宽事件、上下文、结构化错误以及其他日志规则

选定的框架适配器会发现它能够识别的入口点,例如 API 处理器、获取数据的页面和中间件。报告会为这些检查评分,并列出三个优先检查项。在源代码、CLI 版本和适配器相同的情况下,检查结果也会相同。

扫描不会执行处理器或检查流量。高分并不能证明日志能够到达你的日志接收端,也不能证明日志包含足够的上下文来诊断故障。请阅读可观测性评分指南,了解受支持的框架以及结果的局限性。

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 map 的任何设计都不假设项目是全新的。在受支持的框架中,它读取的是源代码,而不是流量,因此一个三年前发布的应用与脚手架项目的评分方式相同:识别入口点、识别模式、执行相同的静态检查。你会在下一次事故发生之前得到问题列表,而不是在事故发生期间才得到它。不受支持的 Node.js 应用,以及适配器无法识别的包装器,不属于该评分范围。请参见可观测性评分

无论代码库存在多久,工作都分为相同的四个步骤:

  1. 运行 npx @evlog/cli map。无需安装、无需部署,也无需让代理监视实时进程。
  2. 阅读 FIX FIRST 区块。它最多包含三个入口点,并根据它们涉及的内容进行排序:资金、认证和 PII 的优先级高于健康检查。
  3. 修复这三个入口点,并分别对它们运行 npx @evlog/cli map <file>,查看规则预期的结构。列表中的其他内容暂时无需处理。
  4. 提交 evlog.map.json,并使用 --baseline 以此设置门禁。从该提交开始,已识别的回退都需要经过审查。

大型代码库通常会在第一次运行时得到很低的评分,而这个数字并不是重点。门禁机制是 ratchet,而不是评分:一个从 34 分开始且从未回退的旧应用,比一个起初为 80 分、却在一年内逐渐下降的新应用更健康。

确定性的静态检查

evlog map 不是运行时基准测试,也不是对应用进行的不稳定测量。它是针对适配器能够识别的入口点,对约定日志规范执行的确定性静态检查:这是你可以据此限制拉取请求的检查类型。

确定性意味着,在任何机器上、每次运行中,相同的源代码、CLI 版本和适配器都会产生相同的评分。规则引擎使用 AST 读取源代码,并应用版本化规则集,因此任何结果都不依赖时序、流量或环境。由此会产生三点结论:

  • 门禁不会不稳定。 由于结果不会在不同运行之间变化,--min-score <n> 的行为就像真正的测试:门禁通过意味着已识别的要求达到了你设置的最低标准,而不是运行器碰巧成功。确定性评分是你唯一应该用来阻止拉取请求的评分类型。
  • 它会逐步提高要求。 --baseline 会与已提交的 evlog.map.json 进行比较,并对现有入口点中已识别的要求或评分回退失败,其行为与快照测试完全相同。悄悄删除 log.set 的重构,无法绕过忽略差异的审查。请参见ratchet以及可观测性评分页面中的基线限制。
  • 它适合审计。 map 是一种版本化、可复现的构建产物:修订版本相同,evlog.map.json 就相同,因此三周前的评分与今天表示的是相同的检查。你可以在审查中引用它,任何人也都可以重新生成它。

它还会在任何流量出现之前报告结果。与必须监视实时应用的代理不同,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 门槛也不可能因为它们而失败。请参见机会

最后两行

✓ Already solid 会列出无需再修复的入口点,因此一个健康的应用也会得到明确肯定。▲ 76 → 86 by fixing the 3 above 表示只修复 FIX FIRST 下列出的内容时将得到的评分,这正是应该从那里开始,而不是从其他地方开始的原因。

三种视图

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

每个入口点

--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相对于已提交的 map 发生回退时以代码 1 退出(路径,或 git:<ref>
--framework <name>detected强制使用 nuxtnitronexttanstack-starthono
--no-writewrites跳过写入 evlog.map.json
--verboseoff显示逐文件解析警告
--cwd <dir>current扫描其他目录
--jsonoff将完整 map 以 JSON 格式输出到 stdout

evlog.map.json

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

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

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

它不会告诉你什么

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

  • 它读取代码,而不是流量。 一个入口点可以得到 100 分,但如果它附加的上下文有误,运行时仍然会发出无用事件。评分说明的是结构存在。
  • 它会跟踪一层导入。 通过本地模块重新导出的辅助函数可以被解析,import { useLogger } from '@/lib/evlog' 也会被计入,但通过你自己的三层抽象传递的 logger 可能无法被识别。
  • 五个框架。 Nuxt、Nitro、Next.js App Router、TanStack Start 和 Hono 拥有适配器。evlog 集成的其他框架目前还不会被扫描。
  • Hono 路由从字面量注册中读取。 带有内联函数或命名函数的 app.get('/path', handler) 会被计入。计算得到的路径、控制器风格的处理器(app.get('/users', users.list)),以及子应用挂载时使用的前缀(app.route('/api', users) 会报告不含 /api 的子路径)无法被解析。
  • 敏感性是一种启发式判断。 它会读取导入和路由路径。evlog map <file> 始终会显示原因,因此错误判断是可见的,而不是悄无声息地发生。

下一步

  • 规则:每项检查、满足条件以及确切的修复方式
  • 评分:权重、等级和敏感性分类器
  • CI:根据评分限制拉取请求