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-v1 | 1 | 旧版 v1 记录 |
legacy-v2 | 2 | 旧版 v2 记录 |
logging | 3 | 常规日志上报 |
audit-log | 4 | 审计日志条目 |
rlimit | 5 | 资源限制触发(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或ndjson | table |
--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):
--input <file>:从本地 JSON / JSON 数组 / NDJSON 文件读取;- stdin 管道:当 stdin 不是 TTY 时自动读取(同样支持 JSON、数组、NDJSON 三种形态);
- 直接访问 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或table | table |
--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 为准,实际替换规则包括:
- 引号与空白规范化:弯引号(
“”‘’)转为直引号、转义引号还原、连续空白折叠为单空格并 trim; - URI 替换:
https?://...→<uri>; - file-id 上下文替换:形如
file-id: <uuid>、file_id=<uuid>等关联位置 →<file-id>; - UUID 替换:匹配
8-4-4-4-12十六进制形态的 UUID →<uuid>; - 括号内数字 ID 替换:
(12345)→(<id>); - 耗时替换:
7.5s、2m3.027s等多段带单位时间 →<elapsed>; - 空结果兜底:归一化后为空串时返回
(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 --all10.3 时间范围查询
--from/--to用于界定时间窗口,映射为服务端since/until:
./scripts/error-reports.mjs list --from 2026-07-20T00:00:00Z --to 2026-07-23T23:59:59Z --all10.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),仅供参考