diff --git a/README.md b/README.md index 206c746..3823323 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,8 @@ func init() { // Log as JSON instead of the default ASCII formatter. log.SetFormatter(&log.JSONFormatter{}) - // Output to stdout instead of the default stderr, could also be a file. + // Output to stdout instead of the default stderr + // Can be any io.Writer, see below for File example log.SetOutput(os.Stdout) // Only log the warning severity or above. @@ -138,7 +139,15 @@ var log = logrus.New() func main() { // The API for setting attributes is a little different than the package level // exported logger. See Godoc. - log.Out = os.Stderr + log.Out = os.Stdout + + // You could set this to any `io.Writer` such as a file + // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666) + // if err == nil { + // log.Out = file + // } else { + // log.Info("Failed to log to file, using default stderr") + // } log.WithFields(logrus.Fields{ "animal": "walrus", diff --git a/examples/basic/basic.go b/examples/basic/basic.go index a1623ec..ad703fc 100644 --- a/examples/basic/basic.go +++ b/examples/basic/basic.go @@ -2,6 +2,7 @@ package main import ( "github.com/Sirupsen/logrus" + // "os" ) var log = logrus.New() @@ -9,6 +10,14 @@ var log = logrus.New() func init() { log.Formatter = new(logrus.JSONFormatter) log.Formatter = new(logrus.TextFormatter) // default + + // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666) + // if err == nil { + // log.Out = file + // } else { + // log.Info("Failed to log to file, using default stderr") + // } + log.Level = logrus.DebugLevel } diff --git a/formatter_bench_test.go b/formatter_bench_test.go index c6d290c..d948158 100644 --- a/formatter_bench_test.go +++ b/formatter_bench_test.go @@ -80,11 +80,14 @@ func BenchmarkLargeJSONFormatter(b *testing.B) { } func doBenchmark(b *testing.B, formatter Formatter, fields Fields) { + logger := New() + entry := &Entry{ Time: time.Time{}, Level: InfoLevel, Message: "message", Data: fields, + Logger: logger, } var d []byte var err error diff --git a/terminal_appengine.go b/terminal_appengine.go index 1960169..632ecbe 100644 --- a/terminal_appengine.go +++ b/terminal_appengine.go @@ -3,6 +3,6 @@ package logrus // IsTerminal returns true if stderr's file descriptor is a terminal. -func IsTerminal() bool { +func IsTerminal(f io.Writer) bool { return true } diff --git a/terminal_notwindows.go b/terminal_notwindows.go index 329038f..190297a 100644 --- a/terminal_notwindows.go +++ b/terminal_notwindows.go @@ -9,14 +9,20 @@ package logrus import ( + "io" + "os" "syscall" "unsafe" ) // IsTerminal returns true if stderr's file descriptor is a terminal. -func IsTerminal() bool { - fd := syscall.Stderr +func IsTerminal(f io.Writer) bool { var termios Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 + switch v := f.(type) { + case *os.File: + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 + default: + return false + } } diff --git a/terminal_solaris.go b/terminal_solaris.go index a3c6f6e..f3d6f96 100644 --- a/terminal_solaris.go +++ b/terminal_solaris.go @@ -9,7 +9,13 @@ import ( ) // IsTerminal returns true if the given file descriptor is a terminal. -func IsTerminal() bool { - _, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA) - return err == nil +func IsTerminal(f io.Writer) bool { + var termios Termios + switch v := f.(type) { + case *os.File: + _, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA) + return err == nil + default: + return false + } } diff --git a/terminal_windows.go b/terminal_windows.go index 3727e8a..67be1c0 100644 --- a/terminal_windows.go +++ b/terminal_windows.go @@ -19,9 +19,13 @@ var ( ) // IsTerminal returns true if stderr's file descriptor is a terminal. -func IsTerminal() bool { - fd := syscall.Stderr - var st uint32 - r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) - return r != 0 && e == 0 +func IsTerminal(f io.Writer) bool { + switch v := f.(type) { + case *os.File: + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 + default: + return false + } } diff --git a/text_formatter.go b/text_formatter.go index 076de5d..b4dffa1 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -3,9 +3,9 @@ package logrus import ( "bytes" "fmt" - "runtime" "sort" "strings" + "sync" "time" ) @@ -20,12 +20,10 @@ const ( var ( baseTimestamp time.Time - isTerminal bool ) func init() { baseTimestamp = time.Now() - isTerminal = IsTerminal() } type TextFormatter struct { @@ -50,6 +48,10 @@ type TextFormatter struct { // that log extremely frequently and don't use the JSON formatter this may not // be desired. DisableSorting bool + + // Whether the logger's out is to a terminal + isTerminal bool + terminalOnce sync.Once } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { @@ -70,8 +72,13 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { prefixFieldClashes(entry.Data) - isColorTerminal := isTerminal && (runtime.GOOS != "windows") - isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors + f.terminalOnce.Do(func() { + if entry.Logger != nil { + f.isTerminal = IsTerminal(entry.Logger.Out) + } + }) + + isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors timestampFormat := f.TimestampFormat if timestampFormat == "" {