IronClaw 零开销延迟追踪宏:ironclaw_observability 的设计契约与实现剖析
2026/9/24 13:45:58 网站建设 项目流程
  • 人工智能
  • AI 应用
  • 交互助手
  • AI Agent

【免费下载链接】ironclaw

IronClaw is an Agent OS focused on privacy, security and extensibility

项目地址:https://gitcode.com/gh_mirrors/iro/ironclaw
点击查看免费下载

ironclaw_observability是 IronClaw(一个以隐私、安全与可扩展性为目标的 Agent OS)中负责延迟追踪的 substrate 层组件:它只提供一组覆盖ironclaw_latencytracing target 的宏与辅助函数,在追踪目标关闭时零开销。本文以该 crate 的 CLAUDE.md 为骨架,结合其 src/lib.rs、Cargo.toml 与七个消费方源码,完整讲解公共 API、零成本关闭原理、"依赖数量即契约"的边界设计,以及如何用两条测试守住这些不变量——读完你既能直接上手埋点,也能理解这套"小到用依赖列表当执行机制"的架构治理思路。

一、职责边界:这是"宏 + 宏需要的辅助",仅此而已

1.1 Charter:可以用测试来检验的定位

CLAUDE.md 开头用一句话定义了该 crate 的宪章(charter):

Everything here is either a macro or a helper the macros need. (这里的一切,要么是宏,要么是宏需要的辅助函数。)

这句话被刻意写成"一条可以随时套用的测试":任何想加入这个 crate 的代码,都必须先回答"它是宏,还是宏的辅助?"如果都不是,就不属于这里。对应的目标架构条目是 PROPOSAL §6.2.5(families/substrates.md)。

1.2 公共表面(Public Surface)

crate 对外暴露的完整清单为:

  • 宏:live_latency_trace!live_latency_trace_ok!live_latency_trace_error!
  • 函数:elapsed_mslive_latency_enabledlive_latency_started_at
  • 再导出:pub use tracing(一个刻意的宏卫生权衡,见下文)

Never contains(明确不允许放入)

  • state(状态)
  • policy(策略)
  • sinks(接收端/导出端)
  • 最容易写错的一条:一个仅仅"产生某个 trace 恰好会记录的值"的函数——这个测量动作属于"被测量的东西的生产者",不属于本 crate。文档原文强调:That measurement belongs to whoever produces the thing being measured.

从源码结构看,src/lib.rs 是全 crate 唯一的源文件(约 100 行,含内联测试),代码量极小,这本身就是宪章的执行结果:没有地方可以藏下"state、policy、sinks"。

1.3 一个依赖,整个 crate 的边界

Cargo.toml 中依赖区只有一项:

[dependencies] # One dependency, deliberately. The macros expand to `tracing`; anything that # would add a second dependency here is a measurement that belongs to its # producer, not to this crate. See AGENTS.md. tracing = "0.1"

注意publish = false,这是一个仅供工作区内共享的私有 crate;其注释直接声明了"唯一个依赖是刻意为之"的立场。

二、公共 API 全解:三个宏与三个辅助函数

2.1 核心宏:live_latency_trace!

最底层的宏是live_latency_trace!,它只是把调用转发到tracingtrace!,并固定 target 为ironclaw_latency

#[macro_export] macro_rules! live_latency_trace { ($($fields:tt)*) => { $crate::tracing::trace!(target: "ironclaw_latency", $($fields)*) }; }

关键点:展开时通过$crate::tracing::trace!调用,而不是裸写tracing::trace!。配合pub use tracing;(lib.rs 第 13 行),消费方在使用这些宏时不需要自己引入tracing依赖或use tracing,这就是文档所说的"宏卫生权衡(macro-hygiene tradeoff)":宏在展开时借助$crate前缀解析到本 crate 再导出的tracing,从而把对tracing的依赖完全收敛到这一个 crate 内。

2.2 成功/失败语义:live_latency_trace_ok! 与 live_latency_trace_error!

两个带语义的宏把componentoperationelapsed_msoutcome("ok"/"error")作为统一字段注入,其中live_latency_trace_error!还额外注入error_kind

