bsc/log/logger.go
2024-02-02 15:55:18 +08:00

233 lines
5.6 KiB
Go

package log
import (
"context"
"fmt"
"math"
"os"
"runtime"
"time"
"golang.org/x/exp/slog"
)
const errorKey = "LOG_ERROR"
const (
legacyLevelCrit = iota
legacyLevelError
legacyLevelWarn
legacyLevelInfo
legacyLevelDebug
legacyLevelTrace
)
const (
levelMaxVerbosity slog.Level = math.MinInt
LevelTrace slog.Level = -8
LevelDebug = slog.LevelDebug
LevelInfo = slog.LevelInfo
LevelWarn = slog.LevelWarn
LevelError = slog.LevelError
LevelCrit slog.Level = 12
// for backward-compatibility
LvlTrace = LevelTrace
LvlInfo = LevelInfo
LvlDebug = LevelDebug
)
// convert from old Geth verbosity level constants
// to levels defined by slog
func FromLegacyLevel(lvl int) slog.Level {
switch lvl {
case legacyLevelCrit:
return LevelCrit
case legacyLevelError:
return slog.LevelError
case legacyLevelWarn:
return slog.LevelWarn
case legacyLevelInfo:
return slog.LevelInfo
case legacyLevelDebug:
return slog.LevelDebug
case legacyLevelTrace:
return LevelTrace
default:
break
}
// TODO: should we allow use of custom levels or force them to match existing max/min if they fall outside the range as I am doing here?
if lvl > legacyLevelTrace {
return LevelTrace
}
return LevelCrit
}
// LevelAlignedString returns a 5-character string containing the name of a Lvl.
func LevelAlignedString(l slog.Level) string {
switch l {
case LevelTrace:
return "TRACE"
case slog.LevelDebug:
return "DEBUG"
case slog.LevelInfo:
return "INFO "
case slog.LevelWarn:
return "WARN "
case slog.LevelError:
return "ERROR"
case LevelCrit:
return "CRIT "
default:
return "unknown level"
}
}
// LevelString returns a string containing the name of a Lvl.
func LevelString(l slog.Level) string {
switch l {
case LevelTrace:
return "trace"
case slog.LevelDebug:
return "debug"
case slog.LevelInfo:
return "info"
case slog.LevelWarn:
return "warn"
case slog.LevelError:
return "error"
case LevelCrit:
return "crit"
default:
return "unknown"
}
}
// LevelFromString returns the appropriate Lvl from a string name.
// Useful for parsing command line args and configuration files.
func LevelFromString(lvlString string) (slog.Level, error) {
switch lvlString {
case "trace", "trce":
return LevelTrace, nil
case "debug", "dbug":
return LevelDebug, nil
case "info":
return LevelInfo, nil
case "warn":
return LevelWarn, nil
case "error", "eror":
return LevelError, nil
case "crit":
return LevelCrit, nil
default:
return LvlDebug, fmt.Errorf("unknown level: %v", lvlString)
}
}
// A Logger writes key/value pairs to a Handler
type Logger interface {
// With returns a new Logger that has this logger's attributes plus the given attributes
With(ctx ...interface{}) Logger
// With returns a new Logger that has this logger's attributes plus the given attributes. Identical to 'With'.
New(ctx ...interface{}) Logger
// Log logs a message at the specified level with context key/value pairs
Log(level slog.Level, msg string, ctx ...interface{})
// Trace log a message at the trace level with context key/value pairs
Trace(msg string, ctx ...interface{})
// Debug logs a message at the debug level with context key/value pairs
Debug(msg string, ctx ...interface{})
// Info logs a message at the info level with context key/value pairs
Info(msg string, ctx ...interface{})
// Warn logs a message at the warn level with context key/value pairs
Warn(msg string, ctx ...interface{})
// Error logs a message at the error level with context key/value pairs
Error(msg string, ctx ...interface{})
// Crit logs a message at the crit level with context key/value pairs, and exits
Crit(msg string, ctx ...interface{})
// Write logs a message at the specified level
Write(level slog.Level, msg string, attrs ...any)
// Enabled reports whether l emits log records at the given context and level.
Enabled(ctx context.Context, level slog.Level) bool
}
type logger struct {
inner *slog.Logger
}
// NewLogger returns a logger with the specified handler set
func NewLogger(h slog.Handler) Logger {
return &logger{
slog.New(h),
}
}
// write logs a message at the specified level:
func (l *logger) Write(level slog.Level, msg string, attrs ...any) {
if !l.inner.Enabled(context.Background(), level) {
return
}
var pcs [1]uintptr
runtime.Callers(3, pcs[:])
if len(attrs)%2 != 0 {
attrs = append(attrs, nil, errorKey, "Normalized odd number of arguments by adding nil")
}
r := slog.NewRecord(time.Now(), level, msg, pcs[0])
r.Add(attrs...)
l.inner.Handler().Handle(context.Background(), r)
}
func (l *logger) Log(level slog.Level, msg string, attrs ...any) {
l.Write(level, msg, attrs...)
}
func (l *logger) With(ctx ...interface{}) Logger {
return &logger{l.inner.With(ctx...)}
}
func (l *logger) New(ctx ...interface{}) Logger {
return l.With(ctx...)
}
// Enabled reports whether l emits log records at the given context and level.
func (l *logger) Enabled(ctx context.Context, level slog.Level) bool {
return l.inner.Enabled(ctx, level)
}
func (l *logger) Trace(msg string, ctx ...interface{}) {
l.Write(LevelTrace, msg, ctx...)
}
func (l *logger) Debug(msg string, ctx ...interface{}) {
l.Write(slog.LevelDebug, msg, ctx...)
}
func (l *logger) Info(msg string, ctx ...interface{}) {
l.Write(slog.LevelInfo, msg, ctx...)
}
func (l *logger) Warn(msg string, ctx ...any) {
l.Write(slog.LevelWarn, msg, ctx...)
}
func (l *logger) Error(msg string, ctx ...interface{}) {
l.Write(slog.LevelError, msg, ctx...)
}
func (l *logger) Crit(msg string, ctx ...interface{}) {
l.Write(LevelCrit, msg, ctx...)
os.Exit(1)
}