跳转至

可观测性

面向维护者。正在使用 T3 Code?请参阅 docs/user。

T3 Code 只有一种服务端可观测性模型:

  • 人类可读的彩色日志输出到 stdout
  • 已完成的 span 写入本地 NDJSON 追踪文件
  • 追踪、指标和日志也可以通过 OTLP 导出到 Grafana LGTM 等真实后端

本地追踪文件是常规本地启动的持久化事实来源。这些启动不会写入单独的服务器日志文件,但 SSH 管理的启动还会将远程进程的 stdout/stderr 持久化到 ~/.t3/ssh-launch/<state>/server.log。

在哪里查找内容

日志

日志面向人类:

  • 目标:stdout
  • 格式:Logger.consolePretty()
  • 常规本地持久化:无
  • SSH 管理启动持久化:~/.t3/ssh-launch/<state>/server.log
  • 远程导出:仅通过 OTLP,在配置后生效

如果希望日志消息出现在追踪文件中,请在活动的 span 内使用 Effect.log... 发出它。Logger.tracerLogger 会将其附加为 span 事件。

配置日志端点会接管这项工作。服务器随后导出日志记录,涵盖所有消息而不只是活动 span 内的消息,并携带 trace 和 span ID,以便仍然与追踪对应。在该模式下会弃用 Logger.tracerLogger,因此同一条消息不会导出两次,追踪文件也不再携带日志消息。无论哪种方式,stdout 输出和 SSH 管理的启动持久化都保持不变。

追踪

已完成的 span 以 NDJSON 记录形式写入 serverTracePath。默认位置取决于服务器的启动方式:生产环境和显式配置的主目录使用 <home>/userdata/logs/server.trace.ndjson(默认即 ~/.t3/userdata/...,或使用 --home-dir /custom/path 时为 /custom/path/userdata/...),链接工作树的开发运行使用 <worktree>/.t3/userdata/logs/server.trace.ndjson,而链接工作树之外的隐式开发运行使用 ~/.t3/dev/logs/server.trace.ndjson。

两种记录类型共有的重要字段:

  • type:effect-span 或 otlp-span
  • name:span 名称
  • traceId、spanId、parentSpanId:关联
  • durationMs:已用时间
  • attributes:结构化上下文
  • events:嵌入的日志和自定义事件

effect-span 记录还包含 exit,值为 Success、Failure 或 Interrupted。otlp-span 记录则携带 OTLP 资源、作用域和可选的状态字段。

TraceRecord、EffectTraceRecord 和 OtlpTraceRecord schema 位于 packages/shared/src/observability.ts。

SQL 语句 span(名为 sqlite)和 sql.transaction span 携带 db.connection 和 db.lock_wait_ms。db.connection 为主连接时为 writer(运行在事件循环上), 或为工作线程中的只读连接时为 reader。db.lock_wait_ms 是 span 中等待该连接的时间部分, 排在其它 SQL 之后。该等待不会阻塞事件循环,因此在将慢 span 归咎于查询之前,请先减去它。事务内的语句使用其连接,并且不携带这些属性。

通过 HTTP 的 MCP 请求有一个 McpHttpServer.authenticateRequest span,仅覆盖准入过程: 凭据检查、模式限制和 Linked environments 门禁。请求在其之后运行,位于 McpServer.…/<method> span 下,每个工具调用都有各自的 mcp.tool.<name> span 来计时。该 span 的属性只标识工具名称,绝不包含其参数。

DPoP 证明失败包含安全的 environment.dpop.failure_code span 属性。time_window 失败意味着签名证明对于环境服务器允许的时间窗口来说过于陈旧或过于超前。 它可能指向任一设备上的日期或时间问题,但也可能由请求延迟导致。

汇总追踪文件

t3 trace summary 直接读取追踪文件及其轮转备份,因此在服务器停滞或停止时也能工作。 它按 span 名称输出计数、速率和延迟百分位数。用它来衡量后台工作或比较两个构建。

t3 trace summary --since 30m --limit 40

