Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

日志与可观测性

ZeroClaw 发出的每个事件都经过同一个 crate:zeroclaw-log。该 crate 负责磁盘上的 JSONL 格式、仪表板读取的进程内广播流、可选的类型化 Observer 桥接、原生 OTLP 日志导出桥接,以及各子系统调用的宏(record!、scope!、spawn!)。

本页介绍运维人员所需的内容:配置、日志位置、事件结构以及如何查询它们。

Desktop 守护进程捕获

桌面端监督程序将守护进程的标准输出和标准错误合并捕获到一个有大小上限的文件中:<config-dir>/logs/zeroclaw-desktop-daemon.log。<config-dir> 遵循规范的配置解析优先级:ZEROCLAW_CONFIG_DIR,其次是 ZEROCLAW_DATA_DIR,再次是已弃用的 ZEROCLAW_WORKSPACE,最后是 Homebrew/默认解析。该文件上限为 8 MiB;当输出超过上限时,最旧的字节会被压缩清除,并保留最新的尾部内容。

配置([observability])

默认值:log_persistence = "rolling"、log_persistence_max_entries = 200、log_tool_io = "redacted"、log_tool_io_truncate_bytes = 40960、log_llm_request_payload = "off"。全新安装会在 ~/.zeroclaw/data/state/runtime-trace.jsonl 生成一个包含 200 条事件的滚动 JSONL,仪表盘的 Logs 页面无需进一步配置即可正常工作。

log_persistence = "none" 会完全禁用持久化,但不会限制仪表盘 SSE 和原生 OTLP 导出所使用的广播流。可选的类型化 Observer 桥接同样独立于持久化,但只有在显式绑定时才会接收规范化日志事件;当前的生产环境启动流程并未安装该绑定。

持久化是尽力而为的,而非事务性的审计保证。Observer 桥接、原生 OTLP 队列以及广播投递都发生在事件被提交到有界后台写入队列之前。队列已满或工作线程写入失败都可能导致事件未写入 JSONL。定期同步仅覆盖当前活动文件;每日轮转(在新的 UTC 日的第一次追加之前进行)以及大小轮转(在越过阈值的追加之后进行)可能在未先同步的情况下重命名活动文件,因此同步周期无法为刚轮转的归档文件提供持久性上限保证。有关各投递层的独立投递契约,请参阅日志架构。

归档轮转 (log_persistence = "rotating")

rotating 通过轮转当前活动文件来保留所有事件,而不是截断它。当触发大小、每日边界或条目数量条件时,活动文件会被重命名为带时间戳的归档文件。每次轮转后,旧归档会按数量和时间期限进行清理。

键默认效果
log_persistence_max_bytes0当追加写入使活动文件达到或超过此字节数时旋转一次。0 禁用按大小轮转。
log_persistence_rotate_dailytrue在新的 UTC 日的第一个事件之前,归档一个最后写入发生在更早一天的文件。
log_persistence_max_entries_per_segment0当分段中的非空行数达到此上限时进行轮转。稳定状态下,每个归档文件恰好包含此数量的条目。首次在已有日志上启用时,整个文件会被归档(产生一次超出上限的转换);下一次轮转后恢复稳定状态。0 禁用按条目数轮转。
log_persistence_retention_max_files7最多保留这么多归档;轮转后,超出上限的最旧归档会被删除。0 表示保留全部。
log_persistence_retention_max_age_days0轮换后删除早于这么多天的归档。0 会禁用基于年龄的清理。

归档文件与活动文件位于同一目录,并保留其扩展名,同时在扩展名之前插入可排序的 UTC 时间戳。例如,runtime-trace.jsonl 轮转后变为 runtime-trace.0000000001-20260624-031500.jsonl。序号前缀(0000000001)在轮转时写入,决定读取端的排序顺序。尽力而为的旁路文件(sidecar)用于在重启之间保留高水位标记。如果无法写入该旁路文件,保留策略会删除所有带编号的归档,随后守护进程重启,序号就可能被重复使用,未完成的游标因此可能绑定到更新的历史记录上。在引入序号之前写入的归档保持原有格式(runtime-trace.20260624-031500.jsonl),并且排在所有带编号的归档之前。

