☰
Penpot Error Reports CLI 实战:基于 RPC API 的错误报告查询、统计与排查指南
2026/11/2 20:58:28 网站建设 项目流程

Penpot Error Reports CLI 实战:基于 RPC API 的错误报告查询、统计与排查指南

【免费下载链接】penpotPenpot: The open-source design platform for Product teams that need scalable collaboration.项目地址: https://gitcode.com/GitHub_Trending/pe/penpot

Penpot 在仓库中内置了一款 Node.js 命令行工具 scripts/error-reports.mjs,可通过后端 RPC 接口查询服务端记录的错误报告(server error report),支持时间范围、来源、租户、版本等多维过滤、游标分页、以及签名/突发/热力图等统计聚合。本文以官方说明文档为核心,结合脚本源码与后端 RPC 实现,完整讲解该工具的配置、三大子命令(list/get/stats)的每一个参数、输出格式、分页原理、Hint 归一化机制与排障要点,帮助读者用它快速定位生产环境中的异常聚集与回归问题。

关联文档:.serena/memories/scripts/error-reports.md;工具实现:scripts/error-reports.mjs;后端实现:backend/src/app/rpc/commands/error_reports.clj。

一、工具定位与适用场景

error-reports是面向 Penpot 服务端运维与排障场景的专用 CLI。它直接调用后端 RPC 方法(get-error-reports/get-error-report)读取数据库server_error_report表中的错误日志,不经过任何 Web UI。它的典型用途包括:

  • 从数据库中查询错误报告,用于 Debug 与事后分析;
  • 按source(来源)、kind(类型)、tenant(租户)、version(后端版本)过滤错误;
  • 将错误数据导出为 JSON、NDJSON 或终端表格;
  • 计算错误统计(Top 签名、版本分布、来源分布、audit-log 类型分布、小时分布、5 分钟突发窗口、星期×小时热力图);
  • 按 ID 调查单条错误报告的完整上下文与调用栈。

从源码结构看,该工具在后端侧依赖的 RPC 端点自 2.20 版本开始提供(见 error_reports.clj 中::doc/added "2.20"标记),因此在调用前需确认后端版本包含对应端点。

二、前置条件与配置

2.1 环境要求

  • Node.js:脚本使用 Node 原生fetch发起请求,需要支持 Fetch API 的运行时;
  • 依赖包:commander(CLI 参数解析)与dotenv(读取 .env)。两者已声明在仓库根目录 package.json(commander: ^15.0.0、dotenv: ^17.4.2),在仓库根目录执行pnpm install(或npm install)即可获得;
  • 运行中的 Penpot 后端,且已包含 error-reports RPC 端点;
  • 带error-reports:read权限的访问令牌(access token)。

2.2 创建 .env 配置文件

在仓库根目录创建.env文件,写入两行配置:

PENPOT_API_URI=http://localhost:3450 PENPOT_ACCESS_TOKEN=<your-token>

其中:

  • PENPOT_API_URI指向后端公开 API 地址。脚本中的rpcCall(见 error-reports.mjs)实际拼接的请求为POST ${PENPOT_API_URI}/api/main/methods/${method},即默认 3450 端口对应本地开发后端;
  • PENPOT_ACCESS_TOKEN为访问令牌,请求头为Authorization: Token <accessToken>。

若缺失任一项,脚本会打印 .env 配置提示并退出(对应 loadConfig 的校验逻辑)。

2.3 为令牌授予error-reports:read权限

令牌权限并非通过 UI 就能直接授予。查看 access_token.clj 的实现可知:通过公共 API 创建的令牌默认只有空权限数组,提升类权限(如error-reports:read)不能通过公开 API 分配,只能借助 SQL 或 REPL 辅助函数。

方式一:直接更新数据库

UPDATE access_token SET perms = ARRAY['error-reports:read']::text[], updated_at = now() WHERE id = '<token-uuid>';

方式二:使用 REPL 辅助函数(同样定义在 access_token.clj):

(repl:grant-access-token-perm cfg token-id "error-reports:read")

权限校验在后端 RPC 定义中强制声明:::rpc/auth-type :token、::rpc/perms #{"error-reports:read"}(见 error_reports.clj),没有该权限时请求会被拒绝(HTTP 403)。

