230 lines
5.4 KiB
Go
230 lines
5.4 KiB
Go
// Copyright Earl Warren <contact@earl-warren.org>
|
|
// Copyright Loïc Dachary <loic@dachary.org>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package logger
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type logger struct {
|
|
levels map[Level]int
|
|
writer io.Writer
|
|
logger *slog.Logger
|
|
level Level
|
|
levelVar *slog.LevelVar
|
|
}
|
|
|
|
var levels = map[Level]int{
|
|
Trace: int(slog.LevelDebug) - 1,
|
|
Debug: int(slog.LevelDebug),
|
|
Info: int(slog.LevelInfo),
|
|
Warn: int(slog.LevelWarn),
|
|
Error: int(slog.LevelError),
|
|
Fatal: int(slog.LevelError + 1),
|
|
}
|
|
|
|
var levelList = []Level{
|
|
Trace,
|
|
Debug,
|
|
Info,
|
|
Warn,
|
|
Error,
|
|
Fatal,
|
|
}
|
|
|
|
func MoreVerbose(level Level) *Level {
|
|
position := levelToPosition(level) - 1
|
|
if position > 0 {
|
|
return &levelList[position]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func LessVerbose(level Level) *Level {
|
|
position := levelToPosition(level) + 1
|
|
if position < len(levelList) {
|
|
return &levelList[position]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func levelToPosition(level Level) int {
|
|
var i int
|
|
for i = 0; i < len(levelList); i++ {
|
|
if levelList[i] == level {
|
|
break
|
|
}
|
|
}
|
|
if i >= len(levelList) {
|
|
panic(fmt.Errorf("unknown verbosity %v", level))
|
|
}
|
|
return i
|
|
}
|
|
|
|
func NewLogger() Interface {
|
|
l := &logger{}
|
|
l.Init()
|
|
return l
|
|
}
|
|
|
|
type captureLogger struct {
|
|
logger
|
|
buf *bytes.Buffer
|
|
}
|
|
|
|
func NewCaptureLogger() CaptureInterface {
|
|
l := &captureLogger{}
|
|
l.buf = new(bytes.Buffer)
|
|
l.writer = l.buf
|
|
l.Init()
|
|
return l
|
|
}
|
|
|
|
func (o *captureLogger) String() string {
|
|
return o.buf.String()
|
|
}
|
|
|
|
func (o *captureLogger) GetBuffer() *bytes.Buffer {
|
|
return o.buf
|
|
}
|
|
|
|
func (o *captureLogger) Reset() {
|
|
o.buf.Reset()
|
|
}
|
|
|
|
var filenamePrefix string
|
|
|
|
func init() {
|
|
_, filename, _, _ := runtime.Caller(0)
|
|
filenamePrefix = strings.TrimSuffix(filename, "logger.go")
|
|
if filenamePrefix == filename {
|
|
// in case the source code file is moved, we can not trim the suffix, the code above should also be updated.
|
|
panic("unable to detect correct package prefix, please update file: " + filename)
|
|
}
|
|
}
|
|
|
|
func (o *logger) Log(skip int, level Level, format string, args ...any) {
|
|
slogLevel := levels[level]
|
|
logger := o.logger
|
|
if !logger.Handler().Enabled(context.Background(), slog.Level(slogLevel)) {
|
|
return
|
|
}
|
|
var pcs [1]uintptr
|
|
runtime.Callers(2+skip, pcs[:]) // 2 is to skip [Callers(), Log()]
|
|
r := slog.NewRecord(time.Now(), slog.Level(slogLevel), fmt.Sprintf(format, args...), pcs[0])
|
|
_ = logger.Handler().Handle(context.Background(), r)
|
|
}
|
|
|
|
func (o *logger) Init() {
|
|
replace := func(groups []string, a slog.Attr) slog.Attr {
|
|
if a.Key == slog.TimeKey && len(groups) == 0 {
|
|
return slog.Attr{}
|
|
}
|
|
if a.Key == slog.SourceKey {
|
|
source := a.Value.Any().(*slog.Source)
|
|
var function string
|
|
dot := strings.LastIndex(source.Function, ".")
|
|
if dot >= 0 {
|
|
function = ":" + source.Function[dot+1:]
|
|
}
|
|
source.File = strings.TrimPrefix(source.File, projectPackagePrefix) + function
|
|
}
|
|
return a
|
|
}
|
|
o.levelVar = new(slog.LevelVar)
|
|
if o.writer == nil {
|
|
o.writer = os.Stdout
|
|
}
|
|
o.logger = slog.New(slog.NewTextHandler(o.writer, &slog.HandlerOptions{
|
|
Level: o.levelVar,
|
|
AddSource: true,
|
|
ReplaceAttr: replace,
|
|
}))
|
|
}
|
|
|
|
func (o *logger) SetLevel(level Level) {
|
|
o.level = level
|
|
o.levelVar.Set(slog.Level(levels[o.level]))
|
|
}
|
|
|
|
func (o *logger) GetLevel() Level {
|
|
return o.level
|
|
}
|
|
|
|
func (o *logger) SetWriter(out io.Writer) {
|
|
o.writer = out
|
|
o.Init()
|
|
}
|
|
|
|
func (o *logger) Message(message string, args ...any) { o.Log(1, Info, message, args...) }
|
|
func (o *logger) Trace(message string, args ...any) { o.Log(1, Trace, message, args...) }
|
|
func (o *logger) Debug(message string, args ...any) { o.Log(1, Debug, message, args...) }
|
|
func (o *logger) Info(message string, args ...any) { o.Log(1, Info, message, args...) }
|
|
func (o *logger) Warn(message string, args ...any) { o.Log(1, Warn, message, args...) }
|
|
func (o *logger) Error(message string, args ...any) { o.Log(1, Error, message, args...) }
|
|
func (o *logger) Fatal(message string, args ...any) { o.Log(1, Fatal, message, args...) }
|
|
|
|
type Logger struct {
|
|
logger Interface
|
|
}
|
|
|
|
func (o *Logger) GetLogger() Interface { return o.logger }
|
|
|
|
func (o *Logger) SetLogger(logger Interface) { o.logger = logger }
|
|
|
|
func (o *Logger) SetLevel(level Level) { o.logger.SetLevel(level) }
|
|
|
|
func (o *Logger) GetLevel() Level { return o.logger.GetLevel() }
|
|
|
|
func (o *Logger) Message(message string, args ...any) {
|
|
o.logger.Log(1, Message, message, args...)
|
|
}
|
|
|
|
func (o *Logger) Trace(message string, args ...any) {
|
|
o.logger.Log(1, Trace, message, args...)
|
|
}
|
|
|
|
func (o *Logger) Debug(message string, args ...any) {
|
|
o.logger.Log(1, Debug, message, args...)
|
|
}
|
|
|
|
func (o *Logger) Info(message string, args ...any) {
|
|
o.logger.Log(1, Info, message, args...)
|
|
}
|
|
|
|
func (o *Logger) Warn(message string, args ...any) {
|
|
o.logger.Log(1, Warn, message, args...)
|
|
}
|
|
|
|
func (o *Logger) Error(message string, args ...any) {
|
|
o.logger.Log(1, Error, message, args...)
|
|
}
|
|
|
|
func (o *Logger) Fatal(message string, args ...any) {
|
|
o.logger.Log(1, Fatal, message, args...)
|
|
}
|
|
|
|
func (o *Logger) Log(skip int, level Level, message string, args ...any) {
|
|
o.logger.Log(skip+1, level, message, args...)
|
|
}
|
|
|
|
var projectPackagePrefix string
|
|
|
|
func init() {
|
|
_, filename, _, _ := runtime.Caller(0)
|
|
projectPackagePrefix = strings.TrimSuffix(filename, "logger/logger.go")
|
|
if projectPackagePrefix == filename {
|
|
// in case the source code file is moved, we can not trim the suffix, the code above should also be updated.
|
|
panic("unable to detect correct package prefix, please update file: " + filename)
|
|
}
|
|
}
|