From 1726e1744a80b3d1f736a74b8b617380ade59a4c Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sun, 5 Feb 2017 09:21:03 -0500 Subject: [PATCH 1/3] text_formatter: detect tty based on fd --- terminal_appengine.go | 2 +- terminal_notwindows.go | 14 ++++++++++---- terminal_solaris.go | 12 +++++++++--- terminal_windows.go | 14 +++++++++----- text_formatter.go | 15 ++++++++++----- 5 files changed, 39 insertions(+), 18 deletions(-) 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..943394c 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(f.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..f75e13e 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -3,7 +3,6 @@ package logrus import ( "bytes" "fmt" - "runtime" "sort" "strings" "time" @@ -20,12 +19,10 @@ const ( var ( baseTimestamp time.Time - isTerminal bool ) func init() { baseTimestamp = time.Now() - isTerminal = IsTerminal() } type TextFormatter struct { @@ -50,6 +47,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 + terminalDetermined bool } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { @@ -70,8 +71,12 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { prefixFieldClashes(entry.Data) - isColorTerminal := isTerminal && (runtime.GOOS != "windows") - isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors + if !f.terminalDetermined { + f.isTerminal = IsTerminal(entry.Logger.Out) + f.terminalDetermined = true + } + + isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors timestampFormat := f.TimestampFormat if timestampFormat == "" { From 11fbf0fa42b837cdd5b234470b53d03bad9e5041 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sun, 5 Feb 2017 19:10:19 -0500 Subject: [PATCH 2/3] text_formatter: fix race --- examples/basic/basic.go | 1 + formatter_bench_test.go | 3 +++ text_formatter.go | 14 ++++++++------ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/basic/basic.go b/examples/basic/basic.go index a1623ec..ea7e551 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() 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/text_formatter.go b/text_formatter.go index f75e13e..b4dffa1 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -5,6 +5,7 @@ import ( "fmt" "sort" "strings" + "sync" "time" ) @@ -49,8 +50,8 @@ type TextFormatter struct { DisableSorting bool // Whether the logger's out is to a terminal - isTerminal bool - terminalDetermined bool + isTerminal bool + terminalOnce sync.Once } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { @@ -71,10 +72,11 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { prefixFieldClashes(entry.Data) - if !f.terminalDetermined { - f.isTerminal = IsTerminal(entry.Logger.Out) - f.terminalDetermined = true - } + f.terminalOnce.Do(func() { + if entry.Logger != nil { + f.isTerminal = IsTerminal(entry.Logger.Out) + } + }) isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors From 141e6dc6a6809c83c4fba8fecb687fd2776de5ed Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Mon, 6 Feb 2017 19:16:20 -0500 Subject: [PATCH 3/3] readme: update with example of logging to file --- README.md | 13 +++++++++++-- examples/basic/basic.go | 10 +++++++++- terminal_solaris.go | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) 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 ea7e551..ad703fc 100644 --- a/examples/basic/basic.go +++ b/examples/basic/basic.go @@ -2,7 +2,7 @@ package main import ( "github.com/Sirupsen/logrus" - "os" + // "os" ) var log = logrus.New() @@ -10,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/terminal_solaris.go b/terminal_solaris.go index 943394c..f3d6f96 100644 --- a/terminal_solaris.go +++ b/terminal_solaris.go @@ -13,7 +13,7 @@ func IsTerminal(f io.Writer) bool { var termios Termios switch v := f.(type) { case *os.File: - _, err := unix.IoctlGetTermios(int(f.Fd()), unix.TCGETA) + _, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA) return err == nil default: return false