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

feat: migrate from logrus to zap, while keeping almost the same API #76

Closed
wants to merge 2 commits into from
Closed
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
77 changes: 77 additions & 0 deletions caller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package logger

import (
"runtime"
"strings"
"sync"
)

var (

// qualified package name, cached at first use
goLoggerPackage string

// Positions in the call stack when tracing to report the calling method
minimumCallerDepth int

// Used for caller information initialisation
callerInitOnce sync.Once
)

const (
maximumCallerDepth int = 25
knownGoLoggerFrames int = 4
)

// getCaller retrieves the name of the first non-go-logger calling function
func getCaller() *runtime.Frame {
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
pcs := make([]uintptr, maximumCallerDepth)
_ = runtime.Callers(0, pcs)

// dynamic get the package name and the minimum caller depth
for i := 0; i < maximumCallerDepth; i++ {
funcName := runtime.FuncForPC(pcs[i]).Name()
if strings.Contains(funcName, "getCaller") {
goLoggerPackage = getPackageName(funcName)
break
}
}

minimumCallerDepth = knownGoLoggerFrames
})

// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])

for f, again := frames.Next(); again; f, again = frames.Next() {
pkg := getPackageName(f.Function)

// If the caller isn't part of this package, we're done
if pkg != goLoggerPackage {
return &f //nolint:scopelint
}
}

// if we got here, we failed to find the caller's context
return nil
}

// getPackageName reduces a fully qualified function name to the package name
// There really ought to be to be a better way...
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
break
}
}

return f
}
22 changes: 22 additions & 0 deletions clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package logger

import "time"

type functionClock struct {
now func() time.Time
newTicker func(time.Duration) *time.Ticker
}

func (c functionClock) Now() time.Time {
if c.now != nil {
return c.now()
}
return time.Now()
}

func (c functionClock) NewTicker(duration time.Duration) *time.Ticker {
if c.newTicker != nil {
return c.newTicker(duration)
}
return time.NewTicker(duration)
}
224 changes: 157 additions & 67 deletions entry.go
Original file line number Diff line number Diff line change
@@ -1,91 +1,181 @@
package logger

import (
"runtime"
"strings"
"sync"
"context"
"fmt"
"os"
"sort"

"go.uber.org/zap/zapcore"
)

// Entry represents a logging entry and all supported method we use
type Entry interface {
Debugf(format string, args ...interface{})
Debug(args ...interface{})
Infof(format string, args ...interface{})
Info(args ...interface{})
Warnf(format string, args ...interface{})
Warn(args ...interface{})
Errorf(format string, args ...interface{})
Error(args ...interface{})
Fatalf(format string, args ...interface{})
Fatal(args ...interface{})
type Entry struct {
logger *Logger
fields Fields
context context.Context
}

var (
// WithError is a convenience wrapper for WithField("error", err)
func (e *Entry) WithError(err error) *Entry {
return e.WithField(errorKey, err)
}

// qualified package name, cached at first use
goLoggerPackage string
// WithField forwards a logging call with a field
func (e *Entry) WithField(key string, value any) *Entry {
e.fields[key] = value
return e
}

// Positions in the call stack when tracing to report the calling method
minimumCallerDepth int
// WithFields forwards a logging call with fields
func (e *Entry) WithFields(fields Fields) *Entry {
for k, v := range fields {
e.fields[k] = v
}
return e
}

// Used for caller information initialisation
callerInitOnce sync.Once
)
// WithContext sets the context for the log-message. Useful when using hooks.
func (e *Entry) WithContext(ctx context.Context) *Entry {
e.context = ctx
return e
}

const (
maximumCallerDepth int = 25
knownGoLoggerFrames int = 4
)
// Info forwards a logging call in the (format, args) format
func (e *Entry) Info(msg string) {
msg, level := e.fireHooks(msg, LevelInfo)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

// getCaller retrieves the name of the first non-go-logger calling function
func getCaller() *runtime.Frame {
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
pcs := make([]uintptr, maximumCallerDepth)
_ = runtime.Callers(0, pcs)

// dynamic get the package name and the minimum caller depth
for i := 0; i < maximumCallerDepth; i++ {
funcName := runtime.FuncForPC(pcs[i]).Name()
if strings.Contains(funcName, "getCaller") {
goLoggerPackage = getPackageName(funcName)
break
}
}
// Infof forwards a logging call in the (format, args) format
func (e *Entry) Infof(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
msg, level := e.fireHooks(msg, LevelInfo)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

minimumCallerDepth = knownGoLoggerFrames
})
// Error forwards an error logging call
func (e *Entry) Error(msg string) {
msg, level := e.fireHooks(msg, LevelError)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
// Errorf forwards an error logging call
func (e *Entry) Errorf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
msg, level := e.fireHooks(msg, LevelError)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

for f, again := frames.Next(); again; f, again = frames.Next() {
pkg := getPackageName(f.Function)
// Debug forwards a debugging logging call
func (e *Entry) Debug(msg string) {
msg, level := e.fireHooks(msg, LevelDebug)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

// If the caller isn't part of this package, we're done
if pkg != goLoggerPackage {
return &f //nolint:scopelint
}
}
// Debugf forwards a debugging logging call
func (e *Entry) Debugf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
msg, level := e.fireHooks(msg, LevelDebug)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

// Warn forwards a warning logging call
func (e *Entry) Warn(msg string) {
msg, level := e.fireHooks(msg, LevelWarn)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

// Warnf forwards a warning logging call
func (e *Entry) Warnf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
msg, level := e.fireHooks(msg, LevelWarn)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

// Fatal forwards a fatal logging call
func (e *Entry) Fatal(msg string) {
msg, level := e.fireHooks(msg, LevelFatal)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

// if we got here, we failed to find the caller's context
return nil
// Fatalf forwards a fatal logging call
func (e *Entry) Fatalf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
msg, level := e.fireHooks(msg, LevelFatal)
zapFields := e.getZapFields()
zapLevel := mapLevelToZapLevel(level)
e.logger.zapLogger.Log(zapLevel, msg, zapFields...)
}

// getPackageName reduces a fully qualified function name to the package name
// There really ought to be to be a better way...
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
break
func (e *Entry) fireHooks(msg string, level Level) (string, Level) {
if len(e.logger.hooks) > 0 {
for _, hook := range e.logger.hooks {
he := &HookEntry{
Fields: e.fields,
Level: level,
Message: msg,
Context: e.context,
}
changed, err := hook.Fire(he)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
continue
}
if !changed {
continue
}
e.fields = he.Fields
level = he.Level
msg = he.Message
e.context = he.Context

}
}
return msg, level
}

return f
func (e *Entry) getZapFields() []zapcore.Field {
res := make([]zapcore.Field, 0, len(e.fields)+1)
for k, v := range e.fields {
f := zapcore.Field{
Key: k,
}
switch v := v.(type) {
case error:
f.Type = zapcore.ErrorType
f.Interface = v
case string:
f.Type = zapcore.StringType
f.String = v
default:
f.Type = zapcore.ReflectType
f.Interface = v
}
res = append(res, f)
}
if e.logger.attemptConsistentOrdering {
sort.Slice(res, func(i, j int) bool {
return res[i].Key < res[j].Key
})
}
return res
}
Loading
Loading