Feat (breaking changes?): panic/fatal bypasses, force level, add unit tests

This commit is contained in:
kayos@tcp.direct 2024-05-20 03:27:50 -07:00
parent dd9ca58d52
commit e99bc8f197
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
8 changed files with 736 additions and 152 deletions

72
bypass.go Normal file

@ -0,0 +1,72 @@
package zwrap
func (l *Logger) checkPanicBypass(fmt string, v ...interface{}) (string, []interface{}, bool) {
if !l.noPanic {
return fmt, v, true
}
if fmt != "" {
fmt = "[PANIC BYPASSED] " + fmt
return fmt, v, false
}
nv := make([]interface{}, len(v)+1)
nv[0] = "[PANIC BYPASSED]"
copy(nv[1:], v)
v = nil
return fmt, nv, false
}
func (l *Logger) checkFatalBypass(fmt string, v ...interface{}) (string, []interface{}, bool) {
if !l.noFatal {
return fmt, v, true
}
if fmt != "" {
fmt = "[FATAL BYPASSED] " + fmt
return fmt, v, false
}
nv := make([]interface{}, len(v)+1)
nv[0] = "[FATAL BYPASSED]"
copy(nv[1:], v)
v = nil
return fmt, nv, false
}
func (l *Logger) NoPanics(b bool) {
l.mu.Lock()
l.noPanic = b
l.mu.Unlock()
}
func (l *Logger) NoFatals(b bool) {
l.mu.Lock()
l.noFatal = b
l.mu.Unlock()
}
func (l *Logger) WithNoPanics() *Logger {
l.NoPanics(true)
return l
}
func (l *Logger) WithNoFatals() *Logger {
l.NoFatals(true)
return l
}
func (l *Logger) ForceLevel(level any) {
l.mu.Lock()
nl := castToZlogLevel(level)
l.forceLevel = &nl
l.printLevel = nl
nll := l.Logger.Level(nl)
l.Logger = &nll
l.mu.Unlock()
}
func (l *Logger) WithForceLevel(level uint32) *Logger {
l.ForceLevel(level)
return l
}

10
go.mod

@ -1,11 +1,11 @@
module git.tcp.direct/kayos/zwrap
go 1.18
go 1.20
require github.com/rs/zerolog v1.30.0
require github.com/rs/zerolog v1.32.0
require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.18.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
golang.org/x/sys v0.20.0 // indirect
)

22
go.sum

@ -1,14 +1,16 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

84
level.go Normal file

@ -0,0 +1,84 @@
package zwrap
import (
"fmt"
"github.com/rs/zerolog"
)
type Level interface {
int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64
}
func castToZlogLevel(level any) zerolog.Level {
switch casted := level.(type) {
case int:
return toZlogLevel[int](casted)
case int8:
return toZlogLevel[int8](casted)
case int16:
return toZlogLevel[int16](casted)
case int32:
return toZlogLevel[int32](casted)
case int64:
return toZlogLevel[int64](casted)
case uint:
return toZlogLevel[uint](casted)
case uint8:
return toZlogLevel[uint8](casted)
case uint16:
return toZlogLevel[uint16](casted)
case uint32:
return toZlogLevel[uint32](casted)
case uint64:
return toZlogLevel[uint64](casted)
case string:
if parsed, err := zerolog.ParseLevel(casted); err == nil {
return parsed
} else {
panic(fmt.Sprintf("invalid log level string %v: %v", level, err))
}
case zerolog.Level:
return casted
default:
panic(fmt.Sprintf("invalid log level type (%T): %v", level, level))
}
}
func toZlogLevel[T Level](level T) zerolog.Level {
switch casted := any(level).(type) {
case uint32: // compat
switch {
case casted == 0:
return zerolog.PanicLevel
case casted == 1:
return zerolog.FatalLevel
case casted == 2:
return zerolog.ErrorLevel
case casted == 3:
return zerolog.WarnLevel
case casted == 4:
return zerolog.InfoLevel
case casted == 5:
return zerolog.DebugLevel
case casted == 6:
return zerolog.TraceLevel
default:
if casted < 0 {
return zerolog.TraceLevel
}
if casted > 5 {
return zerolog.PanicLevel
}
}
case int16, int32, int64, int, uint, uint8, uint16, uint64, int8:
if level < 0 {
return zerolog.TraceLevel
}
if level > 5 {
return zerolog.PanicLevel
}
return zerolog.Level(int8(level))
}
return zerolog.TraceLevel
}

