feat(gateway,admin-web): 可观测面从轮询改为 WS 帧推送(裁决 05.4.3 + 16.4.3-WS)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 新增三个下行帧(纯新增,既有帧名/形状零改动):observability_log / observability_trace /
  observability_metrics,各带 ≤1s 节拍、无新数据不发帧、单帧上限(日志 200 / span 100)
- 帧壳 {type,data} 不带 sessionId;**不进 frameReachesAll 白名单** ⇒ fail-closed 只发 owner+admin
  (变异验证两次:加进白名单 → viewer 用例必红)
- 推送开启条件=owner/admin 凭据「读过一次」对应 REST(引导数据仍由该次读取返回),
  连接全断即退订并停循环(无常驻 goroutine);既有 REST 端点路径/RBAC/形状完全不变
- 前端:删除 logs/metrics/trace 三处 useAutoReload 轮询;新增 observabilityStream(引导+去重合并+窗口)
  与 observabilityViews(三个面板 + WS 徽标),长列表统一走已落地的 VirtualList
- 复核:build/vet 绿 + gateway 全包测试绿(含真 bus→真 /ws/chat 三角色帧断言)+ `-race`;
  前端 tsc 0 错 + vite build 成功
- 待登记契约:api-client 需新增「可观测实时推送」一节
This commit is contained in:
AskaEth committed 2026-10-04 13:58:03 +08:00
1 parent 74b12d87ca
commit 0e1f163ef9
10 files changed
+1449 -312

No files matched your search

@@ -31,10 +31,20 @@ type AdminTraceSpan struct {
// 最近的工作而不会无界增长。
type RingBuffer struct {
mu sync.RWMutex
buf []trace.SpanRecord
buf []traceEntry
next int
full bool
cap int
// seq 是最后写入 span 的序号(单调递增,从 1 起)。
// 增量推送(observability_trace 帧)用它做游标,见 SnapshotSince。
seq uint64
}
// traceEntry 是环形缓冲内的一个存储格。序号随写入单调递增,因此
// 下游可以「只取比游标新的」而不是靠时间戳去猜。
type traceEntry struct {
seq uint64
rec trace.SpanRecord
}
// newRingBuffer 构建至多容纳 cap 个 span 的环形缓冲(cap<=0 时用默认值)。
@@ -42,20 +52,28 @@ func newRingBuffer(cap int) *RingBuffer {
if cap <= 0 {
cap = defaultTraceBufCap
}
return &RingBuffer{buf: make([]trace.SpanRecord, cap), cap: cap}
return &RingBuffer{buf: make([]traceEntry, cap), cap: cap}
}
// Append 存储 rec,缓冲满时逐出最旧的。
func (r *RingBuffer) Append(rec trace.SpanRecord) {
r.mu.Lock()
defer r.mu.Unlock()
r.buf[r.next] = rec
r.seq++
r.buf[r.next] = traceEntry{seq: r.seq, rec: rec}
r.next = (r.next + 1) % r.cap
if r.next == 0 {
r.full = true
}
}
// Seq 返回最后写入 span 的序号(还没写过任何 span 时为 0)。
func (r *RingBuffer) Seq() uint64 {
r.mu.RLock()
defer r.mu.RUnlock()
return r.seq
}
// Snapshot 返回至多 n 条最近 span,最新优先。非正的 n
// 选用 defaultObsLimit;n 被夹取到 maxObsLimit。
func (r *RingBuffer) Snapshot(n int) []trace.SpanRecord {
@@ -77,7 +95,32 @@ func (r *RingBuffer) Snapshot(n int) []trace.SpanRecord {
out := make([]trace.SpanRecord, 0, count)
for i := 0; i < count; i++ {
idx := (r.next - 1 - i + r.cap*2) % r.cap
out = append(out, r.buf[idx])
out = append(out, r.buf[idx].rec)
}
return out
}
// SnapshotSince 返回序号大于 since 的至多 n 条 span,最新优先(与 Snapshot
// 同序),供增量推送使用。n 非正时只用 defaultObsLimit 兜底;调用方按
// 「帧载荷有上限」的约束传入自己的上限。
func (r *RingBuffer) SnapshotSince(since uint64, n int) []traceEntry {
if n <= 0 {
n = defaultObsLimit
}
r.mu.RLock()
defer r.mu.RUnlock()
count := r.next
if r.full {
count = r.cap
}
out := make([]traceEntry, 0, min(count, n))
for i := 0; i < count && len(out) < n; i++ {
idx := (r.next - 1 - i + r.cap*2) % r.cap
e := r.buf[idx]
if e.seq <= since {
break
}
out = append(out, e)
}
return out
}