github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/zlog/logr.go (about)

     1  package zlog
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/go-logr/logr"
     8  	"go.uber.org/zap"
     9  	"go.uber.org/zap/zapcore"
    10  )
    11  
    12  var _ logr.LogSink = (*logrImpl)(nil)
    13  var _ logr.CallDepthLogSink = (*logrImpl)(nil)
    14  
    15  // NewLogrLogger creates a new logr.Logger.
    16  func NewLogrLogger(options ...func(*LogrOptions)) logr.Logger {
    17  	opts := newLogrOptions(options)
    18  	l := opts.Logger.WithOptions(zap.AddCallerSkip(1))
    19  	sink := &logrImpl{opts: opts, l: l}
    20  	return logr.New(sink)
    21  }
    22  
    23  func newLogrOptions(options []func(*LogrOptions)) *LogrOptions {
    24  	opts := &LogrOptions{
    25  		DPanicOnInvalidLog: true,
    26  	}
    27  	for _, f := range options {
    28  		f(opts)
    29  	}
    30  	// Set defaults.
    31  	if opts.ErrorKey == "" {
    32  		opts.ErrorKey = "error"
    33  	}
    34  	if opts.Logger == nil {
    35  		opts.Logger = L().Logger
    36  	}
    37  	return opts
    38  }
    39  
    40  // LogrOptions customizes the behavior of logr logger created by NewLogrLogger.
    41  type LogrOptions struct {
    42  	// Logger optionally configures a zap.Logger to use instead of
    43  	// the default logger.
    44  	Logger *zap.Logger
    45  
    46  	// ErrorKey replaces the default "error" field name used for the error
    47  	// in Logger.Error calls.
    48  	ErrorKey string
    49  
    50  	// NumericLevelKey controls whether the numeric logr level is
    51  	// added to each Info log message and the field key to use.
    52  	NumericLevelKey string
    53  
    54  	// DPanicOnInvalidLog controls whether extra log messages are emitted
    55  	// for invalid log calls with zap's DPanic method.
    56  	// Depending on the configuration of the zap logger, the program then
    57  	// panics after emitting the log message which is useful in development
    58  	// because such invalid log calls are bugs in the program.
    59  	// The log messages explain why a call was invalid (for example,
    60  	// non-string key, mismatched key-values pairs, etc.).
    61  	// This is enabled by default.
    62  	DPanicOnInvalidLog bool
    63  }
    64  
    65  type logrImpl struct {
    66  	opts *LogrOptions
    67  	l    *zap.Logger
    68  }
    69  
    70  func (r *logrImpl) Init(ri logr.RuntimeInfo) {
    71  	r.l = r.l.WithOptions(zap.AddCallerSkip(ri.CallDepth))
    72  }
    73  
    74  const (
    75  	// noLevel tells handleFields to not inject a numeric log level field.
    76  	noLevel = -1
    77  )
    78  
    79  // handleFields is a slightly modified version of zap.SugaredLogger.sweetenFields.
    80  func (r *logrImpl) handleFields(lv int, args []any, additional ...zap.Field) []zap.Field {
    81  	injectNumLevel := r.opts.NumericLevelKey != "" && lv != noLevel
    82  
    83  	if len(args) == 0 {
    84  		// fast-return if we have no sugared fields and no "v" field
    85  		if !injectNumLevel {
    86  			return additional
    87  		}
    88  		return append(additional, zap.Int(r.opts.NumericLevelKey, lv))
    89  	}
    90  
    91  	// Unlike Zap, we can be pretty sure users aren't passing structured.
    92  	// fields (since logr has no concept of that), so guess that we need a
    93  	// little less space.
    94  	numFields := len(args)/2 + len(additional)
    95  	if injectNumLevel {
    96  		numFields++
    97  	}
    98  	fields := make([]zap.Field, 0, numFields)
    99  	if injectNumLevel {
   100  		fields = append(fields, zap.Int(r.opts.NumericLevelKey, lv))
   101  	}
   102  	for i := 0; i < len(args); {
   103  		// Check just in case for strongly-typed Zap fields,
   104  		// which might be illegal (since it breaks implementation agnosticism).
   105  		// If disabled, we can give a better error message.
   106  		if f, ok := args[i].(zap.Field); ok {
   107  			fields = append(fields, f)
   108  			i++
   109  			continue
   110  		}
   111  
   112  		// Make sure this isn't a mismatched key.
   113  		if i == len(args)-1 {
   114  			if r.opts.DPanicOnInvalidLog {
   115  				r.l.WithOptions(zap.AddCallerSkip(1)).
   116  					DPanic("odd number of arguments passed as key-value pairs for logging", toZapField("ignoredKey", args[i]))
   117  			}
   118  			break
   119  		}
   120  
   121  		// Process a key-value pair, ensuring that the key is a string.
   122  		// If the key isn't a string, DPanic and stop checking the later arguments.
   123  		key, val := args[i], args[i+1]
   124  		rvKey := reflect.ValueOf(key)
   125  		if rvKey.Kind() != reflect.String {
   126  			if r.opts.DPanicOnInvalidLog {
   127  				r.l.WithOptions(zap.AddCallerSkip(1)).
   128  					DPanic("non-string key passed to logging, ignoring all later arguments", toZapField("invalidKey", key))
   129  			}
   130  			break
   131  		}
   132  
   133  		keyStr := rvKey.String()
   134  		fields = append(fields, toZapField(keyStr, val))
   135  		i += 2
   136  	}
   137  
   138  	return append(fields, additional...)
   139  }
   140  
   141  func toZapField(field string, val any) zap.Field {
   142  	// Handle types that implement logr.Marshaler: log the replacement
   143  	// object instead of the original one.
   144  	if m, ok := val.(logr.Marshaler); ok {
   145  		field, val = invokeLogrMarshaler(field, m)
   146  	}
   147  	return zap.Any(field, val)
   148  }
   149  
   150  func invokeLogrMarshaler(field string, m logr.Marshaler) (f string, ret any) {
   151  	defer func() {
   152  		if r := recover(); r != nil {
   153  			ret = fmt.Sprintf("PANIC=%s", r)
   154  			f = field + "Error"
   155  		}
   156  	}()
   157  	return field, m.MarshalLog()
   158  }
   159  
   160  // Zap levels are int8, make sure we stay in bounds.
   161  // logr itself should ensure we never get negative values.
   162  func logrToZapLevel(lv int) zapcore.Level {
   163  	if lv > 127 {
   164  		lv = 127
   165  	}
   166  	return 0 - zapcore.Level(lv)
   167  }
   168  
   169  func (r *logrImpl) Enabled(lv int) bool {
   170  	return r.l.Core().Enabled(logrToZapLevel(lv))
   171  }
   172  
   173  func (r *logrImpl) Info(lv int, msg string, keyAndVals ...any) {
   174  	if ce := r.l.Check(logrToZapLevel(lv), msg); ce != nil {
   175  		ce.Write(r.handleFields(lv, keyAndVals)...)
   176  	}
   177  }
   178  
   179  func (r *logrImpl) Error(err error, msg string, keyAndVals ...any) {
   180  	if ce := r.l.Check(zap.ErrorLevel, msg); ce != nil {
   181  		ce.Write(r.handleFields(noLevel, keyAndVals, zap.NamedError(r.opts.ErrorKey, err))...)
   182  	}
   183  }
   184  
   185  func (r *logrImpl) WithValues(keyAndValues ...any) logr.LogSink {
   186  	clone := *r
   187  	clone.l = r.l.With(r.handleFields(noLevel, keyAndValues)...)
   188  	return &clone
   189  }
   190  
   191  func (r *logrImpl) WithName(name string) logr.LogSink {
   192  	clone := *r
   193  	clone.l = r.l.Named(name)
   194  	return &clone
   195  }
   196  
   197  func (r *logrImpl) WithCallDepth(depth int) logr.LogSink {
   198  	clone := *r
   199  	clone.l = r.l.WithOptions(zap.AddCallerSkip(depth))
   200  	return &clone
   201  }
   202  
   203  // Underlier exposes access to the underlying logging implementation.
   204  // Since callers only have a logr.Logger, they have to know which
   205  // implementation is in use, so this interface is less of an abstraction
   206  // and more of way to test type conversion.
   207  type Underlier interface {
   208  	GetUnderlying() *zap.Logger
   209  }
   210  
   211  func (r *logrImpl) GetUnderlying() *zap.Logger {
   212  	return r.l
   213  }