1package logging
2
3import (
4 "fmt"
5 "log/slog"
6 "os"
7 // "path/filepath"
8 "encoding/json"
9 "runtime"
10 "runtime/debug"
11 "sync"
12 "time"
13)
14
15func getCaller() string {
16 var caller string
17 if _, file, line, ok := runtime.Caller(2); ok {
18 // caller = fmt.Sprintf("%s:%d", filepath.Base(file), line)
19 caller = fmt.Sprintf("%s:%d", file, line)
20 } else {
21 caller = "unknown"
22 }
23 return caller
24}
25func Info(msg string, args ...any) {
26 source := getCaller()
27 slog.Info(msg, append([]any{"source", source}, args...)...)
28}
29
30func Debug(msg string, args ...any) {
31 // slog.Debug(msg, args...)
32 source := getCaller()
33 slog.Debug(msg, append([]any{"source", source}, args...)...)
34}
35
36func Warn(msg string, args ...any) {
37 slog.Warn(msg, args...)
38}
39
40func Error(msg string, args ...any) {
41 slog.Error(msg, args...)
42}
43
44func InfoPersist(msg string, args ...any) {
45 args = append(args, persistKeyArg, true)
46 slog.Info(msg, args...)
47}
48
49func DebugPersist(msg string, args ...any) {
50 args = append(args, persistKeyArg, true)
51 slog.Debug(msg, args...)
52}
53
54func WarnPersist(msg string, args ...any) {
55 args = append(args, persistKeyArg, true)
56 slog.Warn(msg, args...)
57}
58
59func ErrorPersist(msg string, args ...any) {
60 args = append(args, persistKeyArg, true)
61 slog.Error(msg, args...)
62}
63
64// RecoverPanic is a common function to handle panics gracefully.
65// It logs the error, creates a panic log file with stack trace,
66// and executes an optional cleanup function before returning.
67func RecoverPanic(name string, cleanup func()) {
68 if r := recover(); r != nil {
69 // Log the panic
70 ErrorPersist(fmt.Sprintf("Panic in %s: %v", name, r))
71
72 // Create a timestamped panic log file
73 timestamp := time.Now().Format("20060102-150405")
74 filename := fmt.Sprintf("crush-panic-%s-%s.log", name, timestamp)
75
76 file, err := os.Create(filename)
77 if err != nil {
78 ErrorPersist(fmt.Sprintf("Failed to create panic log: %v", err))
79 } else {
80 defer file.Close()
81
82 // Write panic information and stack trace
83 fmt.Fprintf(file, "Panic in %s: %v\n\n", name, r)
84 fmt.Fprintf(file, "Time: %s\n\n", time.Now().Format(time.RFC3339))
85 fmt.Fprintf(file, "Stack Trace:\n%s\n", debug.Stack())
86
87 InfoPersist(fmt.Sprintf("Panic details written to %s", filename))
88 }
89
90 // Execute cleanup function if provided
91 if cleanup != nil {
92 cleanup()
93 }
94 }
95}
96
97// Message Logging for Debug
98var MessageDir string
99
100func GetSessionPrefix(sessionId string) string {
101 return sessionId[:8]
102}
103
104var sessionLogMutex sync.Mutex
105
106func AppendToSessionLogFile(sessionId string, filename string, content string) string {
107 if MessageDir == "" || sessionId == "" {
108 return ""
109 }
110 sessionPrefix := GetSessionPrefix(sessionId)
111
112 sessionLogMutex.Lock()
113 defer sessionLogMutex.Unlock()
114
115 sessionPath := fmt.Sprintf("%s/%s", MessageDir, sessionPrefix)
116 if _, err := os.Stat(sessionPath); os.IsNotExist(err) {
117 if err := os.MkdirAll(sessionPath, 0o766); err != nil {
118 Error("Failed to create session directory", "dirpath", sessionPath, "error", err)
119 return ""
120 }
121 }
122
123 filePath := fmt.Sprintf("%s/%s", sessionPath, filename)
124
125 f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
126 if err != nil {
127 Error("Failed to open session log file", "filepath", filePath, "error", err)
128 return ""
129 }
130 defer f.Close()
131
132 // Append chunk to file
133 _, err = f.WriteString(content)
134 if err != nil {
135 Error("Failed to write chunk to session log file", "filepath", filePath, "error", err)
136 return ""
137 }
138 return filePath
139}
140
141func WriteRequestMessageJson(sessionId string, requestSeqId int, message any) string {
142 if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
143 return ""
144 }
145 msgJson, err := json.Marshal(message)
146 if err != nil {
147 Error("Failed to marshal message", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
148 return ""
149 }
150 return WriteRequestMessage(sessionId, requestSeqId, string(msgJson))
151}
152
153func WriteRequestMessage(sessionId string, requestSeqId int, message string) string {
154 if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
155 return ""
156 }
157 filename := fmt.Sprintf("%d_request.json", requestSeqId)
158
159 return AppendToSessionLogFile(sessionId, filename, message)
160}
161
162func AppendToStreamSessionLogJson(sessionId string, requestSeqId int, jsonableChunk any) string {
163 if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
164 return ""
165 }
166 chunkJson, err := json.Marshal(jsonableChunk)
167 if err != nil {
168 Error("Failed to marshal message", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
169 return ""
170 }
171 return AppendToStreamSessionLog(sessionId, requestSeqId, string(chunkJson))
172}
173
174func AppendToStreamSessionLog(sessionId string, requestSeqId int, chunk string) string {
175 if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
176 return ""
177 }
178 filename := fmt.Sprintf("%d_response_stream.log", requestSeqId)
179 return AppendToSessionLogFile(sessionId, filename, chunk)
180}
181
182func WriteChatResponseJson(sessionId string, requestSeqId int, response any) string {
183 if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
184 return ""
185 }
186 responseJson, err := json.Marshal(response)
187 if err != nil {
188 Error("Failed to marshal response", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
189 return ""
190 }
191 filename := fmt.Sprintf("%d_response.json", requestSeqId)
192
193 return AppendToSessionLogFile(sessionId, filename, string(responseJson))
194}
195
196func WriteToolResultsJson(sessionId string, requestSeqId int, toolResults any) string {
197 if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
198 return ""
199 }
200 toolResultsJson, err := json.Marshal(toolResults)
201 if err != nil {
202 Error("Failed to marshal tool results", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
203 return ""
204 }
205 filename := fmt.Sprintf("%d_tool_results.json", requestSeqId)
206 return AppendToSessionLogFile(sessionId, filename, string(toolResultsJson))
207}