三、底层协议:CLI 与后端 RPC 的对应关系

为了用好工具,值得先了解脚本背后真正调用的两个 RPC 方法(源码见 error_reports.clj):

CLI 命令实际 RPC 方法URL 形态(基于rpc.clj路由)用途
list::get-error-reports/api/main/methods/get-error-reports分页列出错误摘要
get::get-error-report/api/main/methods/get-error-report按 ID 取单条完整报告

路由在 rpc.clj 中定义:/api/main/methods/:method-name走mainAPI(CORS + 会话/令牌鉴权),方法注册表通过 rpc.clj 加载app.rpc.commands.error-reports命名空间。

值得注意的分页语义:list --from会映射为服务端参数since(最旧边界),--to映射为服务端参数until(最新边界);脚本注释与代码明确标注了这一映射关系(见 error-reports.mjs)。

3.1 服务端过滤与排序的 SQL 实现

后端 build-list-query 揭示了几条影响过滤行为的细节:

  • 表为server_error_report,content是 Transit JSONB 字段,kind取自content->>'~:kind'(缺省时回退~:origin);
  • --hint过滤实际是 SQLILIKE '%text%'(不区分大小写的模糊匹配),因此支持部分匹配;
  • --source会先由名称映射回整数编码,source-names共 5 种(见下节);
  • 查询恒定为ORDER BY created_at ASC, id ASC,配合LIMIT n,即升序(最旧在前)返回;
  • 判断是否还有下一页:多取 1 条(inc limit),若实际返回行数超过 limit 即生成游标(见 get-error-reports 方法体)。

3.2 五个合法 Source 名称

文档中列出的来源名为logging、audit-log、rlimit;从后端 source-names 的完整映射看,--source实际接受以下5 个值:

名称数据库编码含义
legacy-v11旧版 v1 记录
legacy-v22旧版 v2 记录
logging3常规日志上报
audit-log4审计日志条目
rlimit5资源限制触发(rate limit 等)

CLI 的--source帮助文本也同步列出了这 5 个候选值(见 error-reports.mjs)。

四、命令总览与通用说明

./scripts/error-reports.mjs <command> [options]

三个子命令:

  • list:分页列出错误报告,支持过滤与多种输出格式;
  • get:按 ID 获取单条错误报告;
  • stats:读取 API/文件/stdin 数据并计算统计。

通用要点:

  • 所有过滤项可任意组合(AND 语义);
  • 同时支持--option=value与--option value两种写法;
  • 默认读取仓库根目录.env,可通过--env <path>指定其它配置文件;
  • 如需跨目录执行可写为node /data/web/disk1/git_repo/GitHub_Trending/pe/penpot/scripts/error-reports.mjs或先进入仓库根目录再运行。

五、list:列出与过滤错误报告

./scripts/error-reports.mjs list [options]

5.1 参数表

Flag说明默认
-l, --limit <n>每页条数上限(服务端硬上限 200,后端 schema 校验 1..200)50
--from <date>ISO 时间戳,最旧边界(取该时间之后的数据)—
--to <date>ISO 时间戳,最新边界(取该时间之前的数据)—
--since <date>ISO 时间戳,手动分页的显式游标—
--since-id <uuid>取该 ID 之后的错误(游标分页)—
-s, --source <name>按来源过滤(见“五个合法 Source 名称”一节)—
-p, --profile-id <uuid>按 Profile ID 过滤—
-k, --kind <kind>按类型过滤(字符串)—
-t, --tenant <tenant>按租户过滤—
--version <version>按后端版本过滤—
--hint <text>按 hint 模糊匹配(ILIKE)—
-a, --all自动抓取所有分页(流式输出)false
-f, --format <type>输出格式:json、table或ndjsontable
--normalize-hints归一化 hint(剥离动态值)false
-o, --output <file>写入文件而非 stdout—
--env <path>自定义 .env 路径.env
-h, --help帮助—

流式行为说明:--all模式下输出必须为ndjson或table;--all --format json会被明确拒绝(见 cmdList 中的校验逻辑),因为--all的本质是边拉取边输出。--all --format table会先打印表头再逐行输出;--format ndjson则总是逐行流式输出一个 JSON 对象。

