slogsink.go raw

   1  //go:build go1.21
   2  // +build go1.21
   3  
   4  /*
   5  Copyright 2023 The logr Authors.
   6  
   7  Licensed under the Apache License, Version 2.0 (the "License");
   8  you may not use this file except in compliance with the License.
   9  You may obtain a copy of the License at
  10  
  11      http://www.apache.org/licenses/LICENSE-2.0
  12  
  13  Unless required by applicable law or agreed to in writing, software
  14  distributed under the License is distributed on an "AS IS" BASIS,
  15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16  See the License for the specific language governing permissions and
  17  limitations under the License.
  18  */
  19  
  20  package logr
  21  
  22  import (
  23  	"context"
  24  	"log/slog"
  25  	"runtime"
  26  	"time"
  27  )
  28  
  29  var (
  30  	_ LogSink          = &slogSink{}
  31  	_ CallDepthLogSink = &slogSink{}
  32  	_ Underlier        = &slogSink{}
  33  )
  34  
  35  // Underlier is implemented by the LogSink returned by NewFromLogHandler.
  36  type Underlier interface {
  37  	// GetUnderlying returns the Handler used by the LogSink.
  38  	GetUnderlying() slog.Handler
  39  }
  40  
  41  const (
  42  	// nameKey is used to log the `WithName` values as an additional attribute.
  43  	nameKey = "logger"
  44  
  45  	// errKey is used to log the error parameter of Error as an additional attribute.
  46  	errKey = "err"
  47  )
  48  
  49  type slogSink struct {
  50  	callDepth int
  51  	name      string
  52  	handler   slog.Handler
  53  }
  54  
  55  func (l *slogSink) Init(info RuntimeInfo) {
  56  	l.callDepth = info.CallDepth
  57  }
  58  
  59  func (l *slogSink) GetUnderlying() slog.Handler {
  60  	return l.handler
  61  }
  62  
  63  func (l *slogSink) WithCallDepth(depth int) LogSink {
  64  	newLogger := *l
  65  	newLogger.callDepth += depth
  66  	return &newLogger
  67  }
  68  
  69  func (l *slogSink) Enabled(level int) bool {
  70  	return l.handler.Enabled(context.Background(), slog.Level(-level))
  71  }
  72  
  73  func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
  74  	l.log(nil, msg, slog.Level(-level), kvList...)
  75  }
  76  
  77  func (l *slogSink) Error(err error, msg string, kvList ...interface{}) {
  78  	l.log(err, msg, slog.LevelError, kvList...)
  79  }
  80  
  81  func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) {
  82  	var pcs [1]uintptr
  83  	// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
  84  	runtime.Callers(3+l.callDepth, pcs[:])
  85  
  86  	record := slog.NewRecord(time.Now(), level, msg, pcs[0])
  87  	if l.name != "" {
  88  		record.AddAttrs(slog.String(nameKey, l.name))
  89  	}
  90  	if err != nil {
  91  		record.AddAttrs(slog.Any(errKey, err))
  92  	}
  93  	record.Add(kvList...)
  94  	_ = l.handler.Handle(context.Background(), record)
  95  }
  96  
  97  func (l slogSink) WithName(name string) LogSink {
  98  	if l.name != "" {
  99  		l.name += "/"
 100  	}
 101  	l.name += name
 102  	return &l
 103  }
 104  
 105  func (l slogSink) WithValues(kvList ...interface{}) LogSink {
 106  	l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...))
 107  	return &l
 108  }
 109  
 110  func kvListToAttrs(kvList ...interface{}) []slog.Attr {
 111  	// We don't need the record itself, only its Add method.
 112  	record := slog.NewRecord(time.Time{}, 0, "", 0)
 113  	record.Add(kvList...)
 114  	attrs := make([]slog.Attr, 0, record.NumAttrs())
 115  	record.Attrs(func(attr slog.Attr) bool {
 116  		attrs = append(attrs, attr)
 117  		return true
 118  	})
 119  	return attrs
 120  }
 121