Log export (中文 (简体))
1. 工作原理
Section titled “1. 工作原理”call_logs (SQLite) → callLogExportSource.getCallLogsForExport(cursor, batchSize) → LogExportRecord[] (Logs 标签页的字段集) → 目标端 client.send(batch) → advanceLogExportCursor(id, lastRowId, count)- 计划 — 一个名为
log_export的JobRegistrycron 作业,默认值为0 * * * *(每小时一次,UTC)。在src/lib/initCloudSync.ts中注册;可通过OMNIROUTE_LOG_EXPORT_CRON覆盖。每次触发都会依次排空每个已启用的目标端。 - 游标 — 使用 SQLite 隐式的
call_logs.rowid,并按目标端持久化到log_export_destinations.cursor_row_id中。特意不使用timestamp作为游标:调用方可以自行提供该值,因此较慢的请求可能在启动时间更晚但完成更快的请求之后写入,而时间戳游标会跳过它。 - 批处理 — 每个请求处理
batch_size行(默认 500),每次运行最多处理max_rows_per_run行(默认 10000),因此大量积压会在多次触发中逐步排空,而不会阻塞单次运行。 - 投递 — 仅在
send()成功返回后推进游标。批次失败时,游标会保留在原位置,因此相同的行会在下次运行时重试。这里提供的是至少一次投递加目标端去重,而不是真正的恰好一次投递:BigQuery 使用调用日志 ID 作为每行的键,并在其自身的去重窗口内尽力保证去重。 - 重叠保护 — cron 触发与
POST .../run可能同时发生。已经在排空的目标端会被跳过,而不是被重复排空(运行结果中为skipped: true),因此并发运行无法重新发送某个批次,也无法将游标写回到更早的位置。 - 清除恢复 — 如果
cursor_row_id最终大于MAX(rowid)(整个表已被清除且 rowid 重新开始),运行器会将游标回退到 0,而不会永久失去对新记录的感知。
载荷(提示词和补全内容)
Section titled “载荷(提示词和补全内容)”默认情况下,导出仅包含 Logs 列表中显示的摘要字段。开启导出提示词和响应 (includeBodies) 后,还会额外发送 Logs 详情窗格中为每次调用显示的内容:
| 字段 | 所含内容 |
|---|---|
request_body / response_body |
仪表板呈现的调用载荷 |
pipeline_route_decision |
路由器选择的目标端和模型 |
pipeline_client_request |
客户端发送的原始请求,内容完全保持不变 |
pipeline_openai_request |
转换为内部 OpenAI 格式后的请求 |
pipeline_provider_request |
以提供者协议实际发送到上游的内容 |
pipeline_provider_response |
原始上游响应 |
pipeline_client_response |
返回给调用方的内容 |
pipeline_error |
调用失败时的管道级错误详情 |
bodies_truncated |
上述任一字段达到 maxBodyBytes 限制时为 True |
这些内容包含提示词,因此默认关闭,并特意设计为每个目标端可独立选择。发送的内容与仪表板显示的内容一致,因为两者都通过 getCallLogById 读取:载荷在写入时已完成 PII 清理和机密信息脱敏,而使用 noLog API 密钥发起的调用根本不会存储任何载荷,因此也没有可导出的内容。
载荷会逐行从文件系统制品中读取,因此仅对要求包含载荷的目标端执行数据填充。如果某一行对应的制品缺失或损坏,则会导出其摘要并将载荷设为 null,而不会导致整个批次失败并使游标停滞。
maxBodyBytes(默认 262144)限制每个字段的大小。较长的载荷会被截断而不是丢弃——截短后的提示词仍能回答“请求了什么”——并且该行会用 bodies_truncated 标记。不会导出逐块流式传输的增量数据;组装后的响应已经包含在 pipeline_provider_response 和 pipeline_client_response 中。
| 组件 | 位置 |
|---|---|
| 目标端契约 | src/lib/logExport/types.ts |
| 注册表 | src/lib/logExport/registry.ts |
| 密钥处理 | src/lib/logExport/secrets.ts |
| 运行器(游标循环) | src/lib/logExport/runner.ts |
| API 投影 | src/lib/logExport/presenter.ts |
| BigQuery 目标端 | src/lib/logExport/destinations/bigquery.ts |
| Google SA 身份验证 | src/lib/logExport/googleServiceAccount.ts |
| 调用日志源 | src/lib/usage/callLogExportSource.ts |
| 持久化 | src/lib/db/logExportDestinations.ts |
| Cron 作业 | src/lib/jobs/logExportJob.ts |
| REST 层 | src/app/api/log-export/ |
| 仪表板页面 | src/app/(dashboard)/dashboard/log-export/ |
架构:src/lib/db/migrations/170_log_export_destinations.sql。
3. REST API
Section titled “3. REST API”所有路由均需要管理身份验证(requireManagementAuth)。密钥绝不会被返回:
存储的密钥会以字面值 __stored__ 返回,更新时将该值原样发回会保留已存储的凭据。
创建或更新其类型声明了密钥的目标端时,必须提供
STORAGE_ENCRYPTION_KEY。如果没有该变量,encrypt() 会静默直通,因此写入操作会
以 400 被拒绝,而不是将凭据以明文形式写入 SQLite(这与 Telegram webhook 所采用的
防护措施相同)。
| 方法 | 路径 | 用途 |
|---|---|---|
GET |
/api/log-export/types |
目标端类型及其配置字段列表 |
GET |
/api/log-export/destinations |
列出目标端(密钥已脱敏) |
POST |
/api/log-export/destinations |
创建目标端 |
GET |
/api/log-export/destinations/{id} |
读取单个目标端 |
PUT |
/api/log-export/destinations/{id} |
更新名称 / 启用状态 / 配置 / 批处理设置 |
DELETE |
/api/log-export/destinations/{id} |
删除 |
POST |
/api/log-export/destinations/{id}/test |
探测凭据,不写入任何内容 |
POST |
/api/log-export/destinations/{id}/run |
立即清空积压,使用与计划任务相同的执行路径 |
GET |
/api/log-export/status |
Cron 状态、最近的运行记录、各目标端的积压情况 |
GET /api/log-export/types 使 UI 能够保持通用:仪表板表单根据返回的字段描述符进行
渲染,因此新增目标端无需更改 UI。
4. BigQuery 目标端
Section titled “4. BigQuery 目标端”配置键(type: "bigquery"):
| 键 | 说明 |
|---|---|
projectId |
承载数据集的 GCP 项目 |
datasetId |
[A-Za-z0-9_]+ |
tableId |
[A-Za-z0-9_]+ |
location |
仅在必须创建数据集时使用(默认值为 EU) |
serviceAccountJson |
服务账号密钥。密钥:静态加密,绝不返回 |
autoCreate |
首次导出时创建数据集和表(默认值为 true) |
服务账号需要目标表的 bigquery.tables.updateData 权限;启用 autoCreate 时,还需要
bigquery.datasets.create / bigquery.tables.create 权限。
配置的批次是一个游标单位,而不是 HTTP 单位:send() 会将其拆分为每次最多 500 行的
insertAll 调用,因此较大的 batch_size 不会触发 BigQuery 的 10 MB 请求上限。
对于瞬时状态码(408/429/500/502/503/504),系统会采用指数退避策略最多重试三次,并复用
相同的 insertIds;身份验证和架构失败会在第一次尝试时直接抛出异常,而不会浪费本次运行。
刚刚创建的表不会立即对流式端点可见,该端点在几秒内会返回 404。系统会重试该 404, 但仅限于本次运行创建了该表时——真正缺失的表仍会快速失败。请注意,如果使用一个 最近删除的表名重新创建表,BigQuery 会在数分钟内拒绝流式插入;这是先删除再重建操作本身 的特性,因此应优先使用新的表名,而不是删除后再重新添加。
**部分失败会以 HTTP 200 和非空的 insertErrors[] 返回。**这种情况会被视为失败并抛出
异常,从而阻止游标越过 BigQuery 从未接受的行;tests/unit/log-export-bigquery.test.ts
固定了这一行为。
传输使用纯 REST——先在 https://oauth2.googleapis.com/token 将自签名的 RS256 断言交换为
访问令牌,然后将行发送到 tabledata.insertAll。不引入任何 Google SDK。访问令牌按
(服务账号、作用域)在进程内缓存。
创建的表为 Logs 选项卡中的每个字段设置一列,另外还包含 exported_at,并根据调用日志的
实际查询方式进行布局:
- 按
timestamp进行日分区,因此按日期限定范围的查询只会扫描对应日期。 - 按
api_key_name、provider、model、status聚簇(依此顺序),因此按执行者、 请求去向或是否失败进行筛选时,可在每个分区内裁剪数据块。BigQuery 最多允许四个聚簇列, 且顺序很重要:仅按api_key_name筛选可以进行裁剪,仅按status筛选则不能。 - 可选的分区保留期限通过
partitionExpirationDays设置(0 表示保留所有内容),并在 创建表时应用。
这两个设置都在创建时生效。现有表会保留其已有的布局,因此,如果你想采用这些设置,请将目标指向一个新的表 ID。
tests/unit/log-export-bigquery.test.ts 会断言映射器与表结构始终保持同步,因此新增的调用日志列不会在导出过程中被悄然丢弃。
批次会同时按行数和序列化后的字节数进行分块。导出载荷后,仅按行数分块是不够的:500 行包含提示词的数据可能达到数十兆字节,而 insertAll 会拒绝超过 10 MB 的请求。每个分块达到 500 行或 9 MB 时即结束,以先达到者为准。
5. 添加目标
Section titled “5. 添加目标”- 创建
src/lib/logExport/destinations/<name>.ts,导出一个LogExportDestinationType: 包含一个 ZodconfigSchema、用于 UI 的fields描述符数组、secretFields,以及一个 返回test()/prepare()/send(records)的createClient(config)。 - 将其添加到
src/lib/logExport/registry.ts中的DESTINATIONS数组。 - 在
tests/unit/下编写测试。
这就是全部改动:持久化、cron 作业、REST 层、密钥加密和仪表板表单都会读取该注册表。
新目标需遵循两条规则:
send()在部分失败时必须抛出异常。成功返回意味着“目标中已有这些行”, 此时游标将永久越过这些行。- 接受用户提供 URL 的目标在发起请求前,必须通过
parseAndValidateWebhookUrl(src/shared/network/outboundUrlGuardPolicy.ts)进行验证, 其方式与 webhook 相同。BigQuery 不需要此验证,因为它的主机地址是常量。
- 仪表板:集成 → 日志导出。添加一个目标,运行测试以在不写入任何行的情况下检查凭据, 然后启用该目标。
- 积压:每张目标卡片都会显示待处理行数和游标;
GET /api/log-export/status会返回相同数据,以及最近 20 次作业运行记录。 - 某个目标失败不会导致其他目标失败——运行摘要会在
last_status/last_error中记录各目标的 状态,作业运行历史则会保留汇总结果。 - 删除目标也会删除其游标。 重新添加该目标后,将从保留的最早调用日志开始,
这会重新发送目标中可能已存在的行。在 BigQuery 中,每行的
insertId仅能在 BigQuery 自身的 去重窗口内消除此类重复,因此应优先禁用目标,而不是删除它。
HagiCode
HagiCode 是一套智能体编码工作台:结构化工作流、多 Agent 并行执行与 Hero Dungeon 视图,把想法变成真正交付的软件。
让想法更快变成好用的软件,让智能编码更聪明、更高效,也更有趣。

- Smart结构化工作流将意图转化为从想法到交付的可执行路径。
- Efficient多 Agent 工作流让调研、实现与审阅并行推进。
- FunHero Dungeon 让长时间编码协作更直观、更有参与感。