表格模式下,hint列会被截断到 60 字符(truncateHint),单页列表会显示“Found N error reports”,当还有更多数据时会在末尾提示:

More results: use --since <nextSince> --since-id <nextId>

六、get:按 ID 查询单条错误报告

./scripts/error-reports.mjs get [options]
Flag说明必填
--id <uuid>错误报告 ID是
--error-id <id>错误的 error-id(备选标识)否
-f, --format <type>json或table否(默认table)
--env <path>自定义 .env否(默认.env)
-h, --help帮助否

注意:CLI 定义中--id为requiredOption(见 get 命令注册),实际取值逻辑是args.id优先、否则回退到args.errorId。

get返回的是完整的单条报告。表格模式(formatGetTable)会输出这些字段:

  • ID、Created At、Source、Profile ID、Kind、Version;
  • Hint(归一化错误提示);
  • HREF(关联链接);
  • Context(上下文信息,逐行缩进展示);
  • Params/Props(若存在且非{});
  • Report(取trace或report字段的完整调用栈/报告正文)。

若报告不存在,后端会返回code: report-not-found的 RPC 错误(见 error_reports.clj)。

七、stats:错误统计与异常检测

./scripts/error-reports.mjs stats [options]

stats有三种数据来源(cmdStats):

  1. --input <file>:从本地 JSON / JSON 数组 / NDJSON 文件读取;
  2. stdin 管道:当 stdin 不是 TTY 时自动读取(同样支持 JSON、数组、NDJSON 三种形态);
  3. 直接访问 API:无输入文件且 stdin 为空时,用--from/--to/--limit自动翻页抓取。
Flag说明默认
--from <date>统计区间起点(ISO 时间戳)—
--to <date>统计区间终点(ISO 时间戳)—
--limit <n>从 API 抓取时每页条数200
--input <file>从本地 JSON/NDJSON 文件读取—
--burst检测超过平均速率 3 倍的 5 分钟突发窗口false
--heatmap展示星期×小时的密度热力图false
-f, --format <type>json或tabletable
--env <path>自定义 .env.env

7.1 聚合维度

所有参与统计的 hint 都会先归一化再分组(见第八节),由此实现“同签名错误合并”。表格模式输出 6 组统计(cmdStats):

  • Total / Unique profiles:总量与去重后的 Profile 数;
  • By Signature (normalized hint):按归一化 hint(错误签名)降序;
  • By Source:按来源分布;
  • By Kind (audit-log only):仅对source == "audit-log"的子集按 kind 统计(代码中kindScope: "audit-log");
  • By Version:按版本分布;
  • By Hour (UTC):按小时分布。

每个维度都带数量、百分比、以及长度归一化的条形图,便于肉眼比较占比。

JSON 模式下结构为:

{ "total": 123, "uniqueProfiles": 5, "byHint": [{"key": "...", "count": 30}], "byVersion": [], "bySource": [], "byKind": [], "byHour": [], "kindScope": "audit-log" }

开启--burst后追加bursts数组,开启--heatmap后追加heatmap对象。

7.2 Burst(突发)检测原理

--burst的实现见 buildBurstWindows:以全部有效时间戳覆盖的区间按 5 分钟(5 * 60 * 1000ms)切窗,计算每窗口平均数量averagePerWindow,凡单窗口数量超过max(1, average * 3)即判为突发。输出每个突发窗口的:

  • start/end(ISO 时间区间);
  • count(窗口内错误数)、averagePerWindow、ratio(相对平均值的倍数);
  • ids(最多 12 个样本错误 ID,便于直接跟进get)。

7.3 Heatmap(热力图)原理

--heatmap由 buildHeatmap 实现,按 UTC 计算每条记录落在「周一~周日 × 00~23 时」的哪个桶。表格输出将 24 小时压缩成十位数字一行(如0 0 1 1 ...),单元格分 4 级密度:·=0、1=低、2=中、3=高、4=峰值。热力图对发现“每天固定时段集中报错”这类周期性规律尤其有效。

八、Hint 归一化机制