仪表板和 GET /api/logs 现在会将当前活动文件与所有保留的归档文件视为一个逻辑事件流进行读取。它们会先按最旧归档优先的顺序合并,再以最新事件优先的顺序返回。API 提供了一个感知分段的游标(next_segment_cursor),与现有的字节偏移游标(next_cursor_line_offset)并存。在后续请求中传入 ?until_segment_cursor=,即可跨分段边界进行分页。旧的字节偏移游标仍然有效,并会被解释为活动文件中的偏移量。

每日轮换密钥以 UTC 日历为准,因此其边界可能不会与其他时区的本地午夜对齐。除非 log_persistence = "rotating",否则这些密钥会被忽略,而 none、rolling 和 full 模式保持不变。

原生 OTLP 日志 (observability-otel)

与默认构建相比,该功能的 SDK 和 HTTP 客户端体积更大,因此 observability-otel 功能仍需显式开启(opt-in),不包含在默认构建中。

Wait — I should return only the translation, following the system instructions. Let me redo it properly.

可选启用的 observability-otel 功能通过官方 OpenTelemetry SDK 导出追踪(traces)、指标(metrics)和日志(logs)。日志使用采用 protobuf 编码的 OTLP/HTTP 协议,并以批量方式发送到 <otel_endpoint>/v1/logs;追踪和指标则保持标准的 /v1/traces 和 /v1/metrics 端点。由于 SDK 和 HTTP 客户端的体积较大,该功能仍不包含在默认构建中。

[observability]
backend = "otel"
otel_endpoint = "http://localhost:4318"
otel_service_name = "zeroclaw"

# 本地/离线检查仍可同时使用。
log_persistence = "rolling"

# 可选的供应商或收集器请求头。
[observability.otel_headers]
Authorization = "Bearer <collector-token>"

使用 --features observability-otel 进行构建。端点可以是 OpenTelemetry Collector、Grafana Alloy,或任何接受通过 HTTP/protobuf 发送 OTLP 日志的厂商端点。请求头值属于机密配置字段,绝不会出现在导出的记录中。

每个规范化的 LogEvent 都会映射到稳定版 OpenTelemetry Logs Data Model 中:

ZeroClaw 字段OpenTelemetry 目标
@timestampLogRecord Timestamp;导出时间变为 ObservedTimestamp
severity_number, severity_textLogRecord 标准化严重级别字段
messageLogRecord Body
配置的服务名称 + crate 版本资源 service.name,service.version
有效的 16 字节 trace 标识符和 8 字节 span 标识符LogRecord TraceId、SpanId
event.*ECS 兼容的 LogRecord 属性
_file, _linecode.file.path, code.line.number
模型/提供商/令牌字段gen_ai.* 语义属性
工具身份tool.name
zeroclaw.* 归属命名空间的 LogRecord 属性,包括 zeroclaw.sop_run_id

应用层关联字符串如果不是有效的 OTel trace/span 标识符,仍会以 zeroclaw.trace_id / zeroclaw.span_id 属性的形式保留,便于搜索,而不会被强行转换为虚构的追踪上下文。嵌套的 JSON 值会映射为 OTel AnyValue 的 map 和 list。仅用于广播的临时属性(包括配对码)永远不会传递给导出器。

The exporter is additive. JSONL、仪表盘 SSE、/api/logs、CLI 和 ZeroCode 保持其现有的 schema,并且在 collector 不可用时仍可继续工作。Exporter 替换或关闭时会强制刷新 SDK 批处理队列;运行时的事件发出本身只负责入队,不会在调用方执行任何网络 I/O。

GenAI span 属性(observability-otel)

llm.response span 携带 OTel GenAI 消息内容属性 gen_ai.input.messages、gen_ai.output.messages 和 gen_ai.system_instructions(以 JSON 字符串编码),它们会填充 Langfuse/Tempo 中的输入/输出/系统面板。