108
level_test.go Normal file

@ -0,0 +1,108 @@
package zwrap
import (
"testing"
"github.com/rs/zerolog"
)
func TestCastToZlogLevel(t *testing.T) {
tests := []struct {
name string
level any
want zerolog.Level
}{
{"int", 3, zerolog.ErrorLevel},
{"int8", int8(4), zerolog.FatalLevel},
{"int16", int16(2), zerolog.WarnLevel},
{"int32", int32(1), zerolog.InfoLevel},
{"int64", int64(5), zerolog.PanicLevel},
{"uint", uint(6), zerolog.PanicLevel},
{"uint8", uint8(0), zerolog.DebugLevel},
{"uint16", uint16(4), zerolog.FatalLevel},
{"uint32", uint32(3), zerolog.WarnLevel},
{"uint64", uint64(2), zerolog.WarnLevel},
{"string", "info", zerolog.InfoLevel},
{"zerolog.Level", zerolog.DebugLevel, zerolog.DebugLevel},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := castToZlogLevel(tt.level); got != tt.want {
t.Errorf("castToZlogLevel() = %v, want %v", got, tt.want)
}
})
}
}
func TestCastToZlogLevel_Panic(t *testing.T) {
tests := []struct {
name string
level any
}{
{"invalid type", 3.14},
{"invalid string", "invalid"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("castToZlogLevel() did not panic")
}
}()
castToZlogLevel(tt.level)
})
}
}
func TestToZlogLevel(t *testing.T) {
tests := []struct {
name string
level any
want zerolog.Level
}{
{"int8", int8(3), zerolog.ErrorLevel},
{"int16", int16(2), zerolog.WarnLevel},
{"int32", int32(1), zerolog.InfoLevel},
{"int64", int64(5), zerolog.PanicLevel},
{"uint", uint(0), zerolog.DebugLevel},
{"uint8", uint8(6), zerolog.PanicLevel},
{"uint16", uint16(4), zerolog.FatalLevel},
{"uint32", uint32(3), zerolog.WarnLevel},
{"uint64", uint64(2), zerolog.WarnLevel},
{"-1 trace level", -1, zerolog.TraceLevel},
{"> limit level", uint(7), zerolog.PanicLevel},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
switch casted := tt.level.(type) {
case int8:
if got := toZlogLevel[int8](casted); got != tt.want {
t.Errorf("toZlogLevel(%v) = %v, want %v", tt.level, got, tt.want)
}
case int16:
if got := toZlogLevel[int16](casted); got != tt.want {
t.Errorf("toZlogLevel(%v) = %v, want %v", tt.level, got, tt.want)
}
case int32:
if got := toZlogLevel[int32](casted); got != tt.want {
t.Errorf("toZlogLevel(%v) = %v, want %v", tt.level, got, tt.want)
}
case int64:
if got := toZlogLevel[int64](casted); got != tt.want {
t.Errorf("toZlogLevel(%v) = %v, want %v", tt.level, got, tt.want)
}
case uint:
if got := toZlogLevel[uint](casted); got != tt.want {
t.Errorf("toZlogLevel(%v) = %v, want %v", tt.level, got, tt.want)
}
case uint8:
if got := toZlogLevel[uint8](casted); got != tt.want {
t.Errorf("toZlogLevel(%v) = %v, want %v", tt.level, got, tt.want)
}
}
})
}
}

43
restore_colors_test.go Normal file

