Skip to content

Commit 70fabaf

Browse files
committed
Add new FatalFunc method to logger to override behavior for Fatal-level logging
1 parent 22af876 commit 70fabaf

File tree

2 files changed

+104
-74
lines changed

2 files changed

+104
-74
lines changed

log.go

+85-74
Original file line numberDiff line numberDiff line change
@@ -2,106 +2,105 @@
22
//
33
// A global Logger can be use for simple logging:
44
//
5-
// import "github.com/rs/zerolog/log"
5+
// import "github.com/rs/zerolog/log"
66
//
7-
// log.Info().Msg("hello world")
8-
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
7+
// log.Info().Msg("hello world")
8+
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
99
//
1010
// NOTE: To import the global logger, import the "log" subpackage "github.com/rs/zerolog/log".
1111
//
1212
// Fields can be added to log messages:
1313
//
14-
// log.Info().Str("foo", "bar").Msg("hello world")
15-
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
14+
// log.Info().Str("foo", "bar").Msg("hello world")
15+
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
1616
//
1717
// Create logger instance to manage different outputs:
1818
//
19-
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
20-
// logger.Info().
21-
// Str("foo", "bar").
22-
// Msg("hello world")
23-
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
19+
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
20+
// logger.Info().
21+
// Str("foo", "bar").
22+
// Msg("hello world")
23+
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
2424
//
2525
// Sub-loggers let you chain loggers with additional context:
2626
//
27-
// sublogger := log.With().Str("component": "foo").Logger()
28-
// sublogger.Info().Msg("hello world")
29-
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
27+
// sublogger := log.With().Str("component": "foo").Logger()
28+
// sublogger.Info().Msg("hello world")
29+
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
3030
//
3131
// Level logging
3232
//
33-
// zerolog.SetGlobalLevel(zerolog.InfoLevel)
33+
// zerolog.SetGlobalLevel(zerolog.InfoLevel)
3434
//
35-
// log.Debug().Msg("filtered out message")
36-
// log.Info().Msg("routed message")
35+
// log.Debug().Msg("filtered out message")
36+
// log.Info().Msg("routed message")
3737
//
38-
// if e := log.Debug(); e.Enabled() {
39-
// // Compute log output only if enabled.
40-
// value := compute()
41-
// e.Str("foo": value).Msg("some debug message")
42-
// }
43-
// // Output: {"level":"info","time":1494567715,"routed message"}
38+
// if e := log.Debug(); e.Enabled() {
39+
// // Compute log output only if enabled.
40+
// value := compute()
41+
// e.Str("foo": value).Msg("some debug message")
42+
// }
43+
// // Output: {"level":"info","time":1494567715,"routed message"}
4444
//
4545
// Customize automatic field names:
4646
//
47-
// log.TimestampFieldName = "t"
48-
// log.LevelFieldName = "p"
49-
// log.MessageFieldName = "m"
47+
// log.TimestampFieldName = "t"
48+
// log.LevelFieldName = "p"
49+
// log.MessageFieldName = "m"
5050
//
51-
// log.Info().Msg("hello world")
52-
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
51+
// log.Info().Msg("hello world")
52+
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
5353
//
5454
// Log with no level and message:
5555
//
56-
// log.Log().Str("foo","bar").Msg("")
57-
// // Output: {"time":1494567715,"foo":"bar"}
56+
// log.Log().Str("foo","bar").Msg("")
57+
// // Output: {"time":1494567715,"foo":"bar"}
5858
//
5959
// Add contextual fields to global Logger:
6060
//
61-
// log.Logger = log.With().Str("foo", "bar").Logger()
61+
// log.Logger = log.With().Str("foo", "bar").Logger()
6262
//
6363
// Sample logs:
6464
//
65-
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
66-
// sampled.Info().Msg("will be logged every 10 messages")
65+
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
66+
// sampled.Info().Msg("will be logged every 10 messages")
6767
//
6868
// Log with contextual hooks:
6969
//
70-
// // Create the hook:
71-
// type SeverityHook struct{}
70+
// // Create the hook:
71+
// type SeverityHook struct{}
7272
//
73-
// func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
74-
// if level != zerolog.NoLevel {
75-
// e.Str("severity", level.String())
76-
// }
77-
// }
73+
// func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
74+
// if level != zerolog.NoLevel {
75+
// e.Str("severity", level.String())
76+
// }
77+
// }
7878
//
79-
// // And use it:
80-
// var h SeverityHook
81-
// log := zerolog.New(os.Stdout).Hook(h)
82-
// log.Warn().Msg("")
83-
// // Output: {"level":"warn","severity":"warn"}
79+
// // And use it:
80+
// var h SeverityHook
81+
// log := zerolog.New(os.Stdout).Hook(h)
82+
// log.Warn().Msg("")
83+
// // Output: {"level":"warn","severity":"warn"}
8484
//
8585
// Prehooks are also available. It's called just before "level" is added to a log.
8686
// `msg` always gets empty in `Run()`:
8787
//
88-
// // Use as prehook:
89-
// var h SeverityHook
90-
// log := zerolog.New(os.Stdout).Prehook(h)
91-
// log.Warn().Msg("")
92-
// // Output: {"severity":"warn", "level":"warn"}
88+
// // Use as prehook:
89+
// var h SeverityHook
90+
// log := zerolog.New(os.Stdout).Prehook(h)
91+
// log.Warn().Msg("")
92+
// // Output: {"severity":"warn", "level":"warn"}
9393
//
94-
//
95-
// Caveats
94+
// # Caveats
9695
//
9796
// There is no fields deduplication out-of-the-box.
9897
// Using the same key multiple times creates new key in final JSON each time.
9998
//
100-
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
101-
// logger.Info().
102-
// Timestamp().
103-
// Msg("dup")
104-
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
99+
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
100+
// logger.Info().
101+
// Timestamp().
102+
// Msg("dup")
103+
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
105104
//
106105
// In this case, many consumers will take the last value,
107106
// but this is not guaranteed; check yours if in doubt.
@@ -110,15 +109,15 @@
110109
//
111110
// Be careful when calling UpdateContext. It is not concurrency safe. Use the With method to create a child logger:
112111
//
113-
// func handler(w http.ResponseWriter, r *http.Request) {
114-
// // Create a child logger for concurrency safety
115-
// logger := log.Logger.With().Logger()
112+
// func handler(w http.ResponseWriter, r *http.Request) {
113+
// // Create a child logger for concurrency safety
114+
// logger := log.Logger.With().Logger()
116115
//
117-
// // Add context fields, for example User-Agent from HTTP headers
118-
// logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
119-
// ...
120-
// })
121-
// }
116+
// // Add context fields, for example User-Agent from HTTP headers
117+
// logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
118+
// ...
119+
// })
120+
// }
122121
package zerolog
123122

124123
import (
@@ -236,16 +235,19 @@ func (l Level) MarshalText() ([]byte, error) {
236235
// serialization to the Writer. If your Writer is not thread safe,
237236
// you may consider a sync wrapper.
238237
type Logger struct {
239-
w LevelWriter
240-
level Level
241-
sampler Sampler
242-
context []byte
243-
hooks []Hook
244-
prehooks []Hook
245-
stack bool
246-
ctx context.Context
238+
w LevelWriter
239+
level Level
240+
sampler Sampler
241+
context []byte
242+
hooks []Hook
243+
prehooks []Hook
244+
stack bool
245+
fatalFunc func(string)
246+
ctx context.Context
247247
}
248248

249+
var defaultFatalFunc = func(msg string) { os.Exit(1) }
250+
249251
// New creates a root logger with given output writer. If the output writer implements
250252
// the LevelWriter interface, the WriteLevel method will be called instead of the Write
251253
// one.
@@ -261,7 +263,7 @@ func New(w io.Writer) Logger {
261263
if !ok {
262264
lw = LevelWriterAdapter{w}
263265
}
264-
return Logger{w: lw, level: TraceLevel}
266+
return Logger{w: lw, level: TraceLevel, fatalFunc: defaultFatalFunc}
265267
}
266268

267269
// Nop returns a disabled logger for which all operation are no-op.
@@ -352,6 +354,15 @@ func (l Logger) Prehook(h Hook) Logger {
352354
return l
353355
}
354356

357+
// FatalFunc returns a logger with the f FatalFunc. This func will be called
358+
// when a message is logged at the fatal level. By default, this calls
359+
// `os.Exit(1)`, but you can override this to do something else, particularly
360+
// for testing.
361+
func (l Logger) FatalFunc(f func(string)) Logger {
362+
l.fatalFunc = f
363+
return l
364+
}
365+
355366
// Trace starts a new message with trace level.
356367
//
357368
// You must call Msg on the returned event in order to send the event.
@@ -404,7 +415,7 @@ func (l *Logger) Err(err error) *Event {
404415
//
405416
// You must call Msg on the returned event in order to send the event.
406417
func (l *Logger) Fatal() *Event {
407-
return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) })
418+
return l.newEvent(FatalLevel, l.fatalFunc)
408419
}
409420

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

log_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -1039,3 +1039,22 @@ func TestUnmarshalTextLevel(t *testing.T) {
10391039
})
10401040
}
10411041
}
1042+
1043+
func TestFatalFunc(t *testing.T) {
1044+
out := &bytes.Buffer{}
1045+
1046+
var fatalCalled string
1047+
f := func(msg string) {
1048+
fatalCalled = msg
1049+
}
1050+
log := New(out).FatalFunc(f)
1051+
log.Fatal().Msg("foo")
1052+
1053+
if got, want := decodeIfBinaryToString(out.Bytes()), `{"level":"fatal","message":"foo"}`+"\n"; got != want {
1054+
t.Errorf("invalid log output:\ngot: %v\nwant: %v", got, want)
1055+
}
1056+
1057+
if fatalCalled != "foo" {
1058+
t.Error("fatal func was not called as expected")
1059+
}
1060+
}

0 commit comments

Comments
 (0)