#[macro_export] macro_rules! live_latency_trace_ok { ($component:expr, $operation:expr, $started_at:expr, $($fields:tt)*) => { if let Some(started_at) = $started_at { let elapsed_ms = $crate::elapsed_ms(started_at); $crate::live_latency_trace!( component = $component, operation = $operation, elapsed_ms, outcome = "ok", $($fields)* ); } }; }

两个宏都接受$started_at: Option<Instant>:当传入None(即目标未启用)时,整个宏体是 no-op,一行 trace 都不发。elapsed_ms是在宏内部计算的,消费方无需自行计时。error变体结构相同,只是多一个error_kind = $error_kind字段并把outcome置为"error"(见 lib.rs)。

2.3 三个辅助函数

#[inline] pub fn elapsed_ms(started_at: Instant) -> u64 { started_at.elapsed().as_millis().try_into().unwrap_or(u64::MAX) } #[inline] pub fn live_latency_enabled() -> bool { tracing::enabled!(target: "ironclaw_latency", tracing::Level::TRACE) } #[inline] pub fn live_latency_started_at() -> Option<Instant> { live_latency_enabled().then(Instant::now) }
  • elapsed_ms:把Instant差值换算为毫秒;u128 → u64可能溢出的极端情形下饱和到u64::MAX而不是回绕(原因见第五节测试)。
  • live_latency_enabled:对ironclaw_latencytarget 的 TRACE 级别做tracing::enabled!静态/动态检查。
  • live_latency_started_at:target 启用时返回Some(Instant::now()),否则返回None——这是"零成本关闭"的入口。

2.4 一个最小可用示例

把上述 API 组合起来,一次带语义的计时埋点长这样(结合 host_runtime 的实际用法归纳):

use ironclaw_observability::{live_latency_enabled, live_latency_started_at, live_latency_trace_ok}; let started_at = live_latency_started_at(); // 目标关闭时是 None,后续零成本 // ... 执行被计时的操作 ... live_latency_trace_ok!("my_component", "my_operation", started_at, key = "value", /* 其余自定义字段 */);

成功/失败分支则分别在操作结束时调用live_latency_trace_ok!/live_latency_trace_error!,失败时附上error_kind

三、"零成本关闭"原理,以及调用方必须承担的那一半

3.1 覆盖的是 trace,不是 fields

live_latency_started_at()在 target 关闭时返回None,而每个宏遇到None都是 no-op——这保证了trace 的发射零成本。但文档明确划出一条边界:

That covers thetrace, not thefields: a caller that computes an expensive field before checking is paying for it with tracing off.

也就是说:如果一个调用方在检查之前就计算了一个昂贵的字段(比如序列化整个 JSON 入参、统计字节数),那么即使 trace 不发射,这个计算成本也已经付出了。要守卫的是计算本身,而不只是发射动作。

3.2 守卫计算的正确姿势:ironclaw_host_runtime 的形状

CLAUDE.md 明确推荐参考ironclaw_host_runtime::latency::RuntimeLatencyFields::from_json_input的模式:live_latency_enabled(),再测量。对应源码见 crates/kernel/ironclaw_host_runtime/src/latency.rs:

impl RuntimeLatencyFields { pub(crate) fn from_json_input( capability_id: &CapabilityId, scope: &ResourceScope, runtime: impl Into<String>, input: &serde_json::Value, ) -> Option<Self> { if !ironclaw_observability::live_latency_enabled() { return None; } Self::from_scope(capability_id, scope, runtime, json_value_bytes(input)) } // ... }

json_value_bytes是昂贵的序列化计数,因此必须先检查live_latency_enabled()再调用它;字段构建完成后整体包装成Option<RuntimeLatencyFields>,传入trace_runtime_ok/trace_runtime_error,这两个函数在fieldsNone时直接返回。这样,"目标关闭 → 不构建字段 → 不发 trace"整条链路都是惰性的。

3.3 生产中的完整调用链

在 crates/kernel/ironclaw_host_runtime/src/production.rs 中可以看到真实用法:入口处let total_started_at = live_latency_started_at();let dispatch_started_at = live_latency_started_at();各取一次起点,操作结束时分别走live_latency_trace_ok!/live_latency_trace_error!分支;process_executor.rs 里同样是"先取started_at,末尾按结果选择 ok/error 宏"。这是贯穿全部消费方的标准姿势:早点取起点(惰性),晚点发 trace(一次性)。

四、"依赖数量即契约":serde_json 驱逐始末

4.1 一个伪装成观测助手的函数

这个 crate 曾经有第二个依赖:serde_json,用途只有一个函数json_value_bytes——计算一个 JSON 值的序列化大小。它读起来像个观测助手,但不是:在ironclaw_extension_support的五个调用点中,有三个是喂给ResourceUsage::set_output_bytes的——那是资源记账(resource accounting),而不是 trace 字段。

4.2 共享它买不来任何不变量

进一步分析发现,共享这个函数并没有带来不变量:output_bytes在生产中本就有三种不同的测量方式——

  1. 上述字节计数器(曾在此 crate 中);
  2. output.stdout.len()(在ironclaw_scripts);
  3. Value::to_string().len()(在ironclaw_loop_host)。

原因正如文档所述:每个生产者测量的是"自己生产的东西"(each producer measures whatitproduced),让所有人共享一个计数函数并不能让它们的结果一致,反而给本应轻量的宏 crate 背上一个所有消费方都会继承的serde_json依赖。最终(对应 WS6、PROPOSAL §12.12 D-K)该函数被移到了它的两个消费者那里,serde_json也随之离开。

4.3 迁移后的落点与源码佐证

被驱逐函数的两个消费者之一就是ironclaw_host_runtime,如今它以私有函数形式存在于 crates/kernel/ironclaw_host_runtime/src/latency.rs,且文档注释完整记录了这段历史("Sharing the function bought no invariant and cost the latency macro crate aserde_jsondependency every one of its consumers inherited")。它用JsonByteCounter(实现std::io::Writesaturating_add防溢出)在不物化字节的前提下统计序列化大小,并约定"序列化失败返回 0,trace/记账字段绝不因自身失败而拖垮调用方"。

4.4 裁决的边界条件:两份副本是上限

这条裁决不是无条件的,条件被明确写下来以便"被检查而不是被重吵":

It holds attwocopies. If a third consumer needs that byte counter, the duplication argument flips and D-K should be revisited.

即:当前两份本地副本(ironclaw_host_runtimeironclaw_extension_support)是保持现状的前提;如果出现第三个需要字节计数器的消费者,复制(duplication)论证就反转了——届时应当重新讨论 PROPOSAL §12.12 D-K,既不能简单地再加第三份拷贝,也不能把函数搬回ironclaw_observability。决策记录中还列出了被考虑并否决的替代归宿:ironclaw_common(重构正在主动收窄的 crate)和ironclaw_host_api(已被批评"携带行为"的 contracts 叶子)。

4.5 一句话总结这条 tripwire

如果此处的一个改动需要引入第二个依赖,那就说明这个新增的东西不是本 crate 的职责。

依赖列表因此成为执行机制(enforcement mechanism),而这份文档只是解释。

五、七个消费方:依赖传播就是约束力

5.1 消费方清单

按 2026-08-05 实测,共有七个 crate 依赖ironclaw_observability

  • ironclaw_filesystem(scoped.rs 中直接use ironclaw_observability::live_latency_started_at;
  • ironclaw_host_runtime(latency.rs、production.rs、egress/pipeline.rs、services/process_executor.rs)
  • ironclaw_loop_host(lib.rs、model_gateway.rs)
  • ironclaw_turn_runner(loop_driver_host.rs、turn_run_executor.rs)
  • ironclaw_turns(coordinator.rs、host_managed_ports/prompt.rs)
  • ironclaw_composition(runtime/latency.rs、capability_authorization.rs)
  • ironclaw_extension_support(latency.rs、coding/mod.rs)

5.2 为什么"每个消费方都会继承依赖"本身就是约束

CLAUDE.md 的"Consumers"一节点出要害:Every one of them gets whatever this crate depends on, which is the whole reason the dependency list is the enforcement mechanism and this file is only the explanation.——七个 crate 全部继承本 crate 的依赖,所以任何试图往这里塞"需要第二个依赖的功能"的改动,都会立刻被依赖图放大为七个 crate 的依赖膨胀,这正是把依赖列表当作执行机制的原因。文档(CLAUDE.md / AGENTS.md)只是"解释",Cargo.toml才是"机械化的宪章"(the manifest is the charter made mechanical)。

此外,ironclaw_agent_loop的 executor/latency.rs 也使用了同样的target: "ironclaw_latency"+ TRACE 级别模式,说明ironclaw_latency是工作区内统一的延迟追踪 target 命名约定。

六、测试:两条用例守住全部不变量

运行方式(README.md):

cargo test -p ironclaw_observability # 2 tests: elapsed_ms clamps; disabled without a subscriber

两条测试恰好各押住一个核心性质(见 lib.rs 测试模块):

  1. elapsed_ms_saturates_instead_of_wrapping:验证elapsed_ms在极端时间差下饱和(clamp)而不是回绕(wrap)。注释点破了原因:回绕的时长会被读成一次"飞快的操作"(a wrapped duration reads as afastoperation),这在延迟追踪里是灾难性的误报——慢操作显示成 0ms。测试构造了一个 1.5 秒前的Instant,断言结果>= 1500,同时断言刚创建的Instant计为 0。
  2. started_at_is_none_when_the_latency_target_is_off:测试二进制中未安装任何 subscriber,因此ironclaw_latency的 TRACE target 是关闭的——断言live_latency_enabled()falselive_latency_started_at()None,直接验证"无 subscriber 即零成本关闭"这一整个 crate 存在的前提。

配套地,消费者侧也有对应测试守护:例如 host_runtime/latency.rs 用json_value_bytes_matches_serialized_value_length验证字节计数器与serde_json::to_vec长度一致,用json_byte_counter_saturates_on_write验证计数器u64饱和——两处都延续了"宁可饱和、不可回绕"的记账哲学。

七、什么时候用它,什么时候明确不用它

结合 README.md 的 "Use this when / Don't use this when" 与 AGENTS.md 的边界说明,给出决策清单:

应当使用:任何想要live_latency_trace!风格计时的 crate——在ironclaw_latencytarget 关闭期间零成本,且不需要额外引入tracing依赖(宏展开走$crate再导出的 facade)。

明确不要用

  • 你是在产生一个 trace 恰好会记录的(字节数、大小等)→ 该测量属于"被测量的东西的生产者"(PROPOSAL §12.12 D-K),就近放在自己的 crate 里;
  • 你需要 sinks、exporters、state → 本 crate 中不存在这类东西,去其他适合的层寻找;
  • 你的改动会让本 crate 出现第二个依赖→ 先停下来读 §12.12 D-K 的历史裁决,大概率这个改动不属于这里。

结语

ironclaw_observability用约 100 行代码示范了一种可复制的架构治理:把"一个依赖"写成机械化的契约(manifest 注释),把"边界故事"写成可检查的文档(AGENTS.md / CLAUDE.md),把"不变量"写成两条针对性测试。它既解决了七个消费方统一延迟埋点、免去各自引入tracing的实际问题,又用"serde_json 驱逐案"证明了——观测类 crate 最容易犯的错,就是把"测量"误当成"观测",而正确的答案始终是:测量属于生产者,宏 crate 只负责把它记录成 trace。

继续深挖可参考:本 crate 的 CLAUDE.md(本文依据,含完整裁决叙述)、AGENTS.md(决策记录的规范性版本)、lib.rs(全部实现),以及消费方代表 host_runtime/latency.rs(字段守卫与字节计数器的迁移落点)。

  • 人工智能
  • AI 应用
  • 交互助手
  • AI Agent

【免费下载链接】ironclaw

IronClaw is an Agent OS focused on privacy, security and extensibility

项目地址:https://gitcode.com/gh_mirrors/iro/ironclaw
点击查看免费下载

相关推荐

上一篇:PasteBar:免费开源的跨平台剪贴板管理器,彻底释放你的复制粘贴效率
下一篇:推荐开源项目:Milligram - 极简主义的CSS框架

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

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

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

立即咨询