ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

T3 Code 服务端可观测性实战指南:本地 NDJSON Trace、OTLP 导出与 Grafana LGTM 排障

T3 Code 服务端可观测性实战指南:本地 NDJSON Trace、OTLP 导出与 Grafana LGTM 排障 T3 Code 服务端可观测性实战指南本地 NDJSON Trace、OTLP 导出与 Grafana LGTM 排障【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3codeT3 Code 服务端采用一套模型、三个产出的可观测性设计人类可读的 pretty 日志输出到 stdout完整 span 以 NDJSON 形式持久化到本地 trace 文件Trace 与 Metrics 可按需通过 OTLP 协议导出到 Grafana LGTM 等真实后端。本文面向维护者与二次开发者完整覆盖产物定位、本地/远端两种插桩模式、jq 级 trace 排查、Tempo 可视化、指标语义以及新增代码时如何正确埋点并给出仓库源码级实现依据。可观测性总览日志、Trace 与指标的三分模型T3 Code 只有一个服务端可观测性模型三个产出各司其职pretty 日志走 stdout面向人类格式由Logger.consolePretty()生成正常本地启动不落盘已完成 span 写入本地 NDJSON trace 文件这是本地启动时的持久化事实来源source of truthTrace 与指标可选地通过 OTLP 导出对接 Grafana LGTMLoki/Grafana/Tempo/Mimir这类真实后端做跨请求检索与趋势分析。本地 trace 文件始终开启OTLP 导出是显式选配。普通的本地启动不会额外写一份独立服务端日志文件但SSH 托管的启动方式SSH-managed launch会把远端进程的 stdout/stderr 持久化到~/.t3/ssh-launch/state/server.log。注意对于正常本地启动不写独立 server log与SSH 托管启动持久化 launcher 日志是两种并存行为排障时不要混淆。产物在哪里Logs / Traces / Metrics 的落点与差异Logs面向人类的 stdout维度取值目的地stdout格式Logger.consolePretty()本地持久化无正常本地启动SSH 托管启动~/.t3/ssh-launch/state/server.log实现位于 serverLogger.tsLogger.layer([Logger.consolePretty(), Logger.tracerLogger], { mergeWithExisting: false })同时安装两个 logger。这意味着如果你想让某条日志消息同时出现在 trace 文件中必须在活跃 span 内部用Effect.log...输出——Logger.tracerLogger会把它作为 span event 附加到当前 span 上详见后文日志即 span event。Traces本地 NDJSON 持久化的 span完成的 span 以 NDJSON 记录写入serverTracePath。路径由deriveServerPaths计算见 apps/server/src/config.ts状态目录根据devUrl与baseDirIsExplicit选择userdata或dev子目录日志目录恒为stateDir/logstrace 文件名为固定的server.trace.ndjson。具体落点取决于启动方式启动方式Trace 文件路径生产 / 显式配置 homehome/userdata/logs/server.trace.ndjson默认~/.t3/userdata/...--home-dir /custom/path或T3CODE_HOME指向自定义目录时用/custom/path/userdata/...关联 worktree 的 dev 运行worktree/.t3/userdata/logs/server.trace.ndjson非关联 worktree 的隐式 dev 运行~/.t3/dev/logs/server.trace.ndjson记录公共字段typeeffect-span或otlp-spannamespan 名traceId、spanId、parentSpanId关联与层级durationMs耗时attributes结构化上下文events内嵌日志与自定义事件effect-span记录额外带exit字段取值Success/Failure/Interrupted含 causeotlp-span记录则携带 OTLP resource、scope 与可选的status字段。TraceRecord、EffectTraceRecord、OtlpTraceRecord三个 Schema 定义在 packages/shared/src/observability.ts。序列化细节值得注意同一文件内实现写盘前会对属性做compactTraceAttributesBigInt 转字符串、Date 转 ISO、Error 转结构化对象、Map/Set 归一化、循环引用标记为[Circular]并对超大字符串做截断——普通属性上限 500 字符、db.query.text恒截断到 200 字符并追加…[truncated]后缀observability.ts保证 sink 文件体积可控。另外span 结束时会以startTimeUnixNano/endTimeUnixNano计算durationMs纳秒差除以 1_000_000。DPoP 证明失败排障提示失败 span 会携带安全的environment.dpop.failure_code属性。若出现time_window失败码意味着签名证明相对环境服务器允许的时间窗过旧或过新——可能是两端设备日期/时间不一致也可能只是请求被延迟送达。Metrics只在内存与 OTLP不落本地盘维度取值本地持久化无远端导出仅 OTLP配置后当前定义apps/server/src/observability/Metrics.ts未配置 OTLP 时指标仍在进程内存在但你没有任何本地产物可查。当前定义的核心计数器与计时器包括t3_rpc_requests_total/t3_rpc_request_duration、t3_orchestration_commands_total/t3_orchestration_command_duration/t3_orchestration_command_ack_duration、t3_provider_sessions_total/t3_provider_turns_total/t3_provider_turn_duration、t3_git_commands_total/t3_git_command_duration、t3_terminal_sessions_total/t3_terminal_restarts_total等。Related Artifactsprovider 事件流是另一条线Provider 运行时事件流的 NDJSON 文件logsDir/provider/events.log见 config.ts仍然存在它服务于 provider 运行时流与主 trace 文件相互独立排查 provider 相关问题时需要单独查看。运行在插桩模式下两种实用模式local-onlystdout 本地server.trace.ndjsonfull local observabilitystdout 本地 trace 文件 OTLP 导出到 Grafana/Tempo/Prometheus本地 trace 文件始终开启OTLP 导出是 opt-in。Option 1仅本地 Trace零额外配置不需要任何额外环境变量正常启动即可npx t3node --run devnode --run dev:desktop然后直接检查server.trace.ndjson即可。Option 2运行本地 LGTM 栈Grafana Tempo Prometheus1. 启动 Grafana LGTMdocker run --name lgtm \ -p 3000:3000 \ -p 4317:4317 \ -p 4318:4318 \ --rm -ti \ grafana/otel-lgtm打开http://localhost:3000。默认 Grafana 登录凭据usernameadminpasswordadmin端口约定3000是 Grafana UI4317是 OTLP/gRPC4318是 OTLP/HTTP下文环境变量走 HTTP 端点。2. 导出 OTLP 环境变量export T3CODE_OTLP_TRACES_URLhttp://localhost:4318/v1/traces export T3CODE_OTLP_METRICS_URLhttp://localhost:4318/v1/metrics export T3CODE_OTLP_SERVICE_NAMEt3-local可选export T3CODE_TRACE_MIN_LEVELInfo export T3CODE_TRACE_TIMING_ENABLEDtrue3. 在同一个 shell 中启动应用CLInpx t3Monorepo web/server 开发node --run devMonorepo desktop 开发node --run dev:desktop打包后的桌面应用必须从同一 shell 直接启动应用可执行文件桌面应用与其内嵌后端才能继承T3CODE_OTLP_*。macOS app bundle 示例T3CODE_OTLP_TRACES_URLhttp://localhost:4318/v1/traces \ T3CODE_OTLP_METRICS_URLhttp://localhost:4318/v1/metrics \ T3CODE_OTLP_SERVICE_NAMEt3-desktop \ /Applications/T3 Code.app/Contents/MacOS/T3 Code直接二进制示例T3CODE_OTLP_TRACES_URLhttp://localhost:4318/v1/traces \ T3CODE_OTLP_METRICS_URLhttp://localhost:4318/v1/metrics \ T3CODE_OTLP_SERVICE_NAMEt3-desktop \ ./path/to/your/desktop-app-binary不要依赖 Finder、Spotlight、Dock 或 Windows 开始菜单在设置 shell 环境变量后再启动应用——这些启动方式通常不会继承 shell 环境变量。4. 修改环境变量后必须完全重启后端在进程启动时读取可观测性配置ObservabilityLive在 Layer 组装阶段一次性读取ServerConfig。修改 OTLP 环境变量后请完全停止应用再重新启动单纯热重载不会生效。如何用 Trace 与指标调试服务端先从本地 trace 文件开始trace 文件是检视原始 span 数据最快的途径。按启动模式解析一次路径生产 / 显式配置 home运行时状态位于基础目录的userdata下TRACE_FILE${T3CODE_HOME:-$HOME/.t3}/userdata/logs/server.trace.ndjson从关联 worktree 启动的 dev server默认使用该 worktree 的本地 homeTRACE_FILE$WORKTREE/.t3/userdata/logs/server.trace.ndjson仅非关联 worktree 的隐式 dev 运行使用共享 dev 目录TRACE_FILE$HOME/.t3/dev/logs/server.trace.ndjson追踪选定文件tail -f $TRACE_FILE展示失败 spanexit._tag ! Success覆盖 Failure 与 Interruptedjq -c select(.type effect-span and .exit._tag ! Success) | { name, durationMs, exit, attributes } $TRACE_FILE展示慢 span 1000msjq -c select(.durationMs 1000) | { name, durationMs, traceId, spanId } $TRACE_FILE检视内嵌日志事件effect.logLevel是 tracerLogger 附加到事件属性上的标记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按 traceId 展开父子层级jq -r select(.traceId TRACE_ID_HERE) | [ .name, .spanId, (.parentSpanId // -), .durationMs ] | tsv $TRACE_FILE过滤编排orchestration命令jq -c select(.attributes[orchestration.command_type] ! null) | { name, durationMs, commandType: .attributes[orchestration.command_type], aggregateKind: .attributes[orchestration.aggregate_kind] } $TRACE_FILE过滤 git 活动含 hook 事件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需要真正 trace 查看器时使用 Tempo当目标是以下场景时Tempo 优于手撕 NDJSON跨大量 trace 检索可视化检视父子关系对比多个慢 trace深挖某次失败请求而不必手工按traceId拼接。Grafana 中的推荐流程打开Explore选择Tempo数据源时间范围设为较近的区间如Last 15 minutes从宽开始不要一上来就用极窄的查询先按配置的 service name 找 span再按 span 名或属性收窄。好的初始检索目标service name如t3-local、t3-dev、t3-desktopspan 名如sendTurn或 Git 操作如GitVcsDriver.statusDetails.status带git.operation属性的 Git span该属性标识具体操作带orchestration.command_type等属性的编排 span。确认 trace 到达后再对sendTurn、Git 操作名等做更窄的 TraceQL 查询。用指标看系统性问题Trace 适合单请求排查指标适合看趋势。值得关注的指标族t3_rpc_request_durationt3_orchestration_command_durationt3_orchestration_command_ack_durationt3_provider_turn_durationt3_git_command_duration计数器表达流量与失败率t3_rpc_requests_totalt3_orchestration_commands_totalt3_provider_turns_totalt3_git_commands_total用指标回答是不是一直慢这次改动后是否变差了哪个命令类型失败最多用 trace 回答这次具体请求里发生了什么哪个子 span 导致这次慢交互失败流程里发出了哪些日志指标的定义集中在 Metrics.ts每个 metric 都带 description计数器通过outcome标签区分success/failure/interrupt见 Attributes.ts。新 ack 指标的确切含义t3_orchestration_command_ack_duration度量的是起点命令分发进入编排引擎终点该命令的第一个已提交领域事件被服务端发布。它是一项服务端确认ack指标。它不度量websocket 到浏览器的传输、客户端接收耗时、React 渲染时间。若需要后三者需要额外加客户端插桩或专门的 fanout 指标。源码中的定义见 Metrics.ts。常见工作流这个请求为什么失败了从本地 NDJSON 文件开始找effect-span中exit._tag ! Success的记录按traceId分组检视兄弟 span 与 span events需要完整 trace 树时再转 Tempo。为什么 UI 感觉卡顿在 trace 文件或 Tempo 中搜索慢的顶层 span检查子 span 中的 sqlite、git、provider、terminal 工作对照对应的 duration 指标判断卡顿是否是系统性的。这个命令确认耗时是否过长按commandType检查t3_orchestration_command_ack_duration若偏高检视对应的编排 trace检查 projection、sqlite、provider、git 等子 span。git hooks 是否在造成延迟过滤git.operationspan检视git.hook.started与git.hook.finished事件将 hook 耗时与所在 git span 的总耗时对比。本地有 span但 Grafana 里什么都没有通常是以下之一T3CODE_OTLP_TRACES_URL未设置应用从与导出变量不同的环境启动shell 未继承修改 env 后未完全重启Grafana 时间范围或 service name 选错。关键判据只要本地 NDJSON 文件在更新本地 tracing 就是正常的问题几乎总在 OTLP 导出配置或进程启动方式上。如何给未来的代码加 Tracing边界优先而不是小工具优先好的 span 边界RPC 方法编排命令处理provider 适配器调用外部进程调用持久化写入队列交接queue handoffs避免给每个小工具埋点——多数 helper 应当继承活跃 span而不是新建 span。复用已有的Effect.fn(...)代码库中已大量使用Effect.fn(name)这通常应该是你的第一个 tracing 边界。临时性的 ad hoc 工作可显式加 spanimport { 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));高基数细节放 span 属性ID、路径等详细上下文用 span annotationsyield * Effect.annotateCurrentSpan({ provider.thread_id: input.threadId, provider.request_id: input.requestId, git.cwd: input.cwd, });指标标签保持低基数好标签操作类型、方法名、provider 类型、aggregate 类型、结果outcome。坏标签原始 thread ID、命令 ID、文件路径、cwd、完整 prompt、可用归一化家族名代替的完整模型字符串。详细上下文属于 span不属于指标。仓库内已有一个参照实现normalizeModelMetricLabel把模型名归一到gpt/claude/gemini/other家族Attributes.tsproviderTurnMetricAttributes只把modelFamily放进指标标签Metrics.tscompactMetricAttributes只保留字符串/数字/布尔等可序列化值undefined/null 会被剔除Attributes.ts。日志即 span eventspan 内的日志会汇入 trace 故事yield * Effect.logInfo(starting provider turn); yield * Effect.logDebug(waiting for approval response);因为安装了Logger.tracerLogger这些消息会作为 span events 出现serverLogger.ts。使用管道式 Metrics APIwithMetrics(...)是给 effect 挂 counter timer 的默认方式import { someCounter, someDuration, withMetrics } from ../observability/Metrics.ts; const program doWork().pipe( withMetrics({ counter: someCounter, timer: someDuration, attributes: { operation: work, }, }), );其实现要点Metrics.ts记录开始/结束纳秒时间计算耗时timer 用基础属性更新counter 额外叠加outcome与可选的outcomeAttributes随后原样透传 effect 的成功/失败。RPC 层的完整用法见 RpcInstrumentation.tsobserveRpcEffect/observeRpcStream对每个ws.rpc.methodspan 添加rpc.transportwebsocket、rpc.systemeffect-rpc、rpc.method属性同时通过withMetrics或流式recordRpcStreamMetrics记录时长与计数少数诊断类方法如serverGetTraceDiagnostics、serverGetProcessDiagnostics等会被排除在 tracing 之外以避免自指。API 参考Runtime Wiring可观测性层的组装服务端可观测性层在 apps/server/src/observability/Layers/Observability.ts 中组装ObservabilityLive提供pretty stdout loggerLogger.tracerLogger本地 NDJSON tracermakeLocalFileTracermakeTraceSink可选 OTLP trace exporterOtlpTracer.make仅在otlpTracesUrl已配置时创建可选 OTLP metrics exporterOtlpMetrics.layer仅在otlpMetricsUrl已配置时创建Effect 的 trace 最低级别与 timing 开关 refsTracer.MinimumTraceLevel、References.TracerTimingEnabled并叠加httpHeaderRedactionLayer做请求头脱敏OTLP resource 统一携带service.name取自otlpServiceName、service.runtimet3-server、service.modeweb/desktop便于在 Grafana 中区分实例。写盘机制来自 packages/shared/src/observability.tsmakeTraceSink使用RotatingFileSink见 packages/shared/src/logging.ts记录先入内存 buffer达到 256 条阈值或batchWindowMs周期触发 flushflush 会把 flush 统计逻辑字节、条数、耗时上报给资源遥测归因模块。makeLocalFileTracer的LocalFileSpan同时代理 span 到 OTLP delegate若配置实现本地写盘 远端导出双写。span 事件、links、sampled、kind字段均被序列化exit通过formatTraceExit归一为Success/Interrupted/FailureInterrupted 仅当 cause 只含中断时判定。环境变量本地 trace 文件变量说明默认值T3CODE_TRACE_FILE覆盖 trace 文件路径派生路径见上文表格T3CODE_TRACE_MAX_BYTES单文件轮转大小1048576010 MiBT3CODE_TRACE_MAX_FILES轮转文件数10T3CODE_TRACE_BATCH_WINDOW_MSflush 窗口文档标注200当前源码 apps/server/src/cli/config.ts 的 env 默认值为1000以实际代码为准T3CODE_TRACE_MIN_LEVEL最低 trace 级别InfoT3CODE_TRACE_TIMING_ENABLED启用 timing 元数据trueOTLP 导出变量说明默认值T3CODE_OTLP_TRACES_URLOTLP trace 端点未设置关闭导出T3CODE_OTLP_METRICS_URLOTLP metric 端点未设置关闭导出T3CODE_OTLP_EXPORT_INTERVAL_MS导出间隔10000T3CODE_OTLP_SERVICE_NAME服务名t3-server默认值与解析逻辑均可对照 apps/server/src/cli/config.tstrace 路径派生默认值见 apps/server/src/config.ts。若 OTLP URL 未设置本地 tracing 照常工作指标仅存在于进程内。另有两个相关路径变量T3CODE_HOME等价于--home-dir显式数据目录决定userdata的落点配合上文 trace 路径解析使用。目前已插桩的边界当前高价值 span 与指标边界包括effect/rpc的 websocket RPC 请求 spanws.rpc.methodRPC 请求指标RpcInstrumentation.ts启动阶段startup phases编排命令处理编排命令确认延迟provider 会话与 turn 操作git 命令执行与 git hook 事件终端会话生命周期sqlite 查询执行当前约束span 之外的日志不会写入 trace 文件SSH 托管启动的 stdout/stderr 仍由 launcher 日志捕获指标不在本地快照旧的serverLogPath仍存在于配置中以兼容旧版本但结构化持久化产物已以 trace 文件为主config.ts 中仍可看到server.log与server.trace.ndjson并存。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表