Make MultiLevelWriter resilient to individual log failure (#282)
Fixes #281 Currently MultiLevelWriter would give up if any individual backing logger failed. This could result in no logging ever happening if the bad logger was set up in the first position. As a motivating factor, this can happen in "normal" circumstances, such as running as a Windows Service where stderr is not available and therefore the ConsoleWriter will fail. If you happen to set it as the first logger, then no logging ever takes place. To make matters worse, connecting a debugger creates an stderr, so it made it a pretty daunting situation to overcome. The proposed solution is to go through all underlying loggers and return the last error obtained, if any. In practice there isn't much being done with those errors anyway, as the best way to address logging errors is to hook a ErrorHandler, which will still be triggered despite this change. Co-authored-by: Nuno Diegues <nuno@cloudflare.com>
This commit is contained in:
parent
4f50ae2ed0
commit
a8f5328bb7
28
writer.go
28
writer.go
|
@ -56,30 +56,30 @@ type multiLevelWriter struct {
|
||||||
|
|
||||||
func (t multiLevelWriter) Write(p []byte) (n int, err error) {
|
func (t multiLevelWriter) Write(p []byte) (n int, err error) {
|
||||||
for _, w := range t.writers {
|
for _, w := range t.writers {
|
||||||
n, err = w.Write(p)
|
if _n, _err := w.Write(p); err == nil {
|
||||||
if err != nil {
|
n = _n
|
||||||
return
|
if _err != nil {
|
||||||
}
|
err = _err
|
||||||
if n != len(p) {
|
} else if _n != len(p) {
|
||||||
err = io.ErrShortWrite
|
err = io.ErrShortWrite
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(p), nil
|
}
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) {
|
func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) {
|
||||||
for _, w := range t.writers {
|
for _, w := range t.writers {
|
||||||
n, err = w.WriteLevel(l, p)
|
if _n, _err := w.WriteLevel(l, p); err == nil {
|
||||||
if err != nil {
|
n = _n
|
||||||
return
|
if _err != nil {
|
||||||
}
|
err = _err
|
||||||
if n != len(p) {
|
} else if _n != len(p) {
|
||||||
err = io.ErrShortWrite
|
err = io.ErrShortWrite
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(p), nil
|
}
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultiLevelWriter creates a writer that duplicates its writes to all the
|
// MultiLevelWriter creates a writer that duplicates its writes to all the
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
package zerolog
|
package zerolog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -27,3 +29,85 @@ func TestMultiSyslogWriter(t *testing.T) {
|
||||||
t.Errorf("Invalid syslog message routing: want %v, got %v", want, got)
|
t.Errorf("Invalid syslog message routing: want %v, got %v", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var writeCalls int
|
||||||
|
|
||||||
|
type mockedWriter struct {
|
||||||
|
wantErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c mockedWriter) Write(p []byte) (int, error) {
|
||||||
|
writeCalls++
|
||||||
|
|
||||||
|
if c.wantErr {
|
||||||
|
return -1, errors.New("Expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that a new writer is only used if it actually works.
|
||||||
|
func TestResilientMultiWriter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
writers []io.Writer
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "All valid writers",
|
||||||
|
writers: []io.Writer{
|
||||||
|
mockedWriter {
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
mockedWriter {
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "All invalid writers",
|
||||||
|
writers: []io.Writer{
|
||||||
|
mockedWriter {
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
mockedWriter {
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "First invalid writer",
|
||||||
|
writers: []io.Writer{
|
||||||
|
mockedWriter {
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
mockedWriter {
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "First valid writer",
|
||||||
|
writers: []io.Writer{
|
||||||
|
mockedWriter {
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
mockedWriter {
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
writers := tt.writers
|
||||||
|
multiWriter := MultiLevelWriter(writers...)
|
||||||
|
|
||||||
|
logger := New(multiWriter).With().Timestamp().Logger().Level(InfoLevel)
|
||||||
|
logger.Info().Msg("Test msg")
|
||||||
|
|
||||||
|
if len(writers) != writeCalls {
|
||||||
|
t.Errorf("Expected %d writers to have been called but only %d were.", len(writers), writeCalls)
|
||||||
|
}
|
||||||
|
writeCalls = 0
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue