diff --git a/internal/logger/common.go b/internal/logger/common.go index 8cd118422..f6db8ac43 100644 --- a/internal/logger/common.go +++ b/internal/logger/common.go @@ -98,21 +98,23 @@ import ( // // Different logger types implement different fallback strategies based on their purpose: // -// 1. FileLogger - Stdout Fallback: +// 1. FileLogger - Stderr Fallback: // - Purpose: Operational logs must always be visible -// - Fallback: Redirects to stdout if log directory/file creation fails +// - Fallback: Redirects to stderr if log directory/file creation fails +// (stderr is used, not stdout, to avoid corrupting the stdout +// JSON channel that callers use to receive gateway config output) // - Error: Returns nil (never fails, always provides output) // - Use case: Critical operational messages that must be seen // // Example error handler: // func(err error, logDir, fileName string) (*FileLogger, error) { // log.Printf("WARNING: Failed to initialize log file: %v", err) -// log.Printf("WARNING: Falling back to stdout for logging") +// log.Printf("WARNING: Falling back to stderr for logging") // return &FileLogger{ // logDir: logDir, // fileName: fileName, // useFallback: true, -// logger: log.New(os.Stdout, "", 0), +// logger: log.New(os.Stderr, "", 0), // }, nil // } // diff --git a/internal/logger/file_logger.go b/internal/logger/file_logger.go index f270263e9..2ddc6d01e 100644 --- a/internal/logger/file_logger.go +++ b/internal/logger/file_logger.go @@ -8,7 +8,7 @@ import ( "sync" ) -// FileLogger manages logging to a file with fallback to stdout +// FileLogger manages logging to a file with fallback to stderr type FileLogger struct { lockable logFile *os.File @@ -35,14 +35,16 @@ func setupFileLogger(file *os.File, logDir, fileName string) (*FileLogger, error return fl, nil } -// handleFileLoggerError falls back to stdout when the log file cannot be opened. +// handleFileLoggerError falls back to stderr when the log file cannot be opened. +// Stderr is used (not stdout) to avoid corrupting the stdout JSON channel that +// callers use to receive the gateway configuration output. func handleFileLoggerError(err error, logDir, fileName string) (*FileLogger, error) { - logFallbackWarnings(err, "Failed to initialize log file", "Falling back to stdout for logging") + logFallbackWarnings(err, "Failed to initialize log file", "Falling back to stderr for logging") fl := &FileLogger{ logDir: logDir, fileName: fileName, useFallback: true, - logger: log.New(os.Stdout, "", 0), + logger: log.New(os.Stderr, "", 0), } return fl, nil } @@ -54,7 +56,7 @@ var fileLoggerFactory = loggerFactory[*FileLogger]{ } // InitFileLogger initializes the global file logger -// If the log directory doesn't exist and can't be created, falls back to stdout +// If the log directory doesn't exist and can't be created, falls back to stderr func InitFileLogger(logDir, fileName string) error { logger, err := initLogger(logDir, fileName, os.O_APPEND, fileLoggerFactory) initGlobalLogger(&globalLoggerMu, &globalFileLogger, logger) @@ -103,7 +105,7 @@ func (fl *FileLogger) GetWriter() io.Writer { if fl.logFile != nil { return fl.logFile } - return os.Stdout + return os.Stderr } // Global logging functions that use the global file logger diff --git a/internal/logger/file_logger_test.go b/internal/logger/file_logger_test.go index d3b71af52..07349a3a9 100644 --- a/internal/logger/file_logger_test.go +++ b/internal/logger/file_logger_test.go @@ -200,7 +200,7 @@ func TestFileLoggerFlushes(t *testing.T) { } // TestFileLogger_GetWriter verifies GetWriter returns the underlying file for a real -// logger and os.Stdout for the fallback logger. +// logger and os.Stderr for the fallback logger. func TestFileLogger_GetWriter(t *testing.T) { t.Run("real logger returns file writer", func(t *testing.T) { tmpDir := t.TempDir() @@ -221,7 +221,7 @@ func TestFileLogger_GetWriter(t *testing.T) { assert.True(t, isFile, "GetWriter should return *os.File for real logger") }) - t.Run("fallback logger returns stdout", func(t *testing.T) { + t.Run("fallback logger returns stderr", func(t *testing.T) { err := InitFileLogger("/root/nonexistent/directory", "test.log") require.NoError(t, err) defer CloseGlobalLogger() @@ -233,7 +233,7 @@ func TestFileLogger_GetWriter(t *testing.T) { require.NotNil(t, logger) if logger.useFallback { w := logger.GetWriter() - assert.Equal(t, os.Stdout, w, "Fallback logger GetWriter should return os.Stdout") + assert.Equal(t, os.Stderr, w, "Fallback logger GetWriter should return os.Stderr") } else { t.Skip("System has permissions to write to /root; cannot test fallback path") }