--normalize-hints(list)以及在stats中总是执行的 hint 归一化,目标是把含动态值(文件 ID、UUID、耗时等)的 hint 收敛为“错误签名”,从而正确聚合同类错误。

以源码 normalizeHint 为准,实际替换规则包括:

  1. 引号与空白规范化:弯引号(“”‘’)转为直引号、转义引号还原、连续空白折叠为单空格并 trim;
  2. URI 替换:https?://...→<uri>;
  3. file-id 上下文替换:形如file-id: <uuid>、file_id=<uuid>等关联位置 →<file-id>;
  4. UUID 替换:匹配8-4-4-4-12十六进制形态的 UUID →<uuid>;
  5. 括号内数字 ID 替换:(12345)→(<id>);
  6. 耗时替换:7.5s、2m3.027s等多段带单位时间 →<elapsed>;
  7. 空结果兜底:归一化后为空串时返回(empty),便于对无 hint 记录单独归组。

九、输出格式详解

9.1 table(默认)

面向终端的可读表格。list的列固定为ID | Created At | Source | Profile ID | Kind | Hint(见 TABLE_HEADERS),hint 超过 60 字符会截断。--all时表头先行打印、数据行到达即输出,无需缓冲。

9.2 json

单页模式的结构化输出,形如:

{ "items": [ { "...": "..." } ], "nextSince": "...", "nextId": "..." }

其中nextSince/nextId仅在还有下一页时存在,是手动分页的游标。--all不能与--format json组合(会报错退出);流式场景请用ndjson。

9.3 ndjson

每行一个 JSON 对象,始终流式输出。适合管道处理,例如配合jq抽取字段或wc -l计数。

./scripts/error-reports.mjs list --all --format ndjson | jq -c '{id, hint}' ./scripts/error-reports.mjs list --all --format ndjson | wc -l

十、分页机制详解

服务端按升序返回(最旧在前),游标为(created_at, id)复合游标。这与部分旧实现的 DESC 排序不同,文档与后端ORDER BY created_at ASC, id ASC(见 error_reports.clj)互相印证。

10.1 手动分页

用响应中的nextSince与nextId作为下一请求的--since/--since-id:

./scripts/error-reports.mjs list --limit 50 # 从上一响应取 nextSince / nextId ./scripts/error-reports.mjs list --limit 50 --since "2026-01-20T10:29:00Z" --since-id "next-uuid"

注意--since与--since-id是一对游标值,通常应一起传(服务端以(since, since-id)或(since, 全零 uuid)构造复合比较条件,见 error_reports.clj)。

10.2 自动分页

--all让 CLI 自动循环翻页直到服务端不再返回游标:

./scripts/error-reports.mjs list --all

10.3 时间范围查询

--from/--to用于界定时间窗口,映射为服务端since/until:

./scripts/error-reports.mjs list --from 2026-07-20T00:00:00Z --to 2026-07-23T23:59:59Z --all

10.4 分页与单页模式的实现细节

阅读 cmdList 可见:非流式单页模式下脚本仍会循环抓取(仅在--all时继续翻页),并把最后一次响应的nextSince/nextId附在 JSON 结果的items之外;流式模式下每收到一页即逐条写出并更新内部游标。

十一、完整实战示例

以下示例均以仓库根目录为工作目录、scripts/error-reports.mjs具备执行权限为前提(无执行权限时用node scripts/error-reports.mjs等价替代)。

列出最近 10 条错误

./scripts/error-reports.mjs list --limit 10

时间窗口查询(某一天全部)

./scripts/error-reports.mjs list --from 2026-07-23T00:00:00Z --to 2026-07-23T23:59:59Z --all

全量流式导出为 NDJSON

./scripts/error-reports.mjs list --all --format ndjson > errors.ndjson

用 --output 直接写文件(免 shell 重定向)

./scripts/error-reports.mjs list --all --format ndjson -o errors.ndjson ./scripts/error-reports.mjs list --format json -o errors.json

按来源过滤

./scripts/error-reports.mjs list --source audit-log --limit 20

按类型过滤

./scripts/error-reports.mjs list --kind exception-page

按租户过滤

./scripts/error-reports.mjs list --tenant production

按后端版本过滤

