fix: resolve Gemini review findings — race conditions and error handling

- error.go: appendReport now mutex-protected, handles JSON errors, uses 0600 perms
- log.go: keyvals slice copied before mutation to prevent caller data races
- log.go: defaultLog uses atomic.Pointer for thread-safe replacement

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-18 09:20:10 +00:00
parent 8199727537
commit 2525d10515
2 changed files with 28 additions and 9 deletions

View file

@ -16,6 +16,7 @@ import (
"runtime"
"runtime/debug"
"strings"
"sync"
"time"
)
@ -390,14 +391,21 @@ func (h *ErrPan) Reports(n int) ([]CrashReport, error) {
return reports[len(reports)-n:], nil
}
func (h *ErrPan) appendReport(report CrashReport) {
var reports []CrashReport
var crashMu sync.Mutex
func (h *ErrPan) appendReport(report CrashReport) {
crashMu.Lock()
defer crashMu.Unlock()
var reports []CrashReport
if data, err := os.ReadFile(h.filePath); err == nil {
json.Unmarshal(data, &reports)
if err := json.Unmarshal(data, &reports); err != nil {
reports = nil
}
}
reports = append(reports, report)
data, _ := json.MarshalIndent(reports, "", " ")
os.WriteFile(h.filePath, data, 0644)
if data, err := json.MarshalIndent(reports, "", " "); err == nil {
_ = os.WriteFile(h.filePath, data, 0600)
}
}

View file

@ -12,6 +12,7 @@ import (
"os/user"
"slices"
"sync"
"sync/atomic"
"time"
)
@ -175,6 +176,9 @@ func (l *Log) log(level Level, prefix, msg string, keyvals ...any) {
timestamp := styleTimestamp(time.Now().Format("15:04:05"))
// Copy keyvals to avoid mutating the caller's slice
keyvals = append([]any(nil), keyvals...)
// Automatically extract context from error if present in keyvals
origLen := len(keyvals)
for i := 0; i < origLen; i += 2 {
@ -294,16 +298,23 @@ func Username() string {
// --- Default logger ---
var defaultLog = NewLog(LogOpts{Level: LevelInfo})
var defaultLogPtr atomic.Pointer[Log]
func init() {
l := NewLog(LogOpts{Level: LevelInfo})
defaultLogPtr.Store(l)
}
var defaultLog = defaultLogPtr.Load()
// Default returns the default logger.
func Default() *Log {
return defaultLog
return defaultLogPtr.Load()
}
// SetDefault sets the default logger.
// SetDefault sets the default logger (thread-safe).
func SetDefault(l *Log) {
defaultLog = l
defaultLogPtr.Store(l)
}
// SetLevel sets the default logger's level.