Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions internal/logger/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// }
//
Expand Down
14 changes: 8 additions & 6 deletions internal/logger/file_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions internal/logger/file_logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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")
}
Expand Down
Loading