fix: add write mutex for log writing to file
This commit is contained in:
parent
cadcdc0b41
commit
a675c9ba33
2 changed files with 53 additions and 2 deletions
|
|
@ -38,6 +38,14 @@ var (
|
|||
|
||||
type Logger struct {
|
||||
file *os.File
|
||||
|
||||
// writeMu serializes file writes.
|
||||
// Although POSIX systems typically guarantee atomicity
|
||||
// for single write() syscalls on regular files,
|
||||
// Windows and some filesystems may not.
|
||||
//
|
||||
// This prevents log line interleaving across platforms.
|
||||
writeMu sync.Mutex
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
|
|
@ -116,13 +124,23 @@ func logMessage(level LogLevel, component string, message string, fields map[str
|
|||
}
|
||||
}
|
||||
|
||||
if logger.file != nil {
|
||||
mu.RLock()
|
||||
fileptr := logger.file
|
||||
|
||||
if fileptr != nil {
|
||||
jsonData, err := json.Marshal(entry)
|
||||
if err == nil {
|
||||
logger.file.Write(append(jsonData, '\n'))
|
||||
logger.writeMu.Lock()
|
||||
_, err = fileptr.WriteString(string(jsonData) + "\n")
|
||||
logger.writeMu.Unlock()
|
||||
if err != nil {
|
||||
log.Println("Failed to write to file:", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mu.RUnlock()
|
||||
|
||||
var fieldStr string
|
||||
if len(fields) > 0 {
|
||||
fieldStr = " " + formatFields(fields)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -137,3 +142,31 @@ func TestLoggerHelperFunctions(t *testing.T) {
|
|||
DebugC("test", "Debug with component")
|
||||
WarnF("Warning with fields", map[string]any{"key": "value"})
|
||||
}
|
||||
|
||||
// TestConcurrentFileLogging validates concurrent file writes are race-safe and complete.
|
||||
// Run: go test -race ./pkg/logger -run TestConcurrentFileLogging -count=1
|
||||
func TestConcurrentFileLogging(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
logFile := filepath.Join(dir, "test.log")
|
||||
if err := EnableFileLogging(logFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer DisableFileLogging()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(n int) {
|
||||
defer wg.Done()
|
||||
InfoCF("test", fmt.Sprintf("msg-%d", n), map[string]any{"n": n})
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
data, err := os.ReadFile(logFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
if len(lines) != 100 {
|
||||
t.Errorf("expected 100 log lines, got %d", len(lines))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue