From d55926d8d6324ecc7fe583035c9b8dee8820e6c4 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Thu, 2 Jun 2022 22:04:12 -0700 Subject: [PATCH] V8.0.0 rc7 (#45) - Removed ability to remove individual log levels. Can use AddHandler and RemoveHandler to accomplish the same. Will leave it this way unless there's a compelling reason to expose this functionality. - Move std log redirect to top level our of console handler. - Add error and warning checks for std log output - fix std log redirect reset --- _examples/basic/main.go | 7 +++++ benchmarks/benchmark_test.go | 2 +- default_logger.go | 59 +--------------------------------- default_logger_test.go | 2 +- log.go | 61 +++++++++++++++++++++++++++++++++--- log_test.go | 8 ++--- 6 files changed, 71 insertions(+), 68 deletions(-) diff --git a/_examples/basic/main.go b/_examples/basic/main.go index 029aa15..83d0a06 100644 --- a/_examples/basic/main.go +++ b/_examples/basic/main.go @@ -2,12 +2,15 @@ package main import ( "io" + stdlog "log" "github.com/go-playground/errors/v5" "github.com/go-playground/log/v8" ) func main() { + log.RedirectGoStdLog(true) + // Trace defer log.WithTrace().Info("time to run") @@ -44,4 +47,8 @@ func main() { ) logger.WithField("key", "value").Info("test") + + stdlog.Println("This was redirected from Go STD output!") + log.RedirectGoStdLog(false) + stdlog.Println("This was NOT redirected from Go STD output!") } diff --git a/benchmarks/benchmark_test.go b/benchmarks/benchmark_test.go index 25f5483..f041989 100644 --- a/benchmarks/benchmark_test.go +++ b/benchmarks/benchmark_test.go @@ -30,7 +30,7 @@ var _jane = user{ // annoying because you have to manipulate the TestMain before // running the benchmark you want. func TestMain(m *testing.M) { - cLog := log.NewConsoleBuilder().WithGoSTDErrLogs(false).WithWriter(ioutil.Discard).Build() + cLog := log.NewConsoleBuilder().WithWriter(ioutil.Discard).Build() log.AddHandler(cLog, log.AllLevels...) os.Exit(m.Run()) } diff --git a/default_logger.go b/default_logger.go index de78486..a873e72 100644 --- a/default_logger.go +++ b/default_logger.go @@ -1,10 +1,8 @@ package log import ( - "bufio" "fmt" "io" - stdlog "log" "os" "strconv" "sync" @@ -22,7 +20,6 @@ const ( type ConsoleBuilder struct { writer io.Writer timestampFormat string - redirect bool } // NewConsoleBuilder creates a new ConsoleBuilder for configuring and creating a new console logger @@ -30,15 +27,9 @@ func NewConsoleBuilder() *ConsoleBuilder { return &ConsoleBuilder{ writer: os.Stderr, timestampFormat: DefaultTimeFormat, - redirect: true, } } -func (b *ConsoleBuilder) WithGoSTDErrLogs(redirect bool) *ConsoleBuilder { - b.redirect = redirect - return b -} - func (b *ConsoleBuilder) WithWriter(writer io.Writer) *ConsoleBuilder { b.writer = writer return b @@ -50,44 +41,19 @@ func (b *ConsoleBuilder) WithTimestampFormat(format string) *ConsoleBuilder { } func (b *ConsoleBuilder) Build() *Logger { - c := &Logger{ + return &Logger{ writer: b.writer, timestampFormat: b.timestampFormat, } - if b.redirect { - ready := make(chan struct{}) - go c.handleStdLogger(ready) - <-ready // have to wait, it was running too quickly and some messages can be lost - } - return c } // Logger is an instance of the console logger type Logger struct { m sync.Mutex writer io.Writer - r *io.PipeReader timestampFormat string } -// this will redirect the output of -func (c *Logger) handleStdLogger(ready chan<- struct{}) { - var w *io.PipeWriter - c.r, w = io.Pipe() - stdlog.SetOutput(w) - - scanner := bufio.NewScanner(c.r) - go func() { - close(ready) - }() - - for scanner.Scan() { - WithField("stdlog", true).Notice(scanner.Text()) - } - _ = c.r.Close() - _ = w.Close() -} - // Log handles the log entry func (c *Logger) Log(e Entry) { var lvl string @@ -152,26 +118,3 @@ func (c *Logger) Log(e Entry) { BytePool().Put(buff) } - -// Close cleans up any resources and de-registers the handler with the logger -func (c *Logger) Close() error { - RemoveHandler(c) - // reset the output back to original - // since we reset the output prior to closing we don't have to wait - stdlog.SetOutput(os.Stderr) - if c.r != nil { - _ = c.r.Close() - } - return nil -} - -func (c *Logger) closeAlreadyLocked() error { - removeHandler(c) - // reset the output back to original - // since we reset the output prior to closing we don't have to wait - stdlog.SetOutput(os.Stderr) - if c.r != nil { - _ = c.r.Close() - } - return nil -} diff --git a/default_logger_test.go b/default_logger_test.go index 6e45bca..f337132 100644 --- a/default_logger_test.go +++ b/default_logger_test.go @@ -27,7 +27,6 @@ func TestConsoleLogger(t *testing.T) { cLog := NewConsoleBuilder().WithWriter(buff).WithTimestampFormat("").Build() AddHandler(cLog, AllLevels...) - defer func() { _ = cLog.Close() }() for i, tt := range tests { buff.Reset() @@ -90,6 +89,7 @@ func TestConsoleLogger(t *testing.T) { func TestConsoleSTDLogCapturing(t *testing.T) { buff := new(buffer) + RedirectGoStdLog(true) cLog := NewConsoleBuilder().WithWriter(buff).WithTimestampFormat("MST").Build() AddHandler(cLog, AllLevels...) diff --git a/log.go b/log.go index aabb442..c1718b8 100644 --- a/log.go +++ b/log.go @@ -1,8 +1,12 @@ package log import ( + "bufio" "context" + "io" + stdlog "log" "os" + "strings" "sync" "time" @@ -44,7 +48,9 @@ var ( }{ name: "log", } - rw sync.RWMutex + rw sync.RWMutex + stdLogWriter *io.PipeWriter + redirectComplete chan struct{} ) // Field is a single Field key and value @@ -53,6 +59,54 @@ type Field struct { Value interface{} `json:"value"` } +// RedirectGoStdLog is used to redirect Go's internal std log output to this logger. +func RedirectGoStdLog(redirect bool) { + if (redirect && stdLogWriter != nil) || (!redirect && stdLogWriter == nil) { + // already redirected or already not redirected + return + } + if !redirect { + stdlog.SetOutput(os.Stderr) + // will stop scanner reading PipeReader + _ = stdLogWriter.Close() + stdLogWriter = nil + <-redirectComplete + return + } + + ready := make(chan struct{}) + redirectComplete = make(chan struct{}) + + // last option is to redirect + go func() { + var r *io.PipeReader + r, stdLogWriter = io.Pipe() + defer func() { + _ = r.Close() + }() + + stdlog.SetOutput(stdLogWriter) + defer func() { + close(redirectComplete) + redirectComplete = nil + }() + + scanner := bufio.NewScanner(r) + close(ready) + for scanner.Scan() { + txt := scanner.Text() + if strings.Contains(txt, "error") { + WithField("stdlog", true).Error(txt) + } else if strings.Contains(txt, "warning") { + WithField("stdlog", true).Warn(txt) + } else { + WithField("stdlog", true).Notice(txt) + } + } + }() + <-ready +} + // SetExitFunc sets the provided function as the exit function used in Fatal(), // Fatalf(), Panic() and Panicf(). This is primarily used when wrapping this library, // you can set this to enable testing (with coverage) of your Fatal() and Fatalf() @@ -116,7 +170,6 @@ func AddHandler(h Handler, levels ...Level) { defer rw.Unlock() if defaultHandler != nil { removeHandler(h) - _ = defaultHandler.closeAlreadyLocked() defaultHandler = nil } for _, level := range levels { @@ -149,9 +202,9 @@ OUTER: } } -// RemoveHandlerLevels removes the supplied levels, if no more levels exists for the handler +// removeHandlerLevels removes the supplied levels, if no more levels exists for the handler // it will no longer be registered and need to added via AddHandler again. -func RemoveHandlerLevels(h Handler, levels ...Level) { +func removeHandlerLevels(h Handler, levels ...Level) { rw.Lock() defer rw.Unlock() OUTER: diff --git a/log_test.go b/log_test.go index a5f3bfc..858e327 100644 --- a/log_test.go +++ b/log_test.go @@ -1038,27 +1038,27 @@ func TestRemoveHandlerLevels(t *testing.T) { } logHandlers = map[Level][]Handler{} AddHandler(th, InfoLevel) - RemoveHandlerLevels(th, InfoLevel) + removeHandlerLevels(th, InfoLevel) if len(logHandlers) != 0 { t.Error("expected 0 handlers") } AddHandler(th, InfoLevel) AddHandler(th2, InfoLevel) - RemoveHandlerLevels(th, InfoLevel) + removeHandlerLevels(th, InfoLevel) if len(logHandlers) != 1 { t.Error("expected 1 handlers left") } if len(logHandlers[InfoLevel]) != 1 { t.Error("expected 1 handlers with InfoLevel left") } - RemoveHandlerLevels(th2, InfoLevel) + removeHandlerLevels(th2, InfoLevel) if len(logHandlers) != 0 { t.Error("expected 0 handlers") } AddHandler(th, AllLevels...) - RemoveHandlerLevels(th, DebugLevel) + removeHandlerLevels(th, DebugLevel) if len(logHandlers) != 7 { t.Error("expected 7 log levels left") }