- observability.go/observability_metrics.go/observability_trace.go/observability_audit.go:
拆分保证 ≤500行/函数≤80。metrics 并发安全 MetricsCollector(方法/路径/状态计数、平均/最大延迟、
近1s QPS窗口);trace 并发安全 RingBuffer(默认512, newest-first)+traceSink+MultiTraceWriter;
audit AuditStore 接口+内存回退+SQLite 持久化(NewSQLiteAuditStore 参数化 SQL+audit_logs 表)。
- server.go:Deps.AuditStore + Server.obs + 3 路由 GET /admin/observability/{metrics,trace,audit};
ObserveSpan/TraceSink 注入点;middleware logging recordMetric;audit 落库(仅 slog→Append)。
- 前端 Obs.tsx:metrics 接 4 张真实 StatCard+请求分布、trace 接 span 列表(展开 props/events)、
audit 从后端拉(前端内存审计作回退);api.ts/types.ts 加 obs 端点/类型(AdminObservabilityMetrics/
AdminTraceSpan/AuditRecord.id 放宽)。成本指标不伪造(保留'—'+注明)。
- wire.go 收口:Deps.AuditStore 注入 gateway.NewSQLiteAuditStore(ctx, a.db)(审计真落库),
未注入回退内存。
- Docs/api-client.md §2/v0.6。
实机验证:三端点 200,metrics 返回 totalRequests:4/qps:4/perPath/statusCounts:{200:4}。
go build/vet/test 全绿 + 前端 tsc。trace 数据源对接(gw.TraceSink→loop tracer)因 gw/tracer
构造顺序留作可选后置(端点已就绪, 未对接 span 空)。
40 lines
1.3 KiB
Go
40 lines
1.3 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
|
|
)
|
|
|
|
// auditClaims records an admin write with the caller identity read from the
|
|
// request context (populated by auth.Authorize). It is the who/what/when/result
|
|
// audit trail required for admin operations; the "主代理代审" user-facing note is
|
|
// the frontend's job, not the gateway's.
|
|
func (s *Server) auditClaims(r *http.Request, what, result string) {
|
|
who := "anonymous"
|
|
if claims, ok := auth.ClaimsFromContext(r.Context()); ok {
|
|
who = claims.Sub + ":" + string(claims.Role)
|
|
}
|
|
s.audit(r.Context(), who, what, result)
|
|
}
|
|
|
|
// audit emits one structured audit record on the gateway logger and persists it to
|
|
// the configured AuditStore (falling back to an in-memory store when none is
|
|
// injected). A persist failure is logged but never blocks the admin write, so a
|
|
// broken audit store cannot take down the audit trail's primary job.
|
|
func (s *Server) audit(ctx context.Context, who, what, result string) {
|
|
s.log.Info("admin audit",
|
|
"who", who,
|
|
"what", what,
|
|
"when", time.Now().UTC().Format(time.RFC3339),
|
|
"result", result,
|
|
)
|
|
if s.obs != nil && s.obs.audit != nil {
|
|
if err := s.obs.audit.Append(ctx, who, what, result, time.Now()); err != nil {
|
|
s.log.Error("admin audit persist", "err", err, "what", what)
|
|
}
|
|
}
|
|
}
|