2024-02-08 08:17:53 +03:00
|
|
|
package slog
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"log/slog"
|
|
|
|
"os"
|
2024-03-04 22:54:11 +03:00
|
|
|
"regexp"
|
2024-02-08 08:17:53 +03:00
|
|
|
"runtime"
|
|
|
|
"strconv"
|
2024-02-22 08:57:21 +03:00
|
|
|
"sync"
|
2024-02-08 08:17:53 +03:00
|
|
|
|
|
|
|
"go.unistack.org/micro/v3/logger"
|
2024-05-09 16:41:22 +03:00
|
|
|
"go.unistack.org/micro/v3/semconv"
|
2024-02-08 08:17:53 +03:00
|
|
|
"go.unistack.org/micro/v3/tracer"
|
|
|
|
)
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
const (
|
|
|
|
badKey = "!BADKEY"
|
|
|
|
// defaultCallerSkipCount used by logger
|
|
|
|
defaultCallerSkipCount = 3
|
|
|
|
)
|
|
|
|
|
2024-03-04 22:54:11 +03:00
|
|
|
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
|
|
|
|
|
2024-02-08 08:17:53 +03:00
|
|
|
var (
|
|
|
|
traceValue = slog.StringValue("trace")
|
|
|
|
debugValue = slog.StringValue("debug")
|
|
|
|
infoValue = slog.StringValue("info")
|
|
|
|
warnValue = slog.StringValue("warn")
|
|
|
|
errorValue = slog.StringValue("error")
|
|
|
|
fatalValue = slog.StringValue("fatal")
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
|
|
|
|
switch a.Key {
|
|
|
|
case slog.SourceKey:
|
|
|
|
source := a.Value.Any().(*slog.Source)
|
|
|
|
a.Value = slog.StringValue(source.File + ":" + strconv.Itoa(source.Line))
|
2024-03-04 23:58:31 +03:00
|
|
|
a.Key = s.opts.SourceKey
|
2024-02-08 08:17:53 +03:00
|
|
|
case slog.TimeKey:
|
2024-03-04 23:58:31 +03:00
|
|
|
a.Key = s.opts.TimeKey
|
2024-02-08 08:17:53 +03:00
|
|
|
case slog.MessageKey:
|
2024-03-04 23:58:31 +03:00
|
|
|
a.Key = s.opts.MessageKey
|
2024-02-08 08:17:53 +03:00
|
|
|
case slog.LevelKey:
|
|
|
|
level := a.Value.Any().(slog.Level)
|
|
|
|
lvl := slogToLoggerLevel(level)
|
2024-03-04 23:58:31 +03:00
|
|
|
a.Key = s.opts.LevelKey
|
2024-02-08 08:17:53 +03:00
|
|
|
switch {
|
|
|
|
case lvl < logger.DebugLevel:
|
|
|
|
a.Value = traceValue
|
|
|
|
case lvl < logger.InfoLevel:
|
|
|
|
a.Value = debugValue
|
|
|
|
case lvl < logger.WarnLevel:
|
|
|
|
a.Value = infoValue
|
|
|
|
case lvl < logger.ErrorLevel:
|
|
|
|
a.Value = warnValue
|
|
|
|
case lvl < logger.FatalLevel:
|
|
|
|
a.Value = errorValue
|
|
|
|
case lvl >= logger.FatalLevel:
|
|
|
|
a.Value = fatalValue
|
|
|
|
default:
|
|
|
|
a.Value = infoValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
type slogLogger struct {
|
2024-03-04 23:58:31 +03:00
|
|
|
leveler *slog.LevelVar
|
2024-03-07 07:43:52 +03:00
|
|
|
handler slog.Handler
|
2024-03-04 23:58:31 +03:00
|
|
|
opts logger.Options
|
|
|
|
mu sync.RWMutex
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger {
|
2024-02-22 08:57:21 +03:00
|
|
|
s.mu.RLock()
|
2024-02-08 08:17:53 +03:00
|
|
|
options := s.opts
|
2024-03-07 07:43:52 +03:00
|
|
|
s.mu.RUnlock()
|
2024-02-08 08:17:53 +03:00
|
|
|
|
|
|
|
for _, o := range opts {
|
|
|
|
o(&options)
|
|
|
|
}
|
|
|
|
|
|
|
|
l := &slogLogger{
|
2024-03-04 23:58:31 +03:00
|
|
|
opts: options,
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
l.leveler = new(slog.LevelVar)
|
|
|
|
handleOpt := &slog.HandlerOptions{
|
2024-03-06 00:53:20 +03:00
|
|
|
ReplaceAttr: l.renameAttr,
|
2024-02-08 08:17:53 +03:00
|
|
|
Level: l.leveler,
|
2024-03-06 00:53:20 +03:00
|
|
|
AddSource: l.opts.AddSource,
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
l.leveler.Set(loggerToSlogLevel(l.opts.Level))
|
2024-03-07 08:19:14 +03:00
|
|
|
l.handler = slog.New(slog.NewJSONHandler(options.Out, handleOpt)).With(options.Fields...).Handler()
|
2024-02-22 08:57:21 +03:00
|
|
|
|
2024-02-08 08:17:53 +03:00
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slogLogger) V(level logger.Level) bool {
|
|
|
|
return s.opts.Level.Enabled(level)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slogLogger) Level(level logger.Level) {
|
|
|
|
s.leveler.Set(loggerToSlogLevel(level))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slogLogger) Options() logger.Options {
|
|
|
|
return s.opts
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger {
|
2024-02-22 08:57:21 +03:00
|
|
|
s.mu.RLock()
|
2024-03-07 07:43:52 +03:00
|
|
|
level := s.leveler.Level()
|
|
|
|
options := s.opts
|
|
|
|
s.mu.RUnlock()
|
|
|
|
|
|
|
|
l := &slogLogger{opts: options}
|
2024-03-06 00:53:20 +03:00
|
|
|
l.leveler = new(slog.LevelVar)
|
2024-03-07 07:43:52 +03:00
|
|
|
l.leveler.Set(level)
|
2024-02-08 08:17:53 +03:00
|
|
|
|
|
|
|
handleOpt := &slog.HandlerOptions{
|
2024-03-06 00:53:20 +03:00
|
|
|
ReplaceAttr: l.renameAttr,
|
|
|
|
Level: l.leveler,
|
|
|
|
AddSource: l.opts.AddSource,
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
2024-03-07 08:19:14 +03:00
|
|
|
l.handler = slog.New(slog.NewJSONHandler(l.opts.Out, handleOpt)).With(attrs...).Handler()
|
2024-02-22 08:57:21 +03:00
|
|
|
|
2024-03-06 00:53:20 +03:00
|
|
|
return l
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *slogLogger) Init(opts ...logger.Option) error {
|
2024-02-22 08:57:21 +03:00
|
|
|
s.mu.Lock()
|
2024-02-08 08:17:53 +03:00
|
|
|
|
2024-03-04 23:58:31 +03:00
|
|
|
if len(s.opts.ContextAttrFuncs) == 0 {
|
|
|
|
s.opts.ContextAttrFuncs = logger.DefaultContextAttrFuncs
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
2024-03-04 23:58:31 +03:00
|
|
|
|
|
|
|
for _, o := range opts {
|
|
|
|
o(&s.opts)
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
s.leveler = new(slog.LevelVar)
|
|
|
|
handleOpt := &slog.HandlerOptions{
|
|
|
|
ReplaceAttr: s.renameAttr,
|
|
|
|
Level: s.leveler,
|
2024-03-06 00:53:20 +03:00
|
|
|
AddSource: s.opts.AddSource,
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
s.leveler.Set(loggerToSlogLevel(s.opts.Level))
|
2024-03-07 08:19:14 +03:00
|
|
|
s.handler = slog.New(slog.NewJSONHandler(s.opts.Out, handleOpt)).With(s.opts.Fields...).Handler()
|
2024-02-22 08:57:21 +03:00
|
|
|
s.mu.Unlock()
|
|
|
|
|
2024-02-08 08:17:53 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) {
|
|
|
|
s.printLog(ctx, lvl, msg, attrs...)
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
|
|
|
|
s.printLog(ctx, logger.InfoLevel, msg, attrs...)
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
|
|
|
|
s.printLog(ctx, logger.DebugLevel, msg, attrs...)
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
|
|
|
|
s.printLog(ctx, logger.TraceLevel, msg, attrs...)
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
|
|
|
|
s.printLog(ctx, logger.ErrorLevel, msg, attrs...)
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
|
|
|
|
s.printLog(ctx, logger.FatalLevel, msg, attrs...)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2024-03-06 00:53:20 +03:00
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
|
|
|
|
s.printLog(ctx, logger.WarnLevel, msg, attrs...)
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) Name() string {
|
|
|
|
return s.opts.Name
|
|
|
|
}
|
2024-03-06 00:53:20 +03:00
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) String() string {
|
|
|
|
return "slog"
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
func (s *slogLogger) printLog(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) {
|
|
|
|
if !s.V(lvl) {
|
2024-02-08 08:17:53 +03:00
|
|
|
return
|
|
|
|
}
|
2024-03-06 00:53:20 +03:00
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", lvl.String()).Inc()
|
|
|
|
|
|
|
|
attrs = prepareAttributes(attrs)
|
2024-02-08 08:17:53 +03:00
|
|
|
|
2024-03-06 00:53:20 +03:00
|
|
|
for _, fn := range s.opts.ContextAttrFuncs {
|
2024-10-12 12:37:43 +03:00
|
|
|
a := prepareAttributes(fn(ctx))
|
|
|
|
attrs = append(attrs, a...)
|
2024-03-06 00:53:20 +03:00
|
|
|
}
|
|
|
|
|
2024-10-12 12:37:43 +03:00
|
|
|
for _, attr := range attrs {
|
|
|
|
if ve, hasErr := attr.(error); hasErr && ve != nil {
|
|
|
|
attrs = append(attrs, slog.String(s.opts.ErrorKey, ve.Error()))
|
2024-02-08 08:17:53 +03:00
|
|
|
if span, ok := tracer.SpanFromContext(ctx); ok {
|
2024-10-12 12:37:43 +03:00
|
|
|
span.SetStatus(tracer.SpanStatusError, ve.Error())
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
2024-03-06 00:53:20 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2024-10-12 12:37:43 +03:00
|
|
|
|
|
|
|
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
|
2024-03-04 22:54:11 +03:00
|
|
|
stackInfo := make([]byte, 1024*1024)
|
|
|
|
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
|
|
|
|
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
|
|
|
|
if len(traceLines) != 0 {
|
2024-10-12 12:37:43 +03:00
|
|
|
attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
}
|
2024-03-06 00:53:20 +03:00
|
|
|
}
|
2024-02-08 08:17:53 +03:00
|
|
|
|
|
|
|
var pcs [1]uintptr
|
2024-10-12 12:37:43 +03:00
|
|
|
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, printLog, LogLvlMethod]
|
|
|
|
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), msg, pcs[0])
|
2024-03-06 00:53:20 +03:00
|
|
|
r.Add(attrs...)
|
2024-03-07 07:43:52 +03:00
|
|
|
_ = s.handler.Handle(ctx, r)
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewLogger(opts ...logger.Option) logger.Logger {
|
|
|
|
s := &slogLogger{
|
2024-03-04 23:58:31 +03:00
|
|
|
opts: logger.NewOptions(opts...),
|
2024-02-08 08:17:53 +03:00
|
|
|
}
|
2024-10-12 12:37:43 +03:00
|
|
|
s.opts.CallerSkipCount = defaultCallerSkipCount
|
2024-03-04 23:58:31 +03:00
|
|
|
|
2024-02-08 08:17:53 +03:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
func loggerToSlogLevel(level logger.Level) slog.Level {
|
|
|
|
switch level {
|
|
|
|
case logger.DebugLevel:
|
|
|
|
return slog.LevelDebug
|
|
|
|
case logger.WarnLevel:
|
|
|
|
return slog.LevelWarn
|
|
|
|
case logger.ErrorLevel:
|
|
|
|
return slog.LevelError
|
|
|
|
case logger.TraceLevel:
|
|
|
|
return slog.LevelDebug - 1
|
|
|
|
case logger.FatalLevel:
|
|
|
|
return slog.LevelError + 1
|
|
|
|
default:
|
|
|
|
return slog.LevelInfo
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func slogToLoggerLevel(level slog.Level) logger.Level {
|
|
|
|
switch level {
|
|
|
|
case slog.LevelDebug:
|
|
|
|
return logger.DebugLevel
|
|
|
|
case slog.LevelWarn:
|
|
|
|
return logger.WarnLevel
|
|
|
|
case slog.LevelError:
|
|
|
|
return logger.ErrorLevel
|
|
|
|
case slog.LevelDebug - 1:
|
|
|
|
return logger.TraceLevel
|
|
|
|
case slog.LevelError + 1:
|
|
|
|
return logger.FatalLevel
|
|
|
|
default:
|
|
|
|
return logger.InfoLevel
|
|
|
|
}
|
|
|
|
}
|
2024-10-12 12:37:43 +03:00
|
|
|
|
|
|
|
func prepareAttributes(attrs []interface{}) []interface{} {
|
|
|
|
if len(attrs)%2 == 1 {
|
|
|
|
attrs = append(attrs, badKey)
|
|
|
|
attrs[len(attrs)-1], attrs[len(attrs)-2] = attrs[len(attrs)-2], attrs[len(attrs)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return attrs
|
|
|
|
}
|