如果设置了 T3CODE_TRACE_FILE,则读取它;否则读取 --base-dir 或 T3CODE_HOME 下的 <home>/userdata/logs/server.trace.ndjson,以及 T3CODE_TRACE_MAX_FILES 个轮转备份。 对于开发运行或复制的文件,请设置 T3CODE_TRACE_FILE。--since 30m 保留最近 30 分钟 内结束的 span。速率按第一个和最后一个 span 结束之间的每分钟计算。

指标

指标不会写入本地文件。

  • 本地持久化:无
  • 远程导出:仅通过 OTLP,在配置后生效
  • 当前定义:apps/server/src/observability/Metrics.ts

如果未配置 OTLP,指标仍会在进程内存在,但你将没有可检查的本地产物。

事件循环停滞

apps/server/src/observability/EventLoopMonitor.ts 每 30 秒采样一次服务器的事件循环。当 自上次采样以来循环停滞超过 2 秒时,它会记录一个带警告的根 server.eventLoop.stall span。该 span 的追踪级别为 Warn,因此当 T3CODE_TRACE_MIN_LEVEL 为 Warn 时会保留。除非启用了 OTLP 日志,否则该警告会显示在 设置 > 诊断中。span 时间指的是采样运行的时间,而不是停滞发生的时间。

在繁忙的服务器上,追踪文件远不到一小时就会轮转,因此每次停滞也会作为一行 NDJSON 追加到追踪文件旁边的 event-loop-stalls.ndjson。每行包含 time 和 span 属性。该文件保留最近至少 1000 次停滞,并带有一个轮转备份 (event-loop-stalls.ndjson.1)。用它来观察服务器在数天内的停滞频率。

某些延迟不会被记录:

  • delayMaxMs 是最长的停滞值,可能低估最多 1 秒。2 秒阈值适用于该值,因此超过 3 秒的停滞通常会 被记录,而较短的停滞可能被漏掉。恰好在采样运行时结束的停滞也可能被漏掉。
  • 计算机休眠的时间在 macOS 和 Windows 上会显示为延迟。因此仅当循环忙碌(而非等待事件) 至少 delayMaxMs 时,采样才会计数。忙碌时间覆盖整个窗口,因此在原本忙碌的窗口中的短暂 休眠仍可能记录为虚假停滞。该 span 随后会显示远低于 delayMaxMs 的 CPU 时间。
  • 启动后的第一次采样会被跳过。迁移和投影引导等启动工作在大数据库上可能阻塞循环数秒。

CPU 时间和页面错误涵盖自上一次采样以来整个时间窗内的完整进程。该时间窗名义上是 30 秒,但长时间停顿会延迟采样并使时间窗变长。窗口中的其他工作可能掩盖等待,因此只有远低于 delayMaxMs 的 CPU 时间才能证明线程在等待。请结合页面错误来解读 CPU:

  • cpuSystemMs 高且页面错误多表示内存压力。主要错误是从磁盘或交换分区读取。在 macOS 上,从压缩内存读取属于次要错误并消耗系统 CPU。
  • cpuUserMs 高且页面错误少表示 JavaScript 工作或垃圾回收。
  • CPU 低且主要页面错误少则指向同步磁盘 I/O,例如 SQLite 读取或跟踪文件写入。
  • 大量 involuntaryContextSwitches 表示其他进程在竞争 CPU。

Provider 事件 NDJSON 文件仍然存在,用于 provider 运行时流。它们与主服务器跟踪文件是分开的。

以插桩模式运行服务器

有两种有用的模式:

  • 仅本地:stdout + 本地 server.trace.ndjson
  • 完整本地可观测性:stdout + 本地跟踪文件 + 导出到 Grafana/Tempo/Prometheus 的 OTLP

本地跟踪文件始终开启。OTLP 导出是选择启用的。

选项 1:仅本地跟踪

你不需要任何额外的环境变量。只需正常运行应用并检查 server.trace.ndjson。

示例:

t3
node --run dev
node --run dev:desktop

选项 2:使用本地 LGTM 技术栈运行

1. 启动 Grafana LGTM

docker run --name lgtm \
  -p 3000:3000 \
  -p 4317:4317 \
  -p 4318:4318 \
  --rm -ti \
  grafana/otel-lgtm

然后打开 http://localhost:3000。

默认 Grafana 登录信息:

  • 用户名:admin
  • 密码:admin

