diff --git a/command/app.go b/command/app.go index f10d890e5..95043c457 100644 --- a/command/app.go +++ b/command/app.go @@ -58,6 +58,10 @@ var app = &cli.App{ }, Usage: "log level: (trace, debug, info, error)", }, + &cli.BoolFlag{ + Name: "log-stderr", + Usage: "direct log output to stderr instead of stdout", + }, &cli.BoolFlag{ Name: "install-completion", Usage: "get completion installation instructions for your shell (only available for bash, pwsh, and zsh)", @@ -96,10 +100,11 @@ var app = &cli.App{ workerCount := c.Int("numworkers") printJSON := c.Bool("json") logLevel := c.String("log") + logStderr := c.Bool("log-stderr") isStat := c.Bool("stat") endpointURL := c.String("endpoint-url") - log.Init(logLevel, printJSON) + log.Init(logLevel, printJSON, logStderr) parallel.Init(workerCount) if retryCount < 0 { diff --git a/log/log.go b/log/log.go index 5ec149ac9..1178b12ac 100644 --- a/log/log.go +++ b/log/log.go @@ -18,29 +18,37 @@ var outputCh = make(chan output, 10000) var global *Logger // Init inits global logger. -func Init(level string, json bool) { - global = New(level, json) +func Init(level string, json bool, stderr bool) { + global = New(level, json, stderr) +} + +func LoggerOutFile(logger *Logger) *os.File { + if logger.stderr { + return os.Stderr + } + + return os.Stdout } // Trace prints message in trace mode. func Trace(msg Message) { - global.printf(LevelTrace, msg, os.Stdout) + global.printf(LevelTrace, msg, LoggerOutFile(global)) } // Debug prints message in debug mode. func Debug(msg Message) { - global.printf(LevelDebug, msg, os.Stdout) + global.printf(LevelDebug, msg, LoggerOutFile(global)) } // Info prints message in info mode. func Info(msg Message) { - global.printf(LevelInfo, msg, os.Stdout) + global.printf(LevelInfo, msg, LoggerOutFile(global)) } // Stat prints stat message regardless of the log level with info print formatting. // It uses printfHelper instead of printf to ignore the log level condition. func Stat(msg Message) { - global.printfHelper(LevelInfo, msg, os.Stdout) + global.printfHelper(LevelInfo, msg, LoggerOutFile(global)) } // Error prints message in error mode. @@ -61,15 +69,17 @@ type Logger struct { donech chan struct{} json bool level LogLevel + stderr bool } // New creates new logger. -func New(level string, json bool) *Logger { +func New(level string, json bool, stderr bool) *Logger { logLevel := LevelFromString(level) logger := &Logger{ donech: make(chan struct{}), json: json, level: logLevel, + stderr: stderr, } go logger.out() return logger diff --git a/storage/s3_test.go b/storage/s3_test.go index aee355b1c..f2c552f75 100644 --- a/storage/s3_test.go +++ b/storage/s3_test.go @@ -393,7 +393,7 @@ func TestS3ListContextCancelled(t *testing.T) { } func TestS3Retry(t *testing.T) { - log.Init("debug", false) + log.Init("debug", false, false) testcases := []struct { name string @@ -576,7 +576,7 @@ func TestS3Retry(t *testing.T) { } func TestS3RetryOnNoSuchUpload(t *testing.T) { - log.Init("debug", false) + log.Init("debug", false, false) noSuchUploadError := awserr.New(s3.ErrCodeNoSuchUpload, "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed. status code: 404, request id: PJXXXXX, host id: HOSTIDXX", nil) testcases := []struct { @@ -936,7 +936,7 @@ func TestS3listObjectsV2(t *testing.T) { } func TestSessionCreateAndCachingWithDifferentBuckets(t *testing.T) { - log.Init("error", false) + log.Init("error", false, false) testcases := []struct { bucket string alreadyCreated bool // sessions should not be created again if they already have been created before @@ -1077,7 +1077,7 @@ func TestSessionAutoRegionValidateCredentials(t *testing.T) { } func TestSessionAutoRegion(t *testing.T) { - log.Init("error", false) + log.Init("error", false, false) unitSession := func() *session.Session { return session.Must(session.NewSession(&aws.Config{