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