2. 导出 OTLP 环境变量

关于端点设置和启动优先级,请参阅用户遥测指南。在 worktree 中,使用 vp run dev 而不是 t3 以保持状态隔离。

export T3CODE_OTLP_TRACES_URL=http://localhost:4318/v1/traces
export T3CODE_OTLP_METRICS_URL=http://localhost:4318/v1/metrics
export T3CODE_OTLP_LOGS_URL=http://localhost:4318/v1/logs
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=development

可选:

export T3CODE_TRACE_MIN_LEVEL=Info
export T3CODE_TRACE_TIMING_ENABLED=true

3. 从同一个 shell 启动应用

CLI:

t3

Monorepo web/server 开发:

node --run dev

Monorepo desktop 开发:

node --run dev:desktop

已打包的桌面应用:

从同一个 shell 启动实际的应用可执行文件,以便桌面应用和嵌入式后端继承 T3CODE_OTLP_*。

macOS 应用捆绑包示例:

T3CODE_OTLP_TRACES_URL=http://localhost:4318/v1/traces \
T3CODE_OTLP_METRICS_URL=http://localhost:4318/v1/metrics \
T3CODE_OTLP_LOGS_URL=http://localhost:4318/v1/logs \
"/Applications/T3 Code.app/Contents/MacOS/T3 Code"

直接二进制文件示例:

T3CODE_OTLP_TRACES_URL=http://localhost:4318/v1/traces \
T3CODE_OTLP_METRICS_URL=http://localhost:4318/v1/metrics \
T3CODE_OTLP_LOGS_URL=http://localhost:4318/v1/logs \
./path/to/your/desktop-app-binary

设置 shell 环境变量后,不要依赖从 Finder、Spotlight、Dock 或“开始”菜单启动。这些启动方式通常不会拾取这些变量。

4. 更改环境变量后完全重启

后端在进程启动时读取可观测性配置。如果你更改了 OTLP 环境变量,请完全停止应用并重新启动。

如何使用跟踪和指标调试服务器

从本地跟踪文件开始

跟踪文件是检查原始 span 数据最快的方式。

为启动模式解析一次路径。生产环境和显式配置的主目录会将运行时状态存储在基础目录的 userdata 文件夹下:

TRACE_FILE="${T3CODE_HOME:-$HOME/.t3}/userdata/logs/server.trace.ndjson"

从链接的 worktree 启动的开发服务器默认使用该 worktree 的本地主目录:

TRACE_FILE="$WORKTREE/.t3/userdata/logs/server.trace.ndjson"

只有在链接的 worktree 之外进行的隐式开发运行才会使用共享的开发目录:

TRACE_FILE="$HOME/.t3/dev/logs/server.trace.ndjson"

实时查看所选文件:

tail -f "$TRACE_FILE"

显示失败的 span:

jq -c 'select(.type == "effect-span" and .exit._tag != "Success") | {
  name,
  durationMs,
  exit,
  attributes
}' "$TRACE_FILE"

显示慢 span:

jq -c 'select(.durationMs > 1000) | {
  name,
  durationMs,
  traceId,
  spanId
}' "$TRACE_FILE"

检查嵌入式日志事件:

jq -c 'select(any(.events[]?; .attributes["effect.logLevel"] != null)) | {
  name,
  durationMs,
  events: [
    .events[]
    | select(.attributes["effect.logLevel"] != null)
    | {
        message: .name,
        level: .attributes["effect.logLevel"]
      }
  ]
}' "$TRACE_FILE"

跟踪一条 trace:

jq -r 'select(.traceId == "TRACE_ID_HERE") | [
  .name,
  .spanId,
  (.parentSpanId // "-"),
  .durationMs
] | @tsv' "$TRACE_FILE"

筛选编排命令:

jq -c 'select(.attributes["orchestration_v2.command_type"] != null) | {
  name,
  durationMs,
  commandType: .attributes["orchestration_v2.command_type"],
  threadId: .attributes["orchestration_v2.thread_id"]
}' "$TRACE_FILE"

筛选 git 活动:

jq -c 'select(.attributes["git.operation"] != null) | {
  name,
  durationMs,
  operation: .attributes["git.operation"],
  cwd: .attributes["git.cwd"],
  hookEvents: [
    .events[]
    | select(.name == "git.hook.started" or .name == "git.hook.finished")
  ]
}' "$TRACE_FILE"

