feat(observability): 管理面板可观测性接真实数据——metrics/trace/audit 三端点 + 审计持久化
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 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 空)。
This commit is contained in:
Aska committed 2026-09-06 21:02:46 +08:00
1 parent 76134ed47e
commit e0a543cc37
12 files changed
+803 -22

No files matched your search

+21
View File
@@ -3,11 +3,14 @@ import type {
AdminChannelRecord,
AdminIoTDevice,
AdminLlmConfig,
AdminObservabilityMetrics,
AdminPluginRecord,
AdminRagHit,
AdminRagResponse,
AdminTraceSpan,
AdminUserRecord,
ApiErrorShape,
AuditRecord,
ChatSyncResult,
LoginResult,
MemoryRecord,
@@ -269,3 +272,21 @@ export function adminDeleteRag(id: string): Promise<{ id: string }> {
body: JSON.stringify({ id }),
});
}
/** GET /api/v1/admin/observability/metrics — gateway request metrics. */
export function adminObservabilityMetrics(): Promise<AdminObservabilityMetrics> {
return request<AdminObservabilityMetrics>("/admin/observability/metrics");
}
/** GET /api/v1/admin/observability/trace?limit=N — most recent traced spans. */
export function adminObservabilityTrace(limit?: number): Promise<AdminTraceSpan[]> {
return request<AdminTraceSpan[]>(`/admin/observability/trace${limitQuery(limit)}`);
}
/** GET /api/v1/admin/observability/audit?limit=N — most recent audit entries. */
export function adminObservabilityAudit(limit?: number): Promise<AuditRecord[]> {
return request<AuditRecord[]>(`/admin/observability/audit${limitQuery(limit)}`);
}
/** Builds an optional ?limit=N query suffix (empty when no positive limit). */
function limitQuery(limit?: number): string {
return limit && limit > 0 ? `?limit=${limit}` : "";
}
+39 -2
View File
@@ -274,15 +274,52 @@ export interface AdminRagResponse {
hits?: AdminRagHit[];
}
/** One audit entry: who / what / when / result. */
/** One audit entry: who / what / when / result. `id` is a string for the local
* (client) trail but a number for backend-persisted entries. */
export interface AuditRecord {
id: string;
id: string | number;
who: string;
what: string;
when: string;
result: string;
}
/** Gateway request metrics (GET /api/v1/admin/observability/metrics). Token/cost
* is intentionally absent: the gateway does not track spend, and a fabricated
* number would violate the "不伪造指标" red line. */
export interface AdminObservabilityMetrics {
totalRequests: number;
qps: number;
avgLatencyMs: number;
maxLatencyMs: number;
perMethod: Record<string, number>;
perPath: Record<string, number>;
statusCounts: Record<string, number>;
llmCalls: number;
subagentEvents: number;
}
/** One traced span (GET /api/v1/admin/observability/trace). */
export interface AdminTraceSpan {
id: string;
parentId?: string;
name: string;
kind: string;
status: string;
startedAt: string;
endedAt?: string;
durationNs: number;
props?: Record<string, unknown>;
events?: TraceEvent[];
}
/** A fine-grained event attached to a span. */
export interface TraceEvent {
time: string;
type: string;
props?: Record<string, unknown>;
}
/** The owner profile (mirrors configs/users/owner.yaml). */
export interface OwnerProfile {
id: string;