diff --git a/exec/exec_test.go b/exec/exec_test.go index 8519468..d5a498e 100644 --- a/exec/exec_test.go +++ b/exec/exec_test.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strings" + "sync" "testing" "time" @@ -138,6 +139,29 @@ func TestSetDefaultLogger(t *testing.T) { } } +func TestDefaultLogger_IsConcurrentSafe(t *testing.T) { + original := exec.DefaultLogger() + defer exec.SetDefaultLogger(original) + + logger := &mockLogger{} + + var wg sync.WaitGroup + for i := 0; i < 32; i++ { + wg.Add(2) + go func() { + defer wg.Done() + exec.SetDefaultLogger(logger) + }() + go func() { + defer wg.Done() + _ = exec.DefaultLogger() + }() + } + wg.Wait() + + assert.NotNil(t, exec.DefaultLogger()) +} + func TestCommand_UsesDefaultLogger(t *testing.T) { original := exec.DefaultLogger() defer exec.SetDefaultLogger(original) diff --git a/exec/logger.go b/exec/logger.go index 0340710..5ff59e6 100644 --- a/exec/logger.go +++ b/exec/logger.go @@ -1,5 +1,7 @@ package exec +import "sync" + // Logger interface for command execution logging. // Compatible with pkg/log.Logger and other structured loggers. type Logger interface { @@ -26,7 +28,10 @@ func (NopLogger) Error(string, ...any) {} var _ Logger = NopLogger{} -var defaultLogger Logger = NopLogger{} +var ( + defaultLoggerMu sync.RWMutex + defaultLogger Logger = NopLogger{} +) // SetDefaultLogger sets the package-level default logger. // Commands without an explicit logger will use this. @@ -35,6 +40,9 @@ var defaultLogger Logger = NopLogger{} // // exec.SetDefaultLogger(logger) func SetDefaultLogger(l Logger) { + defaultLoggerMu.Lock() + defer defaultLoggerMu.Unlock() + if l == nil { l = NopLogger{} } @@ -47,5 +55,8 @@ func SetDefaultLogger(l Logger) { // // logger := exec.DefaultLogger() func DefaultLogger() Logger { + defaultLoggerMu.RLock() + defer defaultLoggerMu.RUnlock() + return defaultLogger }