在需要真正的跟踪查看器时使用 Tempo

当你需要以下场景时,Tempo 比原始 NDJSON 更好:

  • 跨多条 trace 搜索
  • 直观地检查父子关系
  • 比较许多慢 trace
  • 深入分析单个失败请求,而无需手动按 traceId 关联

Grafana 中的推荐流程:

  1. 打开 Explore。
  2. 选择 Tempo 数据源。
  3. 将时间范围设置为最近的时间,例如 Last 15 minutes。
  4. 从宽泛的范围开始。不要一开始就使用非常狭窄的查询。
  5. 查找来自 t3code-server 或 t3code-desktop 服务的 span,然后按 span 名称或属性缩小范围。

不错的起始搜索:

  • 服务名 t3code-server 或 t3code-desktop,外加一个资源属性,如 deployment.environment.name
  • span 名称,例如 sendTurn,或如 GitVcsDriver.statusDetails.status 这样的 Git 操作
  • git.operation 属性标识具体操作的 Git spans
  • 带有 orchestration_v2.command_type 等属性的编排 spans

一旦你确认 traces 已经在到达,针对 sendTurn 或 Git 操作名称等更窄范围的 TraceQL 查询就会变得有用。

使用 Metrics 发现系统性问题

Traces 最适合分析单个请求,Metrics 最适合观察趋势。

值得关注的指标族:

  • t3_rpc_request_duration
  • t3_orchestration_command_duration
  • t3_provider_turn_duration(provider 适配器开始一次 turn 所花的时间,并非 turn 自身的运行时间)
  • t3_git_command_duration

计数器(Counters)能告诉你请求量和失败率:

  • t3_rpc_requests_total
  • t3_orchestration_commands_total
  • t3_provider_turns_total
  • t3_git_commands_total

Webhooks 也有自己的指标族:

  • t3_webhook_deliveries_total,按 outcome 和 source(relay 或 direct)区分。除了发送方看到的情况之外,queue_full 表示某个任务等待投递的数量已经达到上限,prompt_too_long 表示填充后的 prompt 超过了 provider 的限制,duplicate 表示 relay 投递了一个该环境已经运行过的请求。
  • t3_webhook_runs_total,按 outcome(started、skipped、failed)区分,对应这些投递所启动的运行;这类运行发生在发送方拿到答案之后。
  • t3_webhook_held_delay,用于衡量 relay 暂存的请求在被送达前等待了多长时间。
  • t3_secret_requests_total,按 status(saved、declined、cancelled、timed_out)区分,对应 agents 向用户请求的 secrets;t3_secret_refs_consumed_total,按 result(used、rejected)区分,对应兑现这些 secrets 的工具。二者都不携带任何值。

ScheduledTaskService.triggerWebhook spans 在每次请求中携带相同的结果;每个由一次投递启动的运行都会成为独立的 ScheduledTaskService.runWebhookDelivery trace。对于 relay 转发的请求,该 span 还会作为 relay span 的子节点进入 T3 Connect 的 trace 导出;直接到达环境的请求永远不会加入发送方的 trace。

当问题属于以下情形时,使用 Metrics:

  • “这个操作是不是一直很慢?”
  • “某次变更后是否变得更糟?”
  • “哪种命令类型失败最频繁?”

当问题属于以下情形时,使用 Traces:

  • “这个具体请求中发生了什么?”
  • “是哪个子 span 导致了这一次慢交互?”
  • “失败流程内部产生了哪些日志?”

常见工作流

“为什么这个请求失败了?”

  1. 从本地 NDJSON 文件开始。
  2. 找到 effect-span 记录,其中 exit._tag != "Success"。
  3. 按 traceId 分组。
  4. 检查兄弟 spans 和 span 事件。
  5. 如有需要,转到 Tempo 查看完整的 trace 树。

“为什么 UI 感觉变慢了?”

  1. 在 trace 文件或 Tempo 中搜索较慢的顶层 spans。
  2. 检查子 spans,看是否有 sqlite、git、provider 或终端相关的工作。
  3. 查看对应的 duration 指标,判断慢速是否是系统性的。

