首页
/ Gemini CLI 本地开发调试指南:基于 OpenTelemetry 的 Tracing 全链路观测与自定义埋点

Gemini CLI 本地开发调试指南:基于 OpenTelemetry 的 Tracing 全链路观测与自定义埋点

2026-09-06 12:47:34作者:谭伦延

Gemini CLI(gemini-cli)使用 OpenTelemetry(OTel)将模型调用、工具调度、工具执行等关键事件记录为 trace,帮助开发者在本地深度调试 Agent 行为。本文基于仓库中的 本地开发指南 展开,覆盖 Genkit、Jaeger、Google Cloud 三种 trace 查看方式的完整操作步骤,并结合 scripts/telemetry.jspackages/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 参数只接受 localgcpgenkit 三个值(非法值会直接报错退出),未指定时回退到 .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 和其他遥测数据。

操作步骤:

  1. 启动 Genkit telemetry 服务

    npm run telemetry -- --target=genkit
    

    脚本会输出 Genkit Developer UI 的地址,例如 Genkit Developer UI: http://localhost:4000

  2. 运行 Gemini CLI:在另一个终端执行:

    gemini
    
  3. 查看 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 协议),再调用 manageTelemetrySettingstelemetry.otlpEndpointtelemetry.otlpProtocol 同步写入工作区配置。也就是说,Genkit 端口可能是动态分配的,无需手工指定。

2.2 方式二:Jaeger(纯本地)

Jaeger 方案完全在本地运行,无需任何云端账号。

操作步骤:

  1. 启动遥测采集环境

    npm run telemetry -- --target=local
    

    该命令会自动下载并启动 Jaeger 和 OTel Collector,并输出 Jaeger UI 链接(通常为 http://localhost:16686)。

    • Collector 日志~/.gemini/tmp/<projectHash>/otel/collector.log
  2. 运行 Gemini CLI:在另一个终端执行:

    gemini
    
  3. 查看 traces:命令执行后,在浏览器打开 Jaeger UI 链接查看 trace。

源码机制scripts/local_telemetry.js):

  • 首次运行时,脚本通过 scripts/telemetry_utils.js 中的 ensureBinary 从上游 Release 下载 otelcol-contribjaeger 二进制,缓存到 ~/.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 走 debug exporter 落到本地日志;
  • 依次启动 Jaeger(--set=receivers.otlp.protocols.grpc.endpoint=localhost:14317)与 Collector,并通过 waitForPort 轮询 16686 / 4317 端口确认就绪;
  • 启动前会 pkill 掉残留的 otelcol-contribjaeger 进程并删除旧日志,保证干净启动。

2.3 方式三:Google Cloud

可以通过本地 OTel Collector 将遥测数据转发到 Google Cloud Trace,用于自定义处理与路由。

注意:使用前需先完成 Google Cloud 遥测的前置条件(Project ID、认证、IAM 角色、相关 API 授权),详见 docs/cli/telemetry.md 中的 Prerequisites 小节。

操作步骤:

  1. 配置 .gemini/settings.json

    {
      "telemetry": {
        "enabled": true,
        "target": "gcp",
        "useCollector": true
      }
    }
    
  2. 启动遥测 Collector

    npm run telemetry -- --target=gcp
    

    脚本会输出在 Google Cloud Console 中查看 traces、metrics、logs 的链接。

    • Collector 日志~/.gemini/tmp/<projectHash>/otel/collector-gcp.log
  3. 运行 Gemini CLI:在另一个终端执行:

    gemini
    
  4. 查看 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)使用 googlecloud exporter: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_callllm_calluser_promptsystem_promptagent_callschedule_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>

可以看到,除了文档示例中的 operationattributes,实际签名还接受 sessionId(自动注入为 gen_ai.conversation.id 属性,使同一会话的 span 可关联)、logPrompts(控制是否写入 gen_ai.input.messages / gen_ai.output.messages)以及 tracesEnabled(总开关)。

埋点内部行为(同样来自 trace.ts):

  1. span 启动即注入基础属性:gen_ai.operation.namegen_ai.agent.name(固定为 gemini-cli)、gen_ai.agent.descriptiongen_ai.conversation.id——所以即使不手工设置 agent 属性,span 也会带上这些 GenAI 标准字段;
  2. 闭包结束时,metadata.input / metadata.output 会被 truncateForTelemetry 截断(默认 10000 字符上限,超出部分替换为 ...[TRUNCATED: original length N]),避免超长内容撑爆存储;
  3. metadata.error 存在时,span 状态置为 ERROR 并调用 span.recordException 记录异常;否则状态置为 OK
  4. tracesEnabled 为假时,仅保留操作名、agent 名等基础属性,输入输出与自定义属性不会被写入;
  5. 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 = truetelemetry.otlpEndpoint(如 http://localhost:4317)、telemetry.targetlocal / gcp)与 telemetry.otlpProtocolgrpc / 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.mdschemas/settings.schema.json 中的 telemetry 字段定义。
登录后查看全文
热门项目推荐
相关项目推荐