./scripts/error-reports.mjs list --version 2.1.0

按 hint 模糊搜索

./scripts/error-reports.mjs list --hint "NullPointerException"

自动翻页抓取全部

./scripts/error-reports.mjs list --all

按 ID 查询单条报告

./scripts/error-reports.mjs get --id 550e8400-e29b-41d4-a716-446655440000

输出为 JSON

./scripts/error-reports.mjs list --limit 5 --format json

组合过滤(来源 + 类型 + 租户)

./scripts/error-reports.mjs list --source audit-log --kind exception-page --tenant production --limit 50

统计 + 突发检测 + 热力图

./scripts/error-reports.mjs stats --from 2026-07-23T00:00:00Z --to 2026-07-23T23:59:59Z --burst --heatmap

从文件做统计

./scripts/error-reports.mjs stats --input errors.json

从管道做统计

./scripts/error-reports.mjs list --all --format json | ./scripts/error-reports.mjs stats ./scripts/error-reports.mjs list --all --format ndjson | ./scripts/error-reports.mjs stats

十二、错误处理与常见问题

脚本对常见失败给出了可读提示(实现集中在 rpcCall):

现象含义应对
提示创建.env并给出示例缺少PENPOT_API_URI或PENPOT_ACCESS_TOKEN按“前置条件与配置”一节补齐配置
Cannot connect to server (connection refused)后端未启动或地址不可达启动后端;核对PENPOT_API_URI
Cannot resolve server hostname(ENOTFOUND)域名无法解析检查PENPOT_API_URI主机名
HTTP 401令牌无效或已过期重新签发/刷新 token
HTTP 403缺少error-reports:read权限执行 2.3 节的 SQL 授权语句
HTTP 502 / 503 / 504后端宕机、暂不可用或网关超时确认服务状态与 URI 可达性
RPC error [report-not-found]: ...指定 ID 的报告不存在核对 ID;确认租户/环境正确

十三、与其它脚本/命令的集成

工具的管道友好性使其能无缝接入日常 shell 工作流:

  • jq 抽取字段:
    ./scripts/error-reports.mjs list --all --format ndjson | jq -c '{id, hint}'
  • 拉取一次、多角度统计:
    ./scripts/error-reports.mjs list --all --format ndjson | ./scripts/error-reports.mjs stats
  • grep / 文本搜索:对表格或 NDJSON 输出做二次模式过滤;
  • --output 落盘:避免大结果集在终端阻塞或丢失。

十四、核心设计原则速览

文档与源码共同强调了以下关键约定,使用时可对照排查:

  • 必须鉴权:使用带error-reports:read权限的 access token;
  • API 地址可配置:由.env的PENPOT_API_URI决定,默认开发端口 3450;
  • 默认表格输出:结构化用--format json,流式用--format ndjson;
  • --all必然流式:只能搭配ndjson或table;--all --format json会被拒绝;
  • 过滤器可自由组合:所有过滤参数可叠加(AND 语义);
  • 两种参数写法均可:--option=value与--option value;
  • 升序返回:服务端按最旧在前返回(与历史 DESC 行为不同),翻页靠nextSince/nextId游标。

十五、进一步探索仓库

如需深入理解数据模型与上报链路,可继续阅读以下文件:

  • 工具源码与全部实现细节:scripts/error-reports.mjs;
  • 后端 RPC 方法、SQL 过滤与游标实现:backend/src/app/rpc/commands/error_reports.clj;
  • 主 API 路由注册(/api/main/methods/:method-name):backend/src/app/rpc.clj;
  • 访问令牌权限模型与 REPL 授权函数:backend/src/app/rpc/commands/access_token.clj;
  • 表结构演进相关迁移脚本:0040-add-error-report-tables.sql、0101-mod-server-error-report-table.sql、0105-mod-server-error-report-table.sql、0144-mod-server-error-report-table.sql、0152-rename-version-and-add-indexes-to-server-error-report.sql;
  • 依赖声明(commander / dotenv):package.json。

【免费下载链接】penpotPenpot: The open-source design platform for Product teams that need scalable collaboration.项目地址: https://gitcode.com/GitHub_Trending/pe/penpot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询