@ -0,0 +1,43 @@
package zwrap
import (
"bytes"
"testing"
"github.com/rs/zerolog"
)
type colorTester struct {
t *testing.T
last []byte
}
func (c *colorTester) Write(p []byte) (n int, err error) {
if len(c.last) == 0 {
c.last = make([]byte, len(p))
copy(c.last, p)
return len(p), nil
}
if bytes.Equal(c.last, p) {
c.t.Errorf("\ncaught second output: %s\nwhich is the same as the first: %s", string(p), string(c.last))
}
return len(p), nil
}
func TestLegacyColorizer(t *testing.T) {
tw := &colorTester{t: t}
zlc := zerolog.NewConsoleWriter()
zlc.Out = tw
zlc.NoColor = false
zl := Wrap(zerolog.New(zlc))
zl.Trace("yeet")
if len(tw.last) == 0 {
t.Fatalf("test writer busted")
}
zlc = zerolog.NewConsoleWriter()
zlc.FormatLevel = LogLevelFmt(false)
zlc.NoColor = false
zlc.Out = tw
zl = Wrap(zerolog.New(zlc))
zl.Trace("yeet")
}

306
wrap.go

@ -13,18 +13,21 @@ import (
type Logger struct {
*zerolog.Logger
*sync.RWMutex
mu *sync.RWMutex
prefix string
printLevel zerolog.Level
forceLevel *zerolog.Level
noPanic bool
noFatal bool
}
func (l *Logger) Warning(args ...any) {
l.Logger.Warn().Msg(fmt.Sprint(args...))
l.printLn(l.Logger.Warn(), false, args...)
}
func (l *Logger) Warningln(args ...any) {
l.Logger.Warn().Msg(fmt.Sprintln(args...))
l.printLn(l.Logger.Warn(), false, args...)
}
func (l *Logger) V(level int) bool {
@ -38,176 +41,226 @@ func (l *Logger) V(level int) bool {
}
func (l *Logger) SetPrefix(prefix string) {
l.Lock()
l.mu.Lock()
l.prefix = prefix
l.Unlock()
l.mu.Unlock()
}
func (l *Logger) SetPrintLevel(level zerolog.Level) {
l.Lock()
l.mu.Lock()
l.printLevel = level
l.Unlock()
l.mu.Unlock()
}
func (l *Logger) Prefix() string {
l.RLock()
defer l.RUnlock()
l.mu.RLock()
p := l.myPrefix()
l.mu.RUnlock()
return p
}
func (l *Logger) myPrefix() string {
return l.prefix
}
func (l *Logger) Println(v ...interface{}) {
l.RLock()
l.Logger.WithLevel(l.printLevel).Msg(fmt.Sprint(v...))
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.WithLevel(l.printLevel), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Printf(format string, v ...interface{}) {
l.RLock()
l.Logger.WithLevel(l.printLevel).Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
var str string
switch {
case len(v) == 0:
str = format
case len(v) == 1:
str = fmt.Sprintf(format, v[0])
default:
str = fmt.Sprintf(format, v...)
}
l.printLn(l.Logger.WithLevel(l.printLevel), false, str)
l.mu.RUnlock()
}
func (l *Logger) Print(v ...interface{}) {
l.RLock()
l.Logger.WithLevel(l.printLevel).Msg(fmt.Sprint(v...))
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.WithLevel(l.printLevel), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Fatal(v ...interface{}) {
// Don't check mutex here because we're exiting anyway.
printLn(l.Logger.Fatal(), v...)
var ok bool
if _, v, ok = l.checkFatalBypass("", v...); ok {
l.printLn(l.Logger.Fatal(), true, v...)
return
}
l.mu.RLock()
l.printLn(l.Logger.Error(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Fatalf(format string, v ...interface{}) {
// Don't check mutex here because we're exiting anyway.
l.Logger.Fatal().Msgf(format, v...)
var ok bool
if format, v, ok = l.checkFatalBypass(format, v...); ok {
l.printLn(l.Logger.Fatal(), true, fmt.Sprintf(format, v...))
return
}
l.mu.RLock()
l.printLn(l.Logger.Error(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Fatalln(v ...interface{}) {
// Don't check mutex here because we're exiting anyway.
printLn(l.Logger.Fatal(), v...)
var ok bool
if _, v, ok = l.checkFatalBypass("", v...); ok {
l.printLn(l.Logger.Fatal(), true, v...)
return
}
l.mu.RLock()
l.printLn(l.Logger.Error(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Panic(v ...interface{}) {
// Don't check mutex here because we're panicking anyway.
printLn(l.Logger.Panic(), v...)
var ok bool
if _, v, ok = l.checkPanicBypass("", v...); ok {
l.printLn(l.Logger.Panic(), true, v...)
return
}
l.mu.RLock()
l.printLn(l.Logger.Error(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Panicf(format string, v ...interface{}) {
// Don't check mutex here because we're panicking anyway.
l.Logger.Panic().Msgf(format, v...)
var ok bool
if format, v, ok = l.checkPanicBypass(format, v...); ok {
l.printLn(l.Logger.Panic(), true, fmt.Sprintf(format, v...))
return
}
l.mu.RLock()
l.printLn(l.Logger.Error(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Panicln(v ...interface{}) {
// Don't check mutex here because we're panicking anyway.
printLn(l.Logger.Panic(), v...)
var ok bool
if _, v, ok = l.checkPanicBypass("", v...); ok {
l.printLn(l.Logger.Panic(), true, v...)
return
}
l.mu.RLock()
l.printLn(l.Logger.Error(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Errorf(format string, v ...interface{}) {
l.RLock()
l.Logger.Error().Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Error(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Warnf(format string, v ...interface{}) {
l.RLock()
l.Logger.Warn().Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Warn(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Infof(format string, v ...interface{}) {
l.RLock()
l.Logger.Info().Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Info(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Debugf(format string, v ...interface{}) {
l.RLock()
l.Logger.Debug().Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Debug(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Tracef(format string, v ...interface{}) {
l.RLock()
l.Logger.Trace().Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Trace(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Error(v ...interface{}) {
l.RLock()
printLn(l.Logger.Error(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Error(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Warn(v ...interface{}) {
l.RLock()
printLn(l.Logger.Warn(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Warn(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Info(v ...interface{}) {
l.RLock()
printLn(l.Logger.Info(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Info(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Debug(v ...interface{}) {
l.RLock()
printLn(l.Logger.Debug(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Debug(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Trace(v ...interface{}) {
l.RLock()
printLn(l.Logger.Trace(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Trace(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Errorln(v ...interface{}) {
l.RLock()
printLn(l.Logger.Error(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Error(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Warnln(v ...interface{}) {
l.RLock()
printLn(l.Logger.Warn(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Warn(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Infoln(v ...interface{}) {
l.RLock()
printLn(l.Logger.Info(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Info(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Debugln(v ...interface{}) {
l.RLock()
printLn(l.Logger.Debug(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Debug(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Traceln(v ...interface{}) {
l.RLock()
printLn(l.Logger.Trace(), v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Trace(), false, v...)
l.mu.RUnlock()
}
func (l *Logger) Verbosef(format string, v ...interface{}) {
l.RLock()
l.Logger.Trace().Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Trace(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Noticef(format string, v ...interface{}) {
l.RLock()
l.Logger.Info().Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Info(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) Warningf(format string, v ...interface{}) {
l.RLock()
l.Logger.Warn().Msgf(format, v...)
l.RUnlock()
l.mu.RLock()
l.printLn(l.Logger.Warn(), false, fmt.Sprintf(format, v...))
l.mu.RUnlock()
}
func (l *Logger) WithPrefix(prefix string) *Logger {
@ -220,50 +273,30 @@ func (l *Logger) Logf(format string, v ...interface{}) {
}
func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
l.RLock()
l.mu.RLock()
nl := l.Logger.With().Fields(fields).Logger()
l.Logger = &nl
l.RUnlock()
l.mu.RUnlock()
return l
}
// SetLevel is compatibility for ghettovoice/gosip/log.Logger
func (l *Logger) SetLevel(level uint32) {
l.Lock()
nl := l.Logger.Level(gosipLevelToZerologLevel(level))
func (l *Logger) SetLevel(level any) {
l.mu.Lock()
nl := l.Logger.Level(castToZlogLevel(level))
l.Logger = &nl
l.Unlock()
}
func gosipLevelToZerologLevel(level uint32) zerolog.Level {
switch level {
case 0:
return zerolog.PanicLevel
case 1:
return zerolog.FatalLevel
case 2:
return zerolog.ErrorLevel
case 3:
return zerolog.WarnLevel
case 4:
return zerolog.InfoLevel
case 5:
return zerolog.DebugLevel
case 6:
return zerolog.TraceLevel
}
panic(fmt.Sprintf("invalid log level %d", level))
l.mu.Unlock()
}
func (l *Logger) Write(p []byte) (n int, err error) {
l.RLock()
l.mu.RLock()
l.Logger.WithLevel(l.printLevel).Msg(string(bytes.TrimSuffix(p, []byte("\n"))))
l.RUnlock()
l.mu.RUnlock()
return len(p), nil
}
func (l *Logger) Output(calldepth int, s string) error {
l.RLock()
l.mu.RLock()
event := l.Logger.Info()
if calldepth != 2 {
if l.prefix != "" {
@ -274,11 +307,48 @@ func (l *Logger) Output(calldepth int, s string) error {
}
event.Msg(s)
zerolog.CallerFieldName = "caller"
l.RUnlock()
l.mu.RUnlock()
return nil
}
func printLn(e *zerolog.Event, v ...interface{}) {
func (l *Logger) transformZEvent(e *zerolog.Event) *zerolog.Event {
switch *l.forceLevel {
case zerolog.PanicLevel:
if !l.noPanic {
e = l.Logger.Panic()
}
case zerolog.FatalLevel:
if !l.noFatal {
e = l.Logger.Fatal()
}
case zerolog.ErrorLevel:
e = l.Logger.Error()
case zerolog.WarnLevel:
e = l.Logger.Warn()
case zerolog.InfoLevel:
e = l.Logger.Info()
case zerolog.DebugLevel:
e = l.Logger.Debug()
case zerolog.TraceLevel:
e = l.Logger.Trace()
default:
panic(fmt.Sprintf("invalid logger config, bad force level %v", l.forceLevel))
}
return e
}
func (l *Logger) printLn(e *zerolog.Event, preserve bool, v ...interface{}) {
if l.forceLevel != nil && !preserve {
e = l.transformZEvent(e)
}
if len(v) == 0 {
e.Msg("")
return
}
if len(v) == 1 {
e.Msg(fmt.Sprint(v[0]))
return
}
strBuf := strBufs.Get().(*strings.Builder)
for i, val := range v {
if i > 0 {
@ -292,18 +362,18 @@ func printLn(e *zerolog.Event, v ...interface{}) {
}
type prefixHook struct {
parent StdCompatLogger
parent *Logger
}
func (h prefixHook) Run(e *zerolog.Event, _ zerolog.Level, _ string) {
if h.parent.Prefix() != "" {
e.Str("caller", h.parent.Prefix())
e.Str("caller", h.parent.myPrefix())
}
}
func Wrap(l zerolog.Logger) *Logger {
wrapped := &Logger{
RWMutex: &sync.RWMutex{},
mu: &sync.RWMutex{},
printLevel: zerolog.InfoLevel,
}
p := prefixHook{wrapped}

@ -14,6 +14,7 @@ type testWriter struct {
t *testing.T
needsPrefix string
mustNotHavePrefix string
mustLevel *zerolog.Level
}
var ErrPrefixMismatch = errors.New("prefix mismatch")
@ -29,6 +30,20 @@ func (w *testWriter) Write(p []byte) (n int, err error) {
w.t.Errorf("unexpected prefix %q, got %q", w.mustNotHavePrefix, line)
return 0, ErrPrefixMismatch
}
if w.mustLevel != nil {
if !strings.Contains(line, w.mustLevel.String()) {
w.t.Errorf("expected level %q, got %q", w.mustLevel.String(), line)
return 0, ErrPrefixMismatch
}
lvl := strings.Split(line, `"level":"`)[1]
lvl = strings.Split(lvl, `"`)[0]
if lvl != w.mustLevel.String() {
w.t.Errorf("expected level %q, got %q", w.mustLevel.String(), lvl)
return 0, ErrPrefixMismatch
}
}
w.t.Log(line)
return len(p), nil
}
@ -46,7 +61,44 @@ func (n *needsLogger) SetLogger(logger aLogger) {
}
func (n *needsLogger) DoSomething() {
n.logger.Println("Hello, world!")
n.logger.Println("yeet")
}
type leveled struct {
name string
test func(*Logger, *testing.T)
shouldPanic bool
panicked bool
t *testing.T
}
const (
expected = "expected"
unexpected = "unexpected"
)
func (l *leveled) expS() string {
if l.shouldPanic {
return expected
}
return unexpected
}
func (l *leveled) Run() {
l.t.Run(l.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
l.t.Logf("caught panic (%s): %v", l.expS(), r)
l.panicked = true
}
}()
zl := zerolog.New(os.Stderr).With().Timestamp().Logger()
wrapped := Wrap(zl)
l.test(wrapped, t)
})
if (l.shouldPanic && !l.panicked) || (!l.shouldPanic && l.panicked) {
l.t.Errorf("%s panic during test: %s", l.expS(), l.name)
}
}
func TestWrap(t *testing.T) {
@ -55,56 +107,209 @@ func TestWrap(t *testing.T) {
writah := &testWriter{t: t}
zl := zerolog.New(writah).With().Timestamp().Logger()
wrapped := Wrap(zl)
wzl := Wrap(zl)
myThing := &needsLogger{}
myThing.SetLogger(wrapped)
myThing.SetLogger(wzl)
multiLog := func(v ...interface{}) {
multiLog := func(wrapped *Logger, v ...interface{}) {
t.Helper()
toggleFatals := wrapped.noFatal == false
togglePanics := wrapped.noPanic == false
wrapped.Print(v...)
wrapped.Printf("%v", v)
wrapped.Printf("f: %v", v...)
wrapped.Println(v...)
wrapped.Error(v...)
wrapped.Errorf("%v", v)
wrapped.Errorf("f: %v", v...)
wrapped.Errorln(v...)
wrapped.Debug(v...)
wrapped.Debugf("%v", v)
wrapped.Debugf("f: %v", v...)
wrapped.Debugln(v...)
wrapped.Warn(v...)
wrapped.Warnf("%v", v)
wrapped.Warnf("f: %v", v...)
wrapped.Warnln(v...)
wrapped.Info(v...)
wrapped.Infof("%v", v)
wrapped.Infof("f: %v", v...)
wrapped.Infoln(v...)
wrapped.Tracef("%v", v)
wrapped.Tracef("f: %v", v...)
wrapped.Trace(v...)
wrapped.Traceln(v...)
wrapped.Logf("%v", v)
wrapped.Warning("%v", v)
wrapped.Warningf("%v", v)
wrapped.Logf("f: %v", v...)
wrapped.Warning(v...)
wrapped.Warningf("f: %v", v...)
wrapped.Println("")
wrapped.Println()
if toggleFatals {
wrapped.NoFatals(true)
}
wrapped.Fatal(v...)
wrapped.Fatalf("f: %v", v...)
wrapped.Fatalln(v...)
if toggleFatals {
wrapped.NoFatals(false)
}
if togglePanics {
wrapped.NoPanics(true)
}
wrapped.Panic(v...)
wrapped.Panicf("f: %v", v...)
wrapped.Panicln(v...)
if togglePanics {
wrapped.NoPanics(false)
}
}
if wrapped.V(0) {
if wzl.V(0) {
t.Error("V(0) should always return false")
}
t.Run("generic", func(t *testing.T) {
multiLog("Hello, world!")
multiLog(wzl, "yeet")
})
t.Run("prefix", func(t *testing.T) {
writah.needsPrefix = "prefix: "
wrapped.SetPrefix("prefix: ")
multiLog("Hello, world!")
wzl.SetPrefix("prefix: ")
multiLog(wzl, "yeet")
writah.needsPrefix = "prefix2: "
wzl = wzl.WithPrefix("prefix2: ")
multiLog(wzl, "yeet")
})
t.Run("remove prefix", func(t *testing.T) {
writah.needsPrefix = ""
writah.mustNotHavePrefix = "prefix: "
wrapped.SetPrefix("")
multiLog("Hello, world!")
wzl.SetPrefix("")
multiLog(wzl, "yeet")
})
forceLevelTests := []leveled{
{
name: "trace",
test: func(wrapped *Logger, t *testing.T) {
wrapped.ForceLevel(zerolog.TraceLevel)
multiLog(wrapped, "yeet")
},
shouldPanic: false,
t: t,
},
{
name: "trace_with_panic",
test: func(wrapped *Logger, t *testing.T) {
wrapped.ForceLevel(zerolog.TraceLevel)
multiLog(wrapped, "yeet")
wrapped.Panic("yeet")
},
shouldPanic: true,
t: t,
},
{
name: "debug",
test: func(wrapped *Logger, t *testing.T) {
wrapped.ForceLevel(zerolog.DebugLevel)
multiLog(wrapped, "yeet")
},
shouldPanic: false,
t: t,
},
{
name: "info",
test: func(wrapped *Logger, t *testing.T) {
wrapped.ForceLevel(zerolog.InfoLevel)
multiLog(wrapped, "yeet")
},
shouldPanic: false,
t: t,
},
{
name: "warn",
test: func(wrapped *Logger, t *testing.T) {
wrapped.ForceLevel(zerolog.WarnLevel)
multiLog(wrapped, "yeet")
},
shouldPanic: false,
t: t,
},
{
name: "error",
test: func(wrapped *Logger, t *testing.T) {
wrapped.ForceLevel(zerolog.ErrorLevel)
multiLog(wrapped, "yeet")
},
shouldPanic: false,
t: t,
},
{
name: "fatal",
test: func(wrapped *Logger, t *testing.T) {
wrapped.ForceLevel(zerolog.FatalLevel)
wrapped.NoFatals(true)
multiLog(wrapped, "yeet")
},
shouldPanic: false,
t: t,
},
{
name: "panic",
test: func(wrapped *Logger, t *testing.T) {
wrapped.ForceLevel(zerolog.PanicLevel)
multiLog(wrapped, "yeet")
},
shouldPanic: true,
t: t,
},
{
name: "panic_with_panic",
test: func(wrapped *Logger, t *testing.T) {
wrapped.Panic("yeet")
},
shouldPanic: true,
t: t,
},
}
for _, test := range forceLevelTests {
test.name = "force_level_" + test.name
test.Run()
}
panicAndFatalBypassTests := []leveled{
{
name: "no_panic",
test: func(wrapped *Logger, t *testing.T) {
wrapped.NoPanics(true)
wrapped.Panic("yeet!!")
},
shouldPanic: false,
t: t,
},
{
name: "no_fatal",
test: func(wrapped *Logger, t *testing.T) {
wrapped.NoFatals(true)
wrapped.Fatal("yeet")
},
shouldPanic: false, // I guess the test should fail if it os.Exits anyway..? :^)
t: t,
},
{
name: "no_panic_no_fatal_force_panic",
test: func(wrapped *Logger, t *testing.T) {
wrapped.NoPanics(true)
wrapped.NoFatals(true)
wrapped.ForceLevel(zerolog.PanicLevel)
multiLog(wrapped, "yeet!!")
wrapped.Panic("yeet!!")
},
shouldPanic: false,
t: t,
},
}
for _, test := range panicAndFatalBypassTests {
test.name = "bypass_" + test.name
test.Run()
}
}
func ExampleWrap() {