mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-20 17:07:57 +00:00
feat: worker 日志 CSV 导出 + 集群日志导出菜单完善
- handleAuditWorkerLogsCsv: 全环 fan-out 收集每节点 frpc 日志, 逐行展开为 CSV (node/worker_key/remote/worker_state/log_line), Excel 可直接过滤/pivot 审计 - gatherClusterWorkerLogs: 提取 JSON 与 CSV 共用的日志聚合逻辑 - 路由: GET /audit/worker-logs.csv (read 级) - ClusterView: 导出 worker 日志改下拉菜单 (JSON/CSV 两格式) - api.ts: downloadAuditCsv 支持 worker-logs 类型 - gofmt: handlers_audit.go 格式对齐
This commit is contained in:
@ -172,6 +172,48 @@ func (h *Handler) handleAuditClusterLogCsv(w http.ResponseWriter, r *http.Reques
|
||||
rows)
|
||||
}
|
||||
|
||||
// handleAuditWorkerLogsCsv serves GET /audit/worker-logs.csv: every alive
|
||||
// node's frpc worker logs, fanned out over the ring and flattened to one row
|
||||
// per LOG LINE (node/worker/state columns repeated) so auditors can filter
|
||||
// and pivot in a spreadsheet. Unreachable nodes contribute an error row.
|
||||
func (h *Handler) handleAuditWorkerLogsCsv(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
methodNotAllowed(w)
|
||||
return
|
||||
}
|
||||
if h.Ring == nil {
|
||||
http.Error(w, "ring engine not enabled", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
_, results := h.gatherClusterWorkerLogs(r)
|
||||
rows := make([][]string, 0, 64)
|
||||
for _, n := range results {
|
||||
if !n.OK || n.Logs == nil {
|
||||
rows = append(rows, []string{n.ID, "", "ERROR", n.Error, ""})
|
||||
continue
|
||||
}
|
||||
for _, wk := range n.Logs.Workers {
|
||||
// One row per line keeps the CSV rectangular; the tail's embedded
|
||||
// newlines would otherwise need multi-line cells (RFC4180 allows
|
||||
// them but Excel's filtering/pivot works better on flat rows).
|
||||
for _, line := range strings.Split(strings.TrimRight(wk.Lines, "\n"), "\n") {
|
||||
if ts, rest, found := strings.Cut(line, "."); found && len(ts) > 10 {
|
||||
line = rest // strip the ANSI-prefixed timestamp prefix noise
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, []string{n.ID, wk.Name, wk.Remote, wk.State, line})
|
||||
}
|
||||
}
|
||||
}
|
||||
writeCSV(w,
|
||||
fmt.Sprintf("audit-worker-logs-%s.csv", time.Now().UTC().Format("20060102-150405")),
|
||||
[]string{"node", "worker_key", "remote", "worker_state", "log_line"},
|
||||
rows)
|
||||
}
|
||||
|
||||
// rawJSON renders the log entry's payload as compact JSON (empty when absent)
|
||||
// for the lossless audit column.
|
||||
func rawJSON(data json.RawMessage) string {
|
||||
|
||||
Reference in New Issue
Block a user