-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdiagnostics.go
More file actions
117 lines (111 loc) · 2.88 KB
/
diagnostics.go
File metadata and controls
117 lines (111 loc) · 2.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package main
import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
func appendDiagnosticLog(event string, detail map[string]any) {
if detail == nil {
detail = map[string]any{}
}
redacted := redactForLog(detail)
record := map[string]any{
"timestamp_ms": time.Now().UnixMilli(),
"pid": os.Getpid(),
"event": event,
"detail": redacted,
}
data, err := json.Marshal(record)
if err != nil {
return
}
path := diagnosticLogPath()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return
}
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return
}
defer file.Close()
_, _ = file.Write(append(data, '\n'))
}
func redactForLog(value any) any {
switch typed := value.(type) {
case map[string]any:
out := map[string]any{}
for key, item := range typed {
lower := strings.ToLower(key)
if strings.Contains(lower, "key") || strings.Contains(lower, "token") || strings.Contains(lower, "authorization") || strings.Contains(lower, "secret") {
out[key] = "[redacted]"
} else {
out[key] = redactForLog(item)
}
}
return out
case []any:
out := make([]any, len(typed))
for i, item := range typed {
out[i] = redactForLog(item)
}
return out
case []string:
out := make([]any, len(typed))
for i, item := range typed {
out[i] = redactForLog(item)
}
return out
case string:
if strings.HasPrefix(typed, "sk-") || strings.HasPrefix(typed, "Bearer ") {
return "[redacted]"
}
return typed
default:
return typed
}
}
func (s *server) readLatestLogs(args map[string]any) commandResult {
request := mapArg(args, "request")
lines := intArg(request, "lines", 200)
path := diagnosticLogPath()
text, err := tailFile(path, lines)
payload := map[string]any{"path": path, "text": text, "lines": lines}
if err != nil {
return failed("读取日志失败:"+err.Error(), payload)
}
return ok("日志已读取。", payload)
}
func tailFile(path string, maxLines int) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
lines := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n")
if maxLines > 0 && len(lines) > maxLines {
lines = lines[len(lines)-maxLines:]
}
return strings.Join(lines, "\n"), nil
}
func (s *server) diagnosticsReport() string {
overview := s.loadOverview()
settings := loadSettings()
report := map[string]any{
"generatedAtMs": time.Now().UnixMilli(),
"version": version,
"overview": map[string]any(overview),
"settings": settings,
"logs": map[string]any{
"diagnosticLogPath": diagnosticLogPath(),
"latestStatusPath": latestStatusPath(),
},
"platform": map[string]any{"os": runtime.GOOS, "arch": runtime.GOARCH},
}
data, err := json.MarshalIndent(report, "", " ")
if err != nil {
return "诊断报告序列化失败:" + err.Error()
}
return string(data)
}