Skip to content

Commit c1ab4ed

Browse files
authored
Make Log.Fatal() call Close on the writer before os.Exit(1) (#634)
1 parent 417580d commit c1ab4ed

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

diode/diode_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"log"
88
"os"
9+
"os/exec"
910
"testing"
1011
"time"
1112

@@ -38,6 +39,43 @@ func TestClose(t *testing.T) {
3839
w.Close()
3940
}
4041

42+
func TestFatal(t *testing.T) {
43+
if os.Getenv("TEST_FATAL") == "1" {
44+
w := diode.NewWriter(os.Stderr, 1000, 0, func(missed int) {
45+
fmt.Printf("Dropped %d messages\n", missed)
46+
})
47+
defer w.Close()
48+
log := zerolog.New(w)
49+
log.Fatal().Msg("test")
50+
return
51+
}
52+
53+
cmd := exec.Command(os.Args[0], "-test.run=TestFatal")
54+
cmd.Env = append(os.Environ(), "TEST_FATAL=1")
55+
stderr, err := cmd.StderrPipe()
56+
if err != nil {
57+
t.Fatal(err)
58+
}
59+
err = cmd.Start()
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
slurp, err := io.ReadAll(stderr)
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
err = cmd.Wait()
68+
if err == nil {
69+
t.Error("Expected log.Fatal to exit with non-zero status")
70+
}
71+
72+
want := "{\"level\":\"fatal\",\"message\":\"test\"}\n"
73+
got := string(slurp)
74+
if got != want {
75+
t.Errorf("Diode Fatal Test failed. got:%s, want:%s!", got, want)
76+
}
77+
}
78+
4179
func Benchmark(b *testing.B) {
4280
log.SetOutput(io.Discard)
4381
defer log.SetOutput(os.Stderr)

log.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,14 @@ func (l *Logger) Err(err error) *Event {
387387
//
388388
// You must call Msg on the returned event in order to send the event.
389389
func (l *Logger) Fatal() *Event {
390-
return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) })
390+
return l.newEvent(FatalLevel, func(msg string) {
391+
if closer, ok := l.w.(io.Closer); ok {
392+
// Close the writer to flush any buffered message. Otherwise the message
393+
// will be lost as os.Exit() terminates the program immediately.
394+
closer.Close()
395+
}
396+
os.Exit(1)
397+
})
391398
}
392399

393400
// Panic starts a new message with panic level. The panic() function

writer.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ func (lw LevelWriterAdapter) WriteLevel(l Level, p []byte) (n int, err error) {
2727
return lw.Write(p)
2828
}
2929

30+
func (lw LevelWriterAdapter) Close() error {
31+
if closer, ok := lw.Writer.(io.Closer); ok {
32+
return closer.Close()
33+
}
34+
return nil
35+
}
36+
3037
type syncWriter struct {
3138
mu sync.Mutex
3239
lw LevelWriter
@@ -57,6 +64,15 @@ func (s *syncWriter) WriteLevel(l Level, p []byte) (n int, err error) {
5764
return s.lw.WriteLevel(l, p)
5865
}
5966

67+
func (s *syncWriter) Close() error {
68+
s.mu.Lock()
69+
defer s.mu.Unlock()
70+
if closer, ok := s.lw.(io.Closer); ok {
71+
return closer.Close()
72+
}
73+
return nil
74+
}
75+
6076
type multiLevelWriter struct {
6177
writers []LevelWriter
6278
}
@@ -89,6 +105,17 @@ func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) {
89105
return n, err
90106
}
91107

108+
func (t multiLevelWriter) Close() error {
109+
for _, w := range t.writers {
110+
if closer, ok := w.(io.Closer); ok {
111+
if err := closer.Close(); err != nil {
112+
return err
113+
}
114+
}
115+
}
116+
return nil
117+
}
118+
92119
// MultiLevelWriter creates a writer that duplicates its writes to all the
93120
// provided writers, similar to the Unix tee(1) command. If some writers
94121
// implement LevelWriter, their WriteLevel method will be used instead of Write.

0 commit comments

Comments
 (0)