CMDC.Telemetry (cmdc v0.5.0)

Copy Markdown View Source

CMDC 标准 :telemetry 事件契约。

CMDC 核心库只广播事件,不内嵌任何 sink;用户可挂任意 handler 到 Langfuse / LangSmith / Grafana Tempo / Datadog / Splunk 等。

设计原则

  1. 零强制依赖:telemetry 已是 BEAM 生态事实标准,CMDC 仅广播
  2. 事件命名稳定:锁定 schema,向后兼容
  3. metadata 白名单:明确列出 metadata 字段,避免泄露敏感数据
  4. 与现有 EventBus 互补:EventBus 走 PubSub 给业务订阅者, Telemetry 走 handler 给可观测性栈

事件清单(v0.5 共 16 事件 = 6 旧 + 10 新)

v0.4 已有 6 事件(走 EventBus → bridge_event/2 桥接)

事件名触发时机metadata
[:cmdc, :agent, :turn, :start]Agent 单 turn 开始session_id, turn
[:cmdc, :agent, :turn, :stop]Agent 单 turn 结束session_id, turn, duration_ms, outcome
[:cmdc, :llm, :request, :start]LLM 调用前session_id, turn, model
[:cmdc, :llm, :request, :stop]LLM 调用后session_id, turn, model, tokens_in, tokens_out, duration_ms
[:cmdc, :tool, :exec, :start]工具开始执行session_id, tool, call_id
[:cmdc, :tool, :exec, :stop]工具结束session_id, tool, call_id, duration_ms, error?

v0.5 新增 10 事件(直接 :telemetry.execute 埋点,不经 EventBus)

这一类事件不走 EventBus——它们是 system-wide 可观测性事件,不是业务订阅事件, 避免 per-session 订阅者产生噪音。Studio / Hive 等集成方 attach 这些事件接入 Langfuse / Datadog / Prometheus 即可。

事件名触发时机measurements / metadata
[:cmdc, :plugin, :pipeline, :start]Plugin Pipeline 整个 hook 开始%{system_time} / %{session_id, hook, plugin_count}
[:cmdc, :plugin, :pipeline, :stop]Plugin Pipeline 整个 hook 完成(hook 粒度,非 per-plugin%{duration_ms} / %{session_id, hook, halted_by, action}
[:cmdc, :plugin, :crash]单个 plugin handle_event 抛出异常(已被 Pipeline rescue)%{count: 1} / %{session_id, plugin, hook, kind, reason}
[:cmdc, :compactor, :run, :start]Compactor 开始压缩%{system_time, tokens_before} / %{session_id}
[:cmdc, :compactor, :run, :stop]Compactor 完成%{duration_ms, tokens_after, ratio} / %{session_id, strategy, outcome}
[:cmdc, :checkpoint, :save, :stop]Checkpoint.save/3 完成%{duration_ms, snapshot_bytes} / %{session_id, backend, label, outcome}
[:cmdc, :checkpoint, :load, :stop]Checkpoint.load/2 完成%{duration_ms, snapshot_bytes, hit?} / %{session_id, backend, outcome}
[:cmdc, :subagent, :spawn, :start]SubAgent.Supervisor 启动子 Agent%{system_time} / %{parent_session_id, sub_session_id, name}
[:cmdc, :subagent, :spawn, :stop]子 Agent 结束%{duration_ms} / %{parent_session_id, sub_session_id, outcome}
[:cmdc, :agent, :hibernate, :configured]Agent init 时配置了 hibernate_after_ms%{ms} / %{session_id, enabled?}

注意:OTP :gen_statem:hibernate_after 是被动行为,无原生 enter/wake 事件挂钩, 因此 v0.5 只暴露 :configured 一次性事件(启动时 emit),用于观测启用率。 真实 hibernate 命中率请通过 Process.info(pid, :memory) 周期采样推算。

与 :tool_execution_metrics 业务事件的关系

:tool_execution_metrics 通过 EventBus 广播业务事件(per-session 订阅)。 Telemetry 通过 :telemetry.execute/3 广播 [:cmdc, :tool, :exec, :*] 全局 事件(system-wide 监控)。两者数据字段一致,触发点合并在 Agent 工具执行器内。

实现说明

  • emitter 桥接模式:Agent 内部广播器在广播 EventBus 事件的同时, 把 6 个核心事件映射到 telemetry(无需改 ToolRunner / Provider)
  • schema 版本号:所有事件 metadata 含 schema_version: 1
  • 用户用 :telemetry.attach/4CMDC.Telemetry.attach_logger/0 接入

Quick Start

# 1. attach 默认 logger(开发期)
CMDC.Telemetry.attach_logger()

# 2. attach 自定义 handler(接入 Langfuse)
:telemetry.attach_many(
  "my-langfuse-handler",
  CMDC.Telemetry.all_events(),
  &MyApp.LangfuseHandler.handle/4,
  nil
)

# 3. detach
:telemetry.detach("my-langfuse-handler")

完整可观测性配方见 docs/recipes/observability/(v0.4.0 提供 Langfuse 示例)。

Summary

Functions

返回 CMDC.Telemetry 所有事件列表。

附加一个开发期 Logger handler,把所有 CMDC telemetry 事件打到 console。

从 EventBus 事件桥接到 telemetry。

Detach Logger handler。

执行 telemetry 事件 — 内部桥接使用。

返回 telemetry schema 版本号。

Functions

all_events()

@spec all_events() :: [[atom()]]

返回 CMDC.Telemetry 所有事件列表。

attach_logger(opts \\ [])

@spec attach_logger(keyword()) :: :ok | {:error, term()}

附加一个开发期 Logger handler,把所有 CMDC telemetry 事件打到 console。

生产环境请用 :telemetry.attach_many/4 接入实际可观测性栈。

bridge_event(session_id, event)

@spec bridge_event(String.t(), term()) :: :ok

从 EventBus 事件桥接到 telemetry。

Agent.Emitter.broadcast/2 调用。映射规则:

EventBus 事件Telemetry 事件
{:agent_start, session_id, turn}[:cmdc, :agent, :turn, :start]
{:agent_end, payload} (success)[:cmdc, :agent, :turn, :stop]
{:llm_request_start, ...}[:cmdc, :llm, :request, :start]
{:llm_response, payload}[:cmdc, :llm, :request, :stop]
{:tool_started, name, call_id, ...}[:cmdc, :tool, :exec, :start]
{:tool_ended, name, call_id, ...}[:cmdc, :tool, :exec, :stop]
{:tool_execution_metrics, ...}[:cmdc, :tool, :exec, :stop](兼容路径)

其他事件不映射(继续走 EventBus)。

detach_logger(opts \\ [])

@spec detach_logger(keyword()) :: :ok | {:error, :not_found}

Detach Logger handler。

execute(event_name, measurements, metadata)

@spec execute([atom()], map(), map()) :: :ok

执行 telemetry 事件 — 内部桥接使用。

自动加 schema_version: 1 到 metadata。

schema_version()

@spec schema_version() :: pos_integer()

返回 telemetry schema 版本号。