Files
Cyrene_v2/internal/services/gateway/audit.go
T
Aska e0a543cc37
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(observability): 管理面板可观测性接真实数据——metrics/trace/audit 三端点 + 审计持久化
- 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 空)。
2026-09-06 21:02:46 +08:00

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)
}
}
}