logger.go

  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("opencode-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}