logger.go

  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}