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 {
|
type Logger struct {
|
||||||
file *os.File
|
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 {
|
type LogEntry struct {
|
||||||
|
|
@ -116,12 +124,22 @@ 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)
|
jsonData, err := json.Marshal(entry)
|
||||||
if err == nil {
|
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
|
var fieldStr string
|
||||||
if len(fields) > 0 {
|
if len(fields) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -137,3 +142,31 @@ func TestLoggerHelperFunctions(t *testing.T) {
|
||||||
DebugC("test", "Debug with component")
|
DebugC("test", "Debug with component")
|
||||||
WarnF("Warning with fields", map[string]any{"key": "value"})
|
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