Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new FatalFunc method to logger to override behavior for Fatal-level logging #16

Merged
merged 5 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
on: [push, pull_request]
on: pull_request
name: Test
jobs:
test:
strategy:
matrix:
go-version: [1.18.x, 1.21.x]
go-version: [1.20.x]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -24,13 +24,13 @@ jobs:
run: go test -race -bench . -benchmem ./...
- name: Test CBOR
run: go test -tags binary_log ./...
coverage:
runs-on: ubuntu-latest
steps:
- name: Update coverage report
uses: ncruces/go-coverage-report@main
with:
report: 'true'
chart: 'true'
amend: 'true'
continue-on-error: true
# coverage:
# runs-on: ubuntu-latest
# steps:
# - name: Update coverage report
# uses: ncruces/go-coverage-report@main
# with:
# report: 'true'
# chart: 'true'
# amend: 'true'
# continue-on-error: true
13 changes: 6 additions & 7 deletions ctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@ package zerolog
import (
"context"
"io/ioutil"
"reflect"
"testing"
)

func TestCtx(t *testing.T) {
log := New(ioutil.Discard)
ctx := log.WithContext(context.Background())
log2 := Ctx(ctx)
if !reflect.DeepEqual(log, *log2) {
if !log.IsEqualToLogger(*log2) {
t.Error("Ctx did not return the expected logger")
}

// update
log = log.Level(InfoLevel)
ctx = log.WithContext(ctx)
log2 = Ctx(ctx)
if !reflect.DeepEqual(log, *log2) {
if !log.IsEqualToLogger(*log2) {
t.Error("Ctx did not return the expected logger")
}

Expand All @@ -45,26 +44,26 @@ func TestCtxDisabled(t *testing.T) {

l := New(ioutil.Discard).With().Str("foo", "bar").Logger()
ctx = l.WithContext(ctx)
if !reflect.DeepEqual(Ctx(ctx), &l) {
if !l.IsEqualToLogger(*Ctx(ctx)) {
t.Error("WithContext did not store logger")
}

l.UpdateContext(func(c Context) Context {
return c.Str("bar", "baz")
})
ctx = l.WithContext(ctx)
if !reflect.DeepEqual(Ctx(ctx), &l) {
if !l.IsEqualToLogger(*Ctx(ctx)) {
t.Error("WithContext did not store updated logger")
}

l = l.Level(DebugLevel)
ctx = l.WithContext(ctx)
if !reflect.DeepEqual(Ctx(ctx), &l) {
if !l.IsEqualToLogger(*Ctx(ctx)) {
t.Error("WithContext did not store copied logger")
}

ctx = dl.WithContext(ctx)
if !reflect.DeepEqual(Ctx(ctx), &dl) {
if !dl.IsEqualToLogger(*Ctx(ctx)) {
t.Error("WithContext did not override logger with a disabled logger")
}
}
2 changes: 1 addition & 1 deletion hlog/hlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestNewHandler(t *testing.T) {
lh := NewHandler(log)
h := lh(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l := FromRequest(r)
if !reflect.DeepEqual(*l, log) {
if !log.IsEqualToLogger(*l) {
t.Fail()
}
}))
Expand Down
179 changes: 105 additions & 74 deletions log.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,106 +2,105 @@
//
// A global Logger can be use for simple logging:
//
// import "github.com/rs/zerolog/log"
// import "github.com/rs/zerolog/log"
//
// log.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
// log.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
//
// NOTE: To import the global logger, import the "log" subpackage "github.com/rs/zerolog/log".
//
// Fields can be added to log messages:
//
// log.Info().Str("foo", "bar").Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
// log.Info().Str("foo", "bar").Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
//
// Create logger instance to manage different outputs:
//
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// logger.Info().
// Str("foo", "bar").
// Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// logger.Info().
// Str("foo", "bar").
// Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
//
// Sub-loggers let you chain loggers with additional context:
//
// sublogger := log.With().Str("component": "foo").Logger()
// sublogger.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
// sublogger := log.With().Str("component": "foo").Logger()
// sublogger.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
//
// Level logging
//
// zerolog.SetGlobalLevel(zerolog.InfoLevel)
// zerolog.SetGlobalLevel(zerolog.InfoLevel)
//
// log.Debug().Msg("filtered out message")
// log.Info().Msg("routed message")
// log.Debug().Msg("filtered out message")
// log.Info().Msg("routed message")
//
// if e := log.Debug(); e.Enabled() {
// // Compute log output only if enabled.
// value := compute()
// e.Str("foo": value).Msg("some debug message")
// }
// // Output: {"level":"info","time":1494567715,"routed message"}
// if e := log.Debug(); e.Enabled() {
// // Compute log output only if enabled.
// value := compute()
// e.Str("foo": value).Msg("some debug message")
// }
// // Output: {"level":"info","time":1494567715,"routed message"}
//
// Customize automatic field names:
//
// log.TimestampFieldName = "t"
// log.LevelFieldName = "p"
// log.MessageFieldName = "m"
// log.TimestampFieldName = "t"
// log.LevelFieldName = "p"
// log.MessageFieldName = "m"
//
// log.Info().Msg("hello world")
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
// log.Info().Msg("hello world")
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
//
// Log with no level and message:
//
// log.Log().Str("foo","bar").Msg("")
// // Output: {"time":1494567715,"foo":"bar"}
// log.Log().Str("foo","bar").Msg("")
// // Output: {"time":1494567715,"foo":"bar"}
//
// Add contextual fields to global Logger:
//
// log.Logger = log.With().Str("foo", "bar").Logger()
// log.Logger = log.With().Str("foo", "bar").Logger()
//
// Sample logs:
//
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
// sampled.Info().Msg("will be logged every 10 messages")
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
// sampled.Info().Msg("will be logged every 10 messages")
//
// Log with contextual hooks:
//
// // Create the hook:
// type SeverityHook struct{}
// // Create the hook:
// type SeverityHook struct{}
//
// func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
// if level != zerolog.NoLevel {
// e.Str("severity", level.String())
// }
// }
// func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
// if level != zerolog.NoLevel {
// e.Str("severity", level.String())
// }
// }
//
// // And use it:
// var h SeverityHook
// log := zerolog.New(os.Stdout).Hook(h)
// log.Warn().Msg("")
// // Output: {"level":"warn","severity":"warn"}
// // And use it:
// var h SeverityHook
// log := zerolog.New(os.Stdout).Hook(h)
// log.Warn().Msg("")
// // Output: {"level":"warn","severity":"warn"}
//
// Prehooks are also available. It's called just before "level" is added to a log.
// `msg` always gets empty in `Run()`:
//
// // Use as prehook:
// var h SeverityHook
// log := zerolog.New(os.Stdout).Prehook(h)
// log.Warn().Msg("")
// // Output: {"severity":"warn", "level":"warn"}
// // Use as prehook:
// var h SeverityHook
// log := zerolog.New(os.Stdout).Prehook(h)
// log.Warn().Msg("")
// // Output: {"severity":"warn", "level":"warn"}
//
//
// Caveats
// # Caveats
//
// There is no fields deduplication out-of-the-box.
// Using the same key multiple times creates new key in final JSON each time.
//
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// logger.Info().
// Timestamp().
// Msg("dup")
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// logger.Info().
// Timestamp().
// Msg("dup")
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
//
// In this case, many consumers will take the last value,
// but this is not guaranteed; check yours if in doubt.
Expand All @@ -110,15 +109,15 @@
//
// Be careful when calling UpdateContext. It is not concurrency safe. Use the With method to create a child logger:
//
// func handler(w http.ResponseWriter, r *http.Request) {
// // Create a child logger for concurrency safety
// logger := log.Logger.With().Logger()
// func handler(w http.ResponseWriter, r *http.Request) {
// // Create a child logger for concurrency safety
// logger := log.Logger.With().Logger()
//
// // Add context fields, for example User-Agent from HTTP headers
// logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
// ...
// })
// }
// // Add context fields, for example User-Agent from HTTP headers
// logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
// ...
// })
// }
package zerolog

import (
Expand All @@ -128,6 +127,7 @@ import (
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
)
Expand Down Expand Up @@ -236,16 +236,19 @@ func (l Level) MarshalText() ([]byte, error) {
// serialization to the Writer. If your Writer is not thread safe,
// you may consider a sync wrapper.
type Logger struct {
w LevelWriter
level Level
sampler Sampler
context []byte
hooks []Hook
prehooks []Hook
stack bool
ctx context.Context
w LevelWriter
level Level
sampler Sampler
context []byte
hooks []Hook
prehooks []Hook
stack bool
fatalFunc func(string)
ctx context.Context
}

var defaultFatalFunc = func(msg string) { os.Exit(1) }

// New creates a root logger with given output writer. If the output writer implements
// the LevelWriter interface, the WriteLevel method will be called instead of the Write
// one.
Expand All @@ -261,7 +264,7 @@ func New(w io.Writer) Logger {
if !ok {
lw = LevelWriterAdapter{w}
}
return Logger{w: lw, level: TraceLevel}
return Logger{w: lw, level: TraceLevel, fatalFunc: defaultFatalFunc}
}

// Nop returns a disabled logger for which all operation are no-op.
Expand Down Expand Up @@ -352,6 +355,15 @@ func (l Logger) Prehook(h Hook) Logger {
return l
}

// FatalFunc returns a logger with the f FatalFunc. This func will be called
// when a message is logged at the fatal level. By default, this calls
// `os.Exit(1)`, but you can override this to do something else, particularly
// for testing.
func (l Logger) FatalFunc(f func(string)) Logger {
l.fatalFunc = f
return l
}

// Trace starts a new message with trace level.
//
// You must call Msg on the returned event in order to send the event.
Expand Down Expand Up @@ -404,7 +416,7 @@ func (l *Logger) Err(err error) *Event {
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Fatal() *Event {
return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) })
return l.newEvent(FatalLevel, l.fatalFunc)
}

// Panic starts a new message with panic level. The panic() function
Expand Down Expand Up @@ -518,3 +530,22 @@ func (l *Logger) should(lvl Level) bool {
}
return true
}

// IsEqualToLogger returns true if the two loggers are equal. This exists for
// use in tests.
//
// reflect.DeepEqual doesn't compare funcs in any useful way so we need to
// remove them and do that comparison ourself.
func (l Logger) IsEqualToLogger(log2 Logger) bool {
ff1 := l.fatalFunc
ff2 := log2.fatalFunc

l.fatalFunc = nil
log2.fatalFunc = nil

if !reflect.DeepEqual(l, log2) {
return false
}

return fmt.Sprintf("%p", ff1) == fmt.Sprintf("%p", ff2)
}
Loading
Loading