隐私与成本。 捕获的内容会尽力进行清理:内联图像数据会被省略,已知的凭据格式(key=value、bearer,以及 sk-/ghp_/xoxb--style 前缀)会被遮盖。这并不保证移除所有机密信息或 PII。如果对话可能包含敏感内容,建议使用受访问控制的 trace 后端。捕获成本为每个 agent-loop 迭代的 O(prompt size)(不断增长的历史会在每轮重新扫描),且完整文本会按比例随每个 span 的 payload 增长。对于按字节计费的后端,应在 exporter 端进行截断,而不是丢弃这些属性。

OTel 内容捕获

OTel 内容捕获独立于基于日志的捕获(log_tool_io, log_llm_request_payload)。它控制哪些内容会作为 OpenTelemetry span 属性发出。

GenAI 内容

在 OTel spans 上控制 gen_ai.system_instructions、gen_ai.input.messages 和 gen_ai.output.messages。

[observability]
otel_genai_content = "off"            # off | redacted | full
otel_genai_content_max_chars = 1000  # 每个字段的截断限制
  • off(默认):无内容属性,仅元数据。
  • redacted:内容经过泄漏扫描,并在每个字段按 max_chars 截断。
  • full:内容已进行泄漏扫描,但未截断。

工具 I/O

在 OTel spans 上控制 gen_ai.tool.arguments、input.value、gen_ai.tool.result 和 output.value。

[observability]
otel_tool_io = "off"                  # off | redacted | full
otel_tool_io_max_chars = 1000        # 每个字段的截断限制
  • off(默认):不包含内容属性,仅包含工具名称 + 结果。
  • redacted:内容经过泄漏扫描,并在每个字段按 max_chars 截断。
  • full:内容已进行泄漏扫描,但未截断。

行为说明

  • 将 *_max_chars = 0 设置为等同于该策略的 off。
  • 内容在截断前始终会被清理(凭据模式 + 密钥模式)。
  • 截断会保留工具参数的 JSON 结构(叶子字符串会被截断)。
  • 截断的字段会带有 …[truncated {n} of {total} chars] 标记。该标记属于元数据,不计入 max_chars:保留的内容恰好是 max_chars 个字符,并在其后附加该标记。
  • 默认 off 是相较于先前行为的一项以隐私优先的变更(功能受特性门控,但在启用时始终开启)。
  • 内容策略绑定到 observer/config 实例,而不是进程。不存在进程全局的 OTel 内容策略:每个 OtelObserver 在构造时都会从 ObservabilityConfig 派生一个不可变的内容配置,并在 OTel 导出边界处查阅它。同一进程中的多个 observer 保持独立的策略:后创建的 observer 不能覆盖或静默关闭先创建的 observer 的隐私设置(没有最后写入者生效,也没有跨 observer 漂移)。

嵌套内存和 RAG span(observability-otel)

memory.recall、memory.store 和 rag.retrieve span 会在操作于已归属的 agent turn 内运行时嵌套在 gen_ai.agent.invoke turn span 下,因此完整的 turn(记忆召回、自动保存存储、LLM 调用、工具调用)会在 Langfuse/Tempo 中呈现为一条 trace。这三个事件携带与 LLM 和工具事件相同的 channel / agent_alias / turn_id 三元组,并作为 zeroclaw.channel、gen_ai.agent.name 和 zeroclaw.turn_id span 属性公开。

在关联的 turn 之外进行的内存操作会持续产生根 span:网关 REST 内存存储,以及 process_message 硬件 RAG 检索——后者在 turn 括号开启之前运行,因此仍是一个带有匹配 zeroclaw.turn_id 属性的根 span(该 span 的完整嵌套在 #8844 中跟踪)。不再匹配活跃 turn 的 turn_id 也会降级为根 span,而不是猜测其父级。

LLM 请求载荷捕获 (log_llm_request_payload)

log_llm_request_payload 控制 llm_request 事件是否在 messages_count 之外还记录发出的提示词和对话。它默认关闭,且是一个隐私敏感面:启用后,ZeroClaw 会在每一轮持久化完整的系统提示词以及整个对话历史。

值捕获了什么
off(默认)不记录任何消息内容。该事件仍会携带 messages_count,以及下文所述、始终启用的前缀指纹(system_chars、tools_count,以及存在时的 system_sha256、tools_sha256)。它们是摘要而非内容,但跟踪读取者仍可对其进行相等性比较。
redacted完整消息历史记录(角色 + 内容),使用与 raw_response 和工具 I/O 相同的 scrub_credentials 处理后,再按 log_tool_io_truncate_bytes 截断。截断会通过 request_messages_truncated 和 request_messages_original_bytes 标记。
full与 redacted 相同的凭证清理,但不截断(回放保真度,镜像 raw_response)。

每个 llm_request 事件还会携带运行时请求输入的前缀指纹,与负载策略无关:system_chars 和 tools_count 始终存在;system_sha256(对连续的前导 system 消息计算 SHA-256,取前 16 个十六进制字符,计算时将这些消息的内容序列化为 JSON 数组,仅当第一条消息的角色为 system 时存在)和 tools_sha256(对原生工具规范序列化为 JSON 数组后进行同样的计算,仅当附加了工具规范时存在,因此工具集合顺序改变会得到不同的指纹)。它们描述的是运行时交给提供方适配器的内容,每个逻辑请求对应一行。提供方侧的转换(如 schema 清理、cache-control 放置、OAuth 系统前缀、兼容协议上的系统消息合并、Anthropic 适配器丢弃的第二条前导系统消息、适配器内部的重试)不在被哈希的字节范围之内。应将变化视为诊断提示,而非缓存判定:system_sha256 变化表示某条前导系统消息发生了变化,或被插入或删除,例如包含的工作区文件发生了变化、调用前钩子修改或添加了一条消息,或工具框架在原生协议与文本协议之间切换;tools_sha256 变化表示运行时工具规范的序列化方式发生了变化,其中包括线路上并不传输的元数据字段。在文本协议模式下,工具说明位于系统文本之中,因此 tools_count 为 0,tools_sha256 不存在,而工具集合的变化会体现在 system_sha256 中。应以提供方报告的缓存用量来确认实际的缓存命中或未命中。这些指纹是计数和未加盐的截断哈希,而非内容:它们可以揭示输入是否发生变化,并允许对已知的候选文本进行相等性测试,但不会持久化任何消息文本。任何能够读取该跟踪记录的人都可以对已知的候选提示词或工具集合进行相等性测试,因此应将跟踪文件与其旁边的、需主动开启的负载捕获文件采用相同的访问控制措施。

redacted 和 full 都始终执行凭据清理;它们之间唯一的区别是截断。该捕获复用了现有的 log_tool_io_truncate_bytes 上限,而不是引入第二个上限。将 log_llm_request_payload = "off" 设为该值,或保持其为该值,即可立即禁用捕获,无需重新部署。

磁盘存储格式

JSONL:每行一个事件,UTF-8,在 Unix 上权限为 0o600。热路径是非阻塞的:record_event 通过有界通道将序列化后的事件交给专用后台线程(zeroclaw-log-writer),然后立即返回。工作线程按固定周期调用 sync_all:每写入 100 次或每经过 1 秒的墙钟时间,以先到者为准,另外在正常关闭时通道关闭后还会进行一次最终的 sync_all。这用有界写入延迟换取了逐事件持久性(之前的同步行为):进程崩溃时,最多可能丢失一个同步间隔内尚未写入的数据。如果工作线程落后,record_event 会丢弃该事件并通过 tracing::warn! 发出警告,而不是阻塞 async 运行时。工作线程是每进程单例;通过 init_from_config 禁用并重新启用持久化时,会丢弃旧工作线程(通道关闭会触发其最终同步和线程退出),并启动一个新的工作线程。

行结构镜像 zeroclaw_log::event::LogEvent。顶层键:

键类型备注
idUUID v4 字符串持久化事件 ID。
@timestampRFC 3339 + 毫秒,UTC按字典序可排序;读取器会基于此进行排序。
severity_numberu8OTel:1 TRACE、5 DEBUG、9 INFO、13 WARN、17 ERROR。
severity_text字符串severity_number 的桶标签。
event.category字符串agent、channel、cron、memory、tool、provider、session、system 或 internal。
event.action字符串稳定标识符(llm_request、channel_message_inbound 等)。
event.outcomestring | omittedsuccess、failure、unknown(当为 unknown 时省略)。
service.name字符串Constant "zeroclaw".
service.version字符串正在运行的守护进程的 Crate 版本。
trace_id十六进制字符串 | 省略每轮关联。一个智能体轮次 = 一个 trace_id。
span_id十六进制字符串 | 省略回合内的子区间。
zeroclaw.*扁平字符串映射别名绑定归因(见下文)。
messagestring | omitted可读的行内容。
attributesobject | omitted自由格式的按操作负载。
schema_versionu8当前为 2。v1 行在启动时就地迁移。

zeroclaw.* 归属

Rust 的事实来源是 crates/zeroclaw-log/src/event.rs 中的 ATTRIBUTION_FIELDS + COMPOSITE_PREFIXES。/api/logs 响应以 attribution_keys 形式携带规范列表;请获取它,而不要硬编码。

普通字段(ATTRIBUTION_FIELDS)各自携带一个字符串。复合前缀会获得三个键:<prefix>、<prefix>_type、<prefix>_alias(例如 channel = "discord.glados"、channel_type = "discord"、channel_alias = "glados")。过滤器可以进行粗略匹配或精确匹配。

当某个跟踪调用将复合前缀字段设置为裸类型(不含 .)时,仅填充 _type 槽位,这样一来,在已携带完整 <type>.<alias> 复合值的 span 内部调用 tracing::*!(model_provider = name, …) 时,就不会在叶→根合并过程中覆盖它。

查询中

仪表板的日志页面是主要界面。在其下方:

GET /api/logs

顶层过滤器(查询参数):since_ts、until_ts、until_line_offset、until_segment_cursor、action、category、outcome、severity_min、trace_id、q(在 message 和 attributes 中进行子字符串匹配)、hide_internal(过滤掉 event.category = "internal" 的事件)、limit。旧版 until_id 字段仍可用,用于兼容基于时间戳或 ID 的游标。

每个其他 ?<key>=<value> 都被视为单个归因等值过滤器,网关会根据 is_attribution_field 验证键,并以 400 拒绝未知键。响应中包含 attribution_keys: string[],因此调用方无需猜测。

示例:

sh

# 自守护进程启动以来的所有 WARN+ 级别事件。
curl "$ZEROCLAW_GATEWAY/api/logs?severity_min=13"

# 特定代理的事件:
curl "$ZEROCLAW_GATEWAY/api/logs?agent_alias=glados"

# 单个机器人的 Discord 流量:
curl "$ZEROCLAW_GATEWAY/api/logs?channel=discord.glados"

# 单个智能体回合:
curl "$ZEROCLAW_GATEWAY/api/logs?trace_id=<value-from-a-prior-event>"

# 归属于单次 SOP 运行的所有已保留事件(跨其各步骤轮次):
curl "$ZEROCLAW_GATEWAY/api/logs?sop_run_id=<run-id>"

日志分页使用感知分段的游标向后遍历。当 at_end 为 false 时:

  1. Prefer next_segment_cursor,原样作为 until_segment_cursor 传回。将其视为不透明令牌:其内部结构取决于页面结束于哪个分段,并可能在版本之间变化。客户端必须逐字原样回传返回的字符串,而不得自行构造或解析。一旦页面中最早的事件已位于轮转归档中,这是唯一能够继续推进的游标。
  2. 当分段游标缺失时,回退使用 next_cursor_line_offset,并将其作为 until_line_offset 回传。它是活动文件中的纯字节偏移量;当最早的事件位于归档文件中时,其值为 null。
  3. 旧版 next_cursor: [timestamp, id] | null 响应仍保留以确保兼容性;将其作为 until_ts + until_id 回传的方式已被弃用,因为按字典序的 ID 决胜规则可能会静默跳过具有相同时间戳的事件。

在更改筛选条件后,从最新的页面重新开始。将 at_end: true 作为停止为该遍历请求更早页面的信号。

at_end 的作用范围仅限于守护进程实际能够读取的分段。当某个保留分段无法打开,或在文件中途停止解码(例如遇到无效的 UTF-8)时,系统会记录日志,未读取的部分不会包含在合并视图中,响应中会设置 incomplete: true。页面仍会返回,但此时 at_end 表示“在可读取的分段中没有更早的事件“,而不是“不存在更早的事件“。此类遍历应呈现为部分结果,而非完整结果。单个事件查询同理:若未找到事件,守护进程应说明该事件未找到,且部分保留历史不可读,因此该事件可能仍然存在,而不是直接返回 not found。无法解析的分段游标同样适用此规则:当其指向的锚点因所在分段无法打开或解码而无法验证时,历史末尾页同样携带 incomplete: true。旧版守护进程会省略该字段,其缺失应视为 false。

until_line_offset 是当前活动文件中的一个位置。归档轮转、启动迁移以及配置的路径变更都会替换它所指向的字节内容或活动文件,因此在这些边界之后,应从最新页重新开始。

until_segment_cursor 在这些边界上都能保持稳健,具体做法取决于页面结束的位置:

  • 以归档结尾的页面通过归档自身的标识进行寻址,该标识在归档写入时确定,且永不重新分配给不同的内容。因此,后续的轮转不会使其失效。如果保留策略此后已删除该归档,读取器会将历史报告为已结束,而不会悄悄地从一个无关的位置恢复。
  • 末尾位于活动文件中的分页会同时记录锚点事件 ID 和偏移量,因为活动文件的路径保持不变,而其内容会在每次轮转时被替换。恢复时,读取器会检查游标边界处的事件是否仍与锚点一致;若不一致,则在保留的分段中搜索该事件,并从其当前所在位置继续读取。这样,分页即可跨越轮转,既不会重复事件,也不会遗漏事件。如果锚定事件已完全不存在,读取器会将该历史记录报告为已结束。

旧版守护进程签发的游标仅以文件名标识日志段而不含锚点,此类游标仍会被接受。这种形式无法判断其指向的是已轮转的归档文件还是当前活动文件,因此读取器会先查找归档文件(归档文件的名称永远不会被重新分配),若未找到,再回退到活动文件。

The log 响应包含 persistence_enabled: boolean,用于区分“没有匹配的事件“与 runtime-trace 持久化已禁用的 daemon。/api/status 响应包含 daemon_started_at: string(RFC 3339),因此仪表盘无需额外请求即可默认显示“自 daemon 启动以来“的数据。

外部日志查看器

JSONL 模式是一种 OTel-logs + ECS 混合格式:@timestamp、severity_number + severity_text、event.{category,action,outcome}、service.{name,version}、attributes,外加 zeroclaw.* 厂商命名空间。大多数日志查看器无需转换或仅需少量转换即可读取它。请在下方示例中将 <install> 替换为你的安装目录的绝对路径(通常为展开后的 ~/.zeroclaw)。

Grafana Loki

Promtail 标签会提取 agent_alias、channel 和 severity_text,以便在 Grafana 中进行筛选:

scrape_configs:
  - 任务名称: zeroclaw
    静态配置:
      - 目标: [localhost]
        标签:
          工作: zeroclaw
          __path__: /data/state/runtime-trace.jsonl
    pipeline_stages:
      - json:
          表达式:
            代理: zeroclaw.agent_alias
            频道: zeroclaw.channel
            级别: severity_text
      - 标签:
          代理:
          频道:
          级别:
      - 时间戳:
          源: '@timestamp'
          格式: RFC3339

OpenTelemetry Collector

使用 observability-otel 构建时,优先使用原生 OTLP 接收器;无需解析或字段转换:

接收者:
  otlp:
    协议:
      http:
        端点: 127.0.0.1:4318

指向该监听器,设置 observability.otel_endpoint。现有的 filelog 接收器在默认构建中仍然是有用的后备方案,可用于回填保留的 JSONL 文件,或运行独立的文件传输程序:

接收者:
  filelog/zeroclaw:
    include: [/data/state/runtime-trace.jsonl]
    操作符:
      - 类型: json_parser
        时间戳:
          parse_from: attributes["@timestamp"]
          layout: '%Y-%m-%dT%H:%M:%S.%LZ'
        严重程度:
          parse_from: attributes.severity_number

Kibana / Elastic

数据采集可直接使用。严格的 ECS 管道要求使用 log.level 替代 severity_text。通过 Filebeat 采集管道将 severity_text 重命名为 log.level(并将 severity_number 重命名为 log.syslog.severity.code)即可弥补这一差异。@timestamp 和 event.{category,action,outcome} 已处于规范位置。

Vector / Fluent Bit

两者都使用 JSON 解析器阶段对 JSONL 进行 tail 操作;在发送到任何后端之前无需进行 schema 转换。

终端格式

守护进程的 stderr 格式化器会在每一行前添加最接近的、绑定了别名的标识:

  • 代理上下文 → [<agent_alias>]
  • 仅频道上下文(频道监听器,尚无代理)→ [<channel_composite>](例如 [discord.glados])
  • 否则 → [system]

跨度链如下:channel_listener{channel=discord.glados}: …。跨度字段内联可见。

架构迁移

启动时,如果启用了 log_persistence 且文件已存在,写入器会在首次追加之前,将所有 schema-1 行通过就地迁移流式转换为 schema-2。这是纯流式处理,无论文件大小如何,内存占用都限定在单行的分配范围内。迁移后的文件会通过原子重命名就位。已是 v2 的文件将保持不变。

如果迁移失败,守护进程会记录一条 warn 日志并继续写入 v2 追加数据;旧的 v1 行仍可被理解 v1 的工具读取,但无法通过 v2 读取器的反序列化器。

什么是 internal?

event.category = "internal" 用于归集运维噪声,即操作员默认无需在仪表板上看到的事件:心跳信号、空闲广播、有损同步重试等。仪表板的“隐藏内部事件“开关(默认开启)会过滤掉这些事件。

当某个高频事件的出现对取证分析很重要,但其缺失才是正常状态时,使用它。不要将它用作真正错误的流量调节器。

相关文件

  • crates/zeroclaw-log/src/event.rs:规范的 LogEvent 结构。
  • crates/zeroclaw-log/src/layer.rs:用于捕获每个 tracing::* 调用并将其馈送到流水线的 tracing-subscriber Layer。
  • crates/zeroclaw-log/src/macro.rs:record!、scope!、spawn!。
  • crates/zeroclaw-log/src/writer.rs:追加、滚动修剪和归档轮转。
  • crates/zeroclaw-log/src/reader.rs:/api/logs 读取器。
  • crates/zeroclaw-log/src/config.rs:StoragePolicy、ToolIoPolicy、ResolvedPolicy。
  • crates/zeroclaw-log/src/migrate.rs:schema-1 → schema-2 流式迁移。
  • crates/zeroclaw-log/src/observer_bridge.rs:面向 Prometheus / OTel 使用方的类型化 Observer 投影。
  • crates/zeroclaw-log/src/export_bridge.rs:供原生 OTLP 日志导出器使用的非阻塞规范事件桥接模块。
  • crates/zeroclaw-runtime/src/observability/otel_logs.rs:OTel Logs 数据模型映射与 OTLP/HTTP protobuf 导出器。
  • crates/zeroclaw-gateway/src/api_logs.rs:HTTP 适配器。

在信任本页文字之前,请先查阅源代码。