“Git hooks 是否导致了延迟?”

  1. 过滤 git.operation spans。
  2. 检查 git.hook.started 和 git.hook.finished 事件。
  3. 将 hook 的耗时与包含它的 git span 的持续时间进行比较。

“为什么本地有 spans,但 Grafana 里什么也没有?”

通常原因是以下之一:

  • T3CODE_OTLP_TRACES_URL 未设置
  • 应用是从与导出环境变量时不同的环境中启动的
  • 更改环境变量后,应用没有完全重启
  • Grafana 查看的是错误的时间范围或服务名

如果本地 NDJSON 文件在持续更新,说明本地 tracing 是正常的。问题几乎总是出在 OTLP 导出配置或进程启动方式上。

如何考虑在未来的代码中添加 Tracing

优先选择边界,而不是细小的辅助函数

合适的 span 边界:

  • RPC 方法
  • 编排命令处理
  • provider 适配器调用
  • 外部进程调用
  • 持久化写入
  • 队列交接

避免对每个细小的辅助函数都进行 tracing。大多数辅助函数应继承当前活动的 span,而不是创建新的 span。

在已有 Effect.fn(...) 的地方复用它

代码库中已经大量使用 Effect.fn("name")。这通常应该是你的第一个 tracing 边界。

对于临时性工作:

import { Effect } from "effect";

const runThing = Effect.gen(function* () {
  yield* Effect.annotateCurrentSpan({
    "thing.id": "abc123",
    "thing.kind": "example",
  });

  yield* Effect.logInfo("starting thing");
  return yield* doWork();
}).pipe(Effect.withSpan("thing.run"));

将高基数细节放在 Spans 上

使用 span 注解来记录 ID、路径和其他详细上下文:

yield *
  Effect.annotateCurrentSpan({
    "provider.thread_id": input.threadId,
    "provider.request_id": input.requestId,
    "git.cwd": input.cwd,
  });

保持 Metric Labels 的低基数

合适的 metric labels:

  • 操作类型
  • 方法名
  • provider 类型
  • 聚合类型
  • 结果

不合适的 metric labels:

  • 原始线程 ID
  • 命令 ID
  • 文件路径
  • cwd
  • 完整 prompts
  • 当标准化的族标签就够用时,使用完整模型字符串

详细的上下文应放在 spans 中,而不是 metrics 中。

将日志用作 Span 事件

span 内部的日志会成为 trace 故事的一部分:

yield * Effect.logInfo("starting provider turn");
yield * Effect.logDebug("waiting for approval response");

这些消息会以 span events 的形式出现,因为已安装了 Logger.tracerLogger。

使用可管道化的 Metrics API

withMetrics(...) 是向一个 effect 附加计数器和计时器的默认方式:

import { someCounter, someDuration, withMetrics } from "../observability/Metrics.ts";

const program = doWork().pipe(
  withMetrics({
    counter: someCounter,
    timer: someDuration,
    attributes: {
      operation: "work",
    },
  }),
);

详细 API 参考

运行时装配

服务端的可观测性层是在 apps/server/src/observability/Observability.ts 中组装的。

它提供:

  • 漂亮的 stdout 日志记录器
  • Logger.tracerLogger
  • 本地 NDJSON tracer
  • 可选的 OTLP trace 导出器
  • 可选的 OTLP metrics 导出器
  • 可选的 OTLP log 导出器
  • Effect trace 级别和计时引用

桌面主进程是第二个生产者,组装在 apps/desktop/src/app/DesktopObservability.ts 中。它读取与其所监管后端相同的 T3CODE_OTLP_* 名称和相同的设置项,并覆盖后端无法看到的工作:应用启动、窗口和菜单处理、后端监管以及更新。它以服务 t3code-desktop 上报,因此采集器会将其与后端并列显示,而不是混入后端。它只导出 traces 和 logs;主进程不记录 metrics,因此 metrics 端点仅适用于后端。

环境变量

本地 trace 文件:

  • T3CODE_TRACE_FILE:覆盖 trace 文件路径
  • T3CODE_TRACE_MAX_BYTES:单文件轮转大小,默认 10485760
  • T3CODE_TRACE_MAX_FILES:轮转文件数量,默认 10
  • T3CODE_TRACE_BATCH_WINDOW_MS:刷新窗口,默认 1000
  • T3CODE_TRACE_MIN_LEVEL:最低 trace 级别,默认 Info
  • T3CODE_TRACE_TIMING_ENABLED:启用计时元数据,默认 true

OTLP 导出:

  • T3CODE_OTLP_TRACES_URL:OTLP trace 端点
  • T3CODE_OTLP_METRICS_URL:OTLP metric 端点
  • T3CODE_OTLP_LOGS_URL:OTLP log 端点
  • T3CODE_OTLP_EXPORT_INTERVAL_MS:导出间隔,默认 10000
  • T3CODE_OTLP_HEADERS:用于全部三个导出器的额外请求头,格式与 OTEL_EXPORTER_OTLP_HEADERS 相同:以逗号分隔的 key=value 对,值为百分号编码。
  • T3CODE_OTLP_PROTOCOL:http/json(默认)或 http/protobuf

服务器和桌面应用还读取标准的 OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_ENDPOINT 和通用 OTEL_EXPORTER_OTLP_ENDPOINT(并附加 /v1/traces、/v1/metrics 或 /v1/logs),适用于期望这些变量的采集器。非空白的 T3CODE_OTLP_*_URL 优先于上述任一变量,而针对某一信号的端点优先于该信号的通用端点。空白值视为未设置。使用 OTEL 端点的信号会从 OTEL_EXPORTER_OTLP_HEADERS 获取请求头,并从 OTEL_EXPORTER_OTLP_PROTOCOL(默认 http/protobuf,读取时不区分大小写)获取协议;针对某一信号的 OTEL_EXPORTER_OTLP_{TRACES,METRICS,LOGS}_HEADERS 或 _PROTOCOL 优先于该信号的通用值。T3CODE_OTLP_HEADERS 和 T3CODE_OTLP_PROTOCOL 绝不应用于此。端点不是 http 或 https URL、协议不是 http/protobuf 或 http/json(例如 grpc)、或请求头不是带有百分号编码值的 key=value 对,都会在启动时发出警告并关闭该信号的导出,而不是将其发送到设置端点。

服务名称是固定的:后端为 t3code-server,桌面主进程为 t3code-desktop,两者都在 service.namespace t3code 中。OTEL_SERVICE_NAME 以及 OTEL_RESOURCE_ATTRIBUTES 中的 service.name 或 service.namespace 会被忽略。要区分不同安装,请使用其他资源属性,例如 OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=staging。

每个进程上报的是自己的构建,而非其通信对象的构建:服务器、桌面主进程、UI 和移动应用各自携带 t3code.release.channel(来自自身版本的 dev、stable、nightly 或 preview;桌面 PR 构建计为 preview,未由发布工作流构建的服务器上报 dev)和 deployment.environment.name(development、production;preview 对应 staging)。在服务器和桌面主进程上,OTEL_RESOURCE_ATTRIBUTES 中这两个键的任意一个都会覆盖自身上报值。UI 和移动应用从不读取该变量:Vercel PR 预览会设置 VITE_RELEASE_CHANNEL=preview,因为它们的 APP_VERSION 必须与服务器的保持可比;移动应用映射其构建变体(development、preview、production),或从开发 bundle 上报 dev。

T3 Code 专属属性使用 t3code. 前缀。UI 在浏览器和桌面窗口中都以 t3code-web 运行,因为它们是同一套代码;t3code.client.surface 为 desktop 或 web。服务器的 t3code.server.managed_by 在桌面应用启动其为 desktop,否则为 standalone。桌面主进程将 t3code.desktop.mode 上报为 development 或 packaged(以前为 service.mode;依赖该键的仪表板必须切换)。Node 进程还上报语义化的 process.runtime.name、process.runtime.version 和 process.runtime.description;UI traces 上报 browser.language、user_agent.original,在 Chromium 中还上报 browser.platform、browser.mobile 和 browser.brands。

每个资源还携带 service.instance.id:服务器和桌面端每个进程随机生成,UI 每次页面加载生成,移动端使用 Expo session。OTEL_RESOURCE_ATTRIBUTES 中的 service.instance.id 会替换服务器和桌面端上生成的 ID。

如果未设置 OTLP URL,本地 trace 仍然可用,metrics 仅保留在进程内,日志仅保留在 stdout。

总开关

T3CODE_OTEL_SDK_DISABLED 和 OTEL_SDK_DISABLED 会关闭服务器和桌面主进程中的所有 OTLP 导出,覆盖来自环境或设置的任何端点。本地 trace 文件和 stdout 日志不受影响。

T3CODE_OTEL_SDK_DISABLED 在设置时优先,因此在为其他所有内容设置 OTEL_SDK_DISABLED 的机器上,T3CODE_OTEL_SDK_DISABLED=false 会重新启用导出。它接受常见的布尔拼写(true/false、yes/no、on/off、1/0、y/n)。OTEL_SDK_DISABLED 遵循 OpenTelemetry 规范,只有 true 会禁用导出,因此 OTEL_SDK_DISABLED=1 不会。值不区分大小写,并会去除首尾空白。无法识别的值会被忽略,并在启动时发出警告。

OTEL_TRACES_EXPORTER、OTEL_METRICS_EXPORTER 或 OTEL_LOGS_EXPORTER 设置为 none 时,只关闭该信号,覆盖 OTEL 端点和设置端点。某个信号的 T3CODE_OTLP_*_URL 仍然优先。otlp 是默认值,任何其他导出器名称(如 console 或 prometheus)都会被忽略,并在启动时发出警告。

目前哪些部分已接入观测埋点

当前高价值的 span 和指标边界包括:

  • WebSocket RPC 请求 span(ws.rpc.<method>)以及 apps/server/src/observability/RpcInstrumentation.ts 中的指标
  • 启动阶段
  • 编排命令处理
  • provider 会话与 turn 操作
  • git 命令执行与 git hook 事件
  • 终端会话生命周期
  • sqlite 查询执行,但 orchestrationV2.EventSink.write 内的语句除外,其 sql.transaction span 将整体计为一个
  • 事件循环停滞(server.eventLoop.stall)

当前限制

  • span 之外的日志不会持久化到 trace 文件中;SSH 管理的启动 stdout/stderr 仍会捕获到其 launcher 日志中。
  • 指标不会在本地进行快照。

堆快照

要查看长时间运行的服务器在内存中持有哪些内容,可向其发送 SIGUSR2。服务器会将 V8 堆快照写入其 logs 目录并记录该路径。这适用于桌面版、npx t3 以及 macOS 和 Linux 上的服务安装。Windows 没有 SIGUSR2。

将信号发送到 server-runtime.json 中的服务器 pid,该文件位于服务器的 state 目录中,与 logs 目录相邻。对于开发服务器或 --home-dir 启动的实例,请使用 Traces 中该服务器的 state 目录。不要将其发送给桌面应用或服务启动器:没有该处理器的进程会在收到 SIGUSR2 时退出。崩溃后,该文件可能保留一个过期的 pid,该 pid 现在属于另一个进程,因此请先检查 pid。

pid="$(jq .pid "${T3CODE_HOME:-$HOME/.t3}/userdata/server-runtime.json")"
ps -p "$pid" -o command=

如果 ps 显示的是 T3 Code 服务器,则发送信号:

kill -USR2 "$pid"

文件为 <logsDir>/server-<pid>-<timestamp>.heapsnapshot,位于 server.trace.ndjson 旁边。要打开它,请使用 Chrome DevTools 中的 Memory 标签页并选择 Load。

在生成快照之前:

  • 服务器在写入文件时会停止。对于较大的堆,这可能需要一分钟或更长时间。已连接的客户端可以在暂停期间重新连接;如果服务器配置了事件循环监视器,它会将暂停记录为一次停滞。只发送一次信号。在写入期间发送的第二个信号会在第一个完成后再次生成快照。
  • 写入所需的空闲内存大约与堆使用的内存相当。在一台已经在进行内存交换的机器上,这可能会使问题恶化或导致服务器崩溃。
  • 该文件包含服务器内存中的全部内容,包括令牌、密钥和线程内容。请勿公开分享。完成后请删除它,因为存储清理不会移除该文件。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 t3/t3code