Files
Cyrene_v2/cmd/cyrene/observability.go
T
AskaEth 52f08dc343
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(cyrene): cmd/cyrene 存量英文注释中文化(22 个非测试文件,仅注释)
- 四组并行完成:main/wire*.go、wire_capacity|channels|runtime、channels|persona_*|emotion|observability、
  voice|plugins|stream|skills|vision|subagent_tool|extractor|mcp|sessionread_tool|terminal_tool。
- 机械校验(git diff -U0 去 // 行与空行)仅剩 wire_frontend.go:84 一处行尾注释:代码前缀
  「\ta.gw = gw 」经 cat -A 与 HEAD 逐字节比对一致(allow 清单已声明)。
- 验收:隔离 worktree(HEAD + 本批 patch)go build/vet 绿、全树 58 包 ok、
  linux/amd64 + darwin/arm64 交叉编译绿;gofmt -l 干净。
- 备注:整树两次红均为负载敏感 flake(backthink / orchestration 的 timeout 类用例单独 -count=20 全过)。
2026-10-04 16:58:06 +08:00

108 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"fmt"
"os"
"sync"
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
"git.yeij.top/AskaEth/Cyrene_v2/internal/services/gateway"
)
// newAppTraceWriter 构建组合根的 trace writer:JSONL stderr 记录器(每个 span 的
// 本地结构化日志),外加为 gateway 可观测性出口预留的位置 —— 后者只能在 gateway
// 存在之后注册(见 wireGatewayTraceSink)。
func newAppTraceWriter() *traceFanout {
return newTraceFanout(trace.NewJSONRecorder(os.Stderr))
}
// wireGatewayTraceSink 把 gateway 的可观测性出口注册到应用的 trace 扇出上。它由
// wireFrontend 调用 —— 那是 gateway 首次存在的时刻 —— 因为 gateway 构建在发出 span
// 的循环之后。没有它,管理面板的 trace 环形缓冲区永远不会被喂数据,
// GET /api/v1/admin/observability/trace 会永远返回空列表(design/19 §2)。在没有
// tracer 或 gateway 的启动中是空操作。
func wireGatewayTraceSink(a *app, gw *gateway.Server) {
if a.traceWriter == nil || gw == nil {
return
}
a.traceWriter.Add(gatewaySpanSink{srv: gw})
a.log.Info("observability trace sink wired")
}
// traceFanout 是组合根的 trace.Writer。它把每个已完成的 span 扇出到启动时安装的
// 出口(JSONL stderr 记录器),以及启动流程中稍后注册的出口:gateway 的可观测性
// 缓冲区要等 wireFrontend 构建好 gateway 才存在,而发出 span 的循环在 wireRuntime
// 中更早就构建了。
//
// 它可安全并发使用 —— 循环会从多个 goroutine 结束 span(按步、按工具调用、子代理)。
type traceFanout struct {
mu sync.RWMutex
writers []trace.Writer
}
// newTraceFanout 在给定 writers 之上构建扇出。nil writer 会被丢弃。
func newTraceFanout(writers ...trace.Writer) *traceFanout {
f := &traceFanout{}
for _, w := range writers {
f.Add(w)
}
return f
}
// Add 再安装一个出口。nil writer 被忽略。
func (f *traceFanout) Add(w trace.Writer) {
if w == nil {
return
}
f.mu.Lock()
defer f.mu.Unlock()
f.writers = append(f.writers, w)
}
// WriteSpan 把 rec 写给每个出口并返回第一个错误。即使失败也仍会尝试所有出口,
// 因此单个坏掉的出口无法把 span 从其他出口那里藏起来;tracer 只记录返回的错误
// (trace.Tracer.emit)。
func (f *traceFanout) WriteSpan(rec trace.SpanRecord) error {
f.mu.RLock()
defer f.mu.RUnlock()
var firstErr error
for _, w := range f.writers {
if w == nil {
continue
}
if err := w.WriteSpan(rec); err != nil && firstErr == nil {
firstErr = fmt.Errorf("trace fanout: %w", err)
}
}
return firstErr
}
// wireGatewayLogBuffer 把组合根的日志环形缓冲区交给 gateway,使
// GET /api/v1/admin/observability/logs 能提供最近的后端日志尾部。logger 构建在
// gateway 之前(其记录必须从第一行日志起就被捕获),因此缓冲区在这里注入 ——
// gateway 首次存在的时刻 —— 与 wireGatewayTraceSink 对 span 的做法一致。在没有
// gateway 的启动中是空操作。
func wireGatewayLogBuffer(a *app, buf *logger.RingBuffer) {
if a == nil || a.gw == nil || buf == nil {
return
}
a.gw.SetLogBuffer(buf)
a.log.Info("observability log buffer wired", "capacity", buf.Len())
}
// gatewaySpanSink 通过 *gateway.Server 的 ObserveSpan 入口把它适配成 trace.Writer,
// 使发出的 span 落在管理面板的 trace 环形缓冲区**并且**计入 LLM/子代理指标计数器
// (ObserveSpan 既追加到缓冲区,也观测指标;缓冲区正是
// GET /api/v1/admin/observability/trace 所提供的内容)。
type gatewaySpanSink struct{ srv *gateway.Server }
// WriteSpan 把 rec 喂给 gateway。server 缺失时是空操作。
func (g gatewaySpanSink) WriteSpan(rec trace.SpanRecord) error {
if g.srv == nil {
return nil
}
g.srv.ObserveSpan(rec)
return nil
}