Gemini CLI 本地开发调试指南:基于 OpenTelemetry 的 Tracing 全链路观测与自定义埋点
Gemini CLI(gemini-cli)使用 OpenTelemetry(OTel)将模型调用、工具调度、工具执行等关键事件记录为 trace,帮助开发者在本地深度调试 Agent 行为。本文基于仓库中的 本地开发指南 展开,覆盖 Genkit、Jaeger、Google Cloud 三种 trace 查看方式的完整操作步骤,并结合 scripts/telemetry.js、packages/core/src/telemetry/trace.ts 等源码,讲解 runInDevTraceSpan 埋点函数的真实签名、内部机制与本地遥测环境的自动化管理原理。
一、Tracing 在 Gemini CLI 中的定位
Gemini CLI 的 trace 体系围绕 OTel 构建,自动覆盖以下几类关键事件:
- 模型调用(LLM Call):一次对模型服务的请求及其输入输出;
- 工具调度(Schedule Tool Calls):工具调度器(tool scheduler)对并行/串行工具调用的编排;
- 工具调用(Tool Call):单个工具的执行过程。
这些 trace 在启用 telemetry 后自动采集,无需手工开启。从源码结构看,trace 的语义遵循 OTel 的 GenAI 语义约定,核心属性常量定义在 packages/core/src/telemetry/constants.ts,包括:
// Gemini CLI 遵循的 GenAI 语义约定属性
export const GEN_AI_OPERATION_NAME = 'gen_ai.operation.name';
export const GEN_AI_AGENT_NAME = 'gen_ai.agent.name';
export const GEN_AI_INPUT_MESSAGES = 'gen_ai.input.messages';
export const GEN_AI_OUTPUT_MESSAGES = 'gen_ai.output.messages';
export const GEN_AI_CONVERSATION_ID = 'gen_ai.conversation.id';
// ...
同时,源码中用 GeminiCliOperation 枚举标识 span 的操作类型:
export enum GeminiCliOperation {
ToolCall = 'tool_call',
LLMCall = 'llm_call',
UserPrompt = 'user_prompt',
SystemPrompt = 'system_prompt',
AgentCall = 'agent_call',
ScheduleToolCalls = 'schedule_tool_calls',
}
这个枚举正是后文自定义埋点时 operation 字段的取值来源。
二、查看 Traces 的三种方式
本地开发时,trace 可以通过 Genkit Developer UI、Jaeger 或 Google Cloud 查看。三种方式共用同一个入口命令:
npm run telemetry -- --target=<genkit|local|gcp>
从 scripts/telemetry.js 的源码可以看到,--target 参数只接受 local、gcp、genkit 三个值(非法值会直接报错退出),未指定时回退到 .gemini/settings.json 中的 telemetry.target 配置,默认值为 local。脚本按 target 分发到三个独立实现:
| target | 实际执行脚本 | 后端 |
|---|---|---|
genkit |
scripts/telemetry_genkit.js | Genkit Developer UI |
local |
scripts/local_telemetry.js | Jaeger + OTel Collector |
gcp |
scripts/telemetry_gcp.js | OTel Collector → Google Cloud Trace |
以下依次说明三种方式的操作步骤。
2.1 方式一:Genkit Developer UI
Genkit 提供 Web UI,用于查看 trace 和其他遥测数据。
操作步骤:
-
启动 Genkit telemetry 服务:
npm run telemetry -- --target=genkit脚本会输出 Genkit Developer UI 的地址,例如
Genkit Developer UI: http://localhost:4000。 -
运行 Gemini CLI:在另一个终端执行:
gemini -
查看 traces:在浏览器打开 Genkit Developer UI 地址,切换到 Traces 标签页即可看到 trace 数据。
源码机制:scripts/telemetry_genkit.js 通过 npx -y genkit-cli start --non-interactive 拉起 Genkit 服务,并持续解析其 stdout 输出中的 Telemetry API running on http://... 行,动态把 OTLP 端点写为 <api-url>/api/otlp(HTTP 协议),再调用 manageTelemetrySettings 将 telemetry.otlpEndpoint 与 telemetry.otlpProtocol 同步写入工作区配置。也就是说,Genkit 端口可能是动态分配的,无需手工指定。
2.2 方式二:Jaeger(纯本地)
Jaeger 方案完全在本地运行,无需任何云端账号。
操作步骤:
-
启动遥测采集环境:
npm run telemetry -- --target=local该命令会自动下载并启动 Jaeger 和 OTel Collector,并输出 Jaeger UI 链接(通常为
http://localhost:16686)。- Collector 日志:
~/.gemini/tmp/<projectHash>/otel/collector.log
- Collector 日志:
-
运行 Gemini CLI:在另一个终端执行:
gemini -
查看 traces:命令执行后,在浏览器打开 Jaeger UI 链接查看 trace。
源码机制(scripts/local_telemetry.js):
- 首次运行时,脚本通过 scripts/telemetry_utils.js 中的
ensureBinary从上游 Release 下载otelcol-contrib与jaeger二进制,缓存到~/.gemini/tmp/<projectHash>/otel/bin/下(<projectHash>是项目根目录路径的 SHA-256 哈希),避免污染用户系统环境; - 生成一份 OTel Collector 配置(
collector-local.yaml):CLI 通过localhost:4317(OTLP/gRPC)上报,Collector 经 batch 处理后转发到 Jaeger 的localhost:14317,metrics 与 logs 走debugexporter 落到本地日志; - 依次启动 Jaeger(
--set=receivers.otlp.protocols.grpc.endpoint=localhost:14317)与 Collector,并通过waitForPort轮询 16686 / 4317 端口确认就绪; - 启动前会
pkill掉残留的otelcol-contrib、jaeger进程并删除旧日志,保证干净启动。
2.3 方式三:Google Cloud
可以通过本地 OTel Collector 将遥测数据转发到 Google Cloud Trace,用于自定义处理与路由。
注意:使用前需先完成 Google Cloud 遥测的前置条件(Project ID、认证、IAM 角色、相关 API 授权),详见 docs/cli/telemetry.md 中的 Prerequisites 小节。
操作步骤:
-
配置
.gemini/settings.json:{ "telemetry": { "enabled": true, "target": "gcp", "useCollector": true } } -
启动遥测 Collector:
npm run telemetry -- --target=gcp脚本会输出在 Google Cloud Console 中查看 traces、metrics、logs 的链接。
- Collector 日志:
~/.gemini/tmp/<projectHash>/otel/collector-gcp.log
- Collector 日志:
-
运行 Gemini CLI:在另一个终端执行:
gemini -
查看 Logs、Metrics 与 Traces:发送 prompt 后,在 Google Cloud Console 中查看数据。Logs、Metrics、Trace 各控制台的入口链接见 docs/cli/telemetry.md 中的 “View Google Cloud telemetry” 小节。
源码机制(scripts/telemetry_gcp.js):
- 该脚本强制要求环境变量
OTLP_GOOGLE_CLOUD_PROJECT(Project ID),未设置会直接报错退出;同时提示需要完成gcloud auth application-default login或配置GOOGLE_APPLICATION_CREDENTIALS,且账号需具备 "Cloud Trace Agent"、"Monitoring Metric Writer"、"Logs Writer" 角色; - 生成的 Collector 配置(
collector-gcp.yaml)使用googlecloudexporter:metrics 前缀为custom.googleapis.com/gemini_cli,日志写入名为gemini_cli的 Log; - Collector 监听
localhost:4317,脚本启动后依次输出 Google Cloud Console 的 Logs / Metrics / Traces 查询链接。
三、用 runInDevTraceSpan 给自己的代码打 trace
官方文档给出的核心 API 是 runInDevTraceSpan,用它把任意代码段包进一个 trace span,以获得更细粒度的执行流观测。
基本示例(源自 docs/local-development.md):
import { runInDevTraceSpan } from '@google/gemini-cli-core';
import { GeminiCliOperation } from '@google/gemini-cli-core/lib/telemetry/constants.js';
await runInDevTraceSpan(
{
operation: GeminiCliOperation.ToolCall,
attributes: {
[GEN_AI_AGENT_NAME]: 'gemini-cli',
},
},
async ({ metadata }) => {
// metadata 允许你记录操作的输入输出以及其他属性
metadata.input = { key: 'value' };
// 设置自定义属性
metadata.attributes['custom.attribute'] = 'custom.value';
// 需要被追踪的代码写在这里
try {
const output = await somethingRisky();
metadata.output = output;
return output;
} catch (e) {
metadata.error = e;
throw e;
}
},
);
各字段含义:
operation:span 的操作类型,取GeminiCliOperation枚举值(tool_call、llm_call、user_prompt、system_prompt、agent_call、schedule_tool_calls);metadata.input:(可选)被追踪操作的输入数据对象;metadata.output:(可选)被追踪操作的输出数据对象;metadata.attributes:(可选)附加到 span 上的自定义属性记录;metadata.error:(可选)操作失败时记录的错误对象。
结合源码补充的真实签名(packages/core/src/telemetry/trace.ts):
export async function runInDevTraceSpan<R>(
opts: SpanOptions & {
operation: GeminiCliOperation; // span 的操作类型
logPrompts?: boolean; // 是否记录 prompt 级别的输入/输出
sessionId: string; // 会话 ID,自动映射为 gen_ai.conversation.id
tracesEnabled?: boolean;
},
fn: ({ metadata }: { metadata: SpanMetadata }) => Promise<R>,
): Promise<R>
可以看到,除了文档示例中的 operation 与 attributes,实际签名还接受 sessionId(自动注入为 gen_ai.conversation.id 属性,使同一会话的 span 可关联)、logPrompts(控制是否写入 gen_ai.input.messages / gen_ai.output.messages)以及 tracesEnabled(总开关)。
埋点内部行为(同样来自 trace.ts):
- span 启动即注入基础属性:
gen_ai.operation.name、gen_ai.agent.name(固定为gemini-cli)、gen_ai.agent.description、gen_ai.conversation.id——所以即使不手工设置 agent 属性,span 也会带上这些 GenAI 标准字段; - 闭包结束时,
metadata.input/metadata.output会被truncateForTelemetry截断(默认 10000 字符上限,超出部分替换为...[TRUNCATED: original length N]),避免超长内容撑爆存储; metadata.error存在时,span 状态置为ERROR并调用span.recordException记录异常;否则状态置为OK;- 当
tracesEnabled为假时,仅保留操作名、agent 名等基础属性,输入输出与自定义属性不会被写入; - 若
fn返回的是 AsyncIterable(流式结果),span 不会在函数返回时立即结束,而是包装一层异步迭代器,在流消费完毕(或抛错)时结束 span;并通过FinalizationRegistry兜底,确保流对象被 GC 时 span 也能正常关闭,不会泄漏。
这套机制意味着:自定义埋点只需专注业务逻辑本身,输入输出截断、错误状态标记、流式结束时机均由框架统一处理,与内置的模型调用、工具调用 span 保持同一套语义约定,在 Jaeger 或 Genkit UI 中可以按会话 ID 串联整条执行链。
四、本地遥测环境的自动化管理原理
三种 target 的脚本共享 scripts/telemetry_utils.js 中的两个关键能力,理解它们有助于排查本地调试问题:
1. 工作区 settings.json 的自动改写与还原
manageTelemetrySettings 在启动时改写工作区 .gemini/settings.json:
- 设置
telemetry.enabled = true、telemetry.otlpEndpoint(如http://localhost:4317)、telemetry.target(local/gcp)与telemetry.otlpProtocol(grpc/http); - 把
sandbox临时置为false——因为在沙箱内运行时网络栈受限,本地 OTLP 端口可能不可达;退出时registerCleanup会还原原 sandbox 配置并删除上述临时字段; - 因此
npm run telemetry运行期间,工作区配置会被脚本管理,Ctrl+C 退出后自动恢复原状。
2. 进程与日志的生命周期管理
registerCleanup 注册 exit / SIGINT / SIGTERM / uncaughtException 四类钩子:退出时向 Jaeger、Collector、Genkit 等子进程发送 SIGTERM,关闭日志文件描述符,并把 settings.json 恢复干净。所有二进制、日志、临时配置统一收敛在 ~/.gemini/tmp/<projectHash>/otel/ 目录下,互不干扰。
排障小贴士:
- trace 没出现时,先确认另一个终端的
npm run telemetry仍在前台运行且 4317 端口监听正常,再tail -f对应的collector.log(或collector-gcp.log)查看 OTLP 接收情况; - 二进制首次下载需要访问上游 Release 仓库,若网络受限可先手工确认
~/.gemini/tmp/<projectHash>/otel/bin/下是否已有otelcol-contrib/jaeger; - 端口被占用或残留进程导致的启动失败,脚本启动前已内置
pkill清理逻辑,通常重跑命令即可恢复。
五、小结
- 本地查看 trace 的完整链路是:
npm run telemetry -- --target=<genkit|local|gcp>拉起后端 → 另一终端运行gemini→ 在对应 UI(Genkit Traces 页 / Jaeger UI / Google Cloud Console)中查看; - 三种 target 分别适合快速查看(Genkit)、零依赖本地全量调试(Jaeger)和接入企业级 Cloud 观测体系(GCP);
- 需要更深观测时,用
runInDevTraceSpan包裹目标代码段,配合GeminiCliOperation枚举与metadata对象即可产出自定义 span,框架自动处理属性注入、内容截断、错误标记与流式 span 生命周期; - 更多遥测参数(OTLP 端点、协议、导出目标等)可参考 docs/cli/telemetry.md 与 schemas/settings.schema.json 中的
telemetry字段定义。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00