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

     1  //go:build go1.21
     2  
     3  package zlog
     4  
     5  import (
     6  	"context"
     7  	"log/slog"
     8  	"runtime"
     9  
    10  	"go.uber.org/zap"
    11  	"go.uber.org/zap/zapcore"
    12  )
    13  
    14  var _ slog.Handler = (*slogImpl)(nil)
    15  
    16  // For Go1.21+, we also replace the default logger in slog package.
    17  func init() {
    18  	replaceSlogDefault = func(l *zap.Logger, disableCaller bool) func() {
    19  		old := slog.Default()
    20  		slog.SetDefault(NewSlogLogger(func(opts *SlogOptions) {
    21  			opts.Logger = l
    22  			opts.DisableCaller = disableCaller
    23  		}))
    24  		return func() { slog.SetDefault(old) }
    25  	}
    26  }
    27  
    28  // SetSlogDefault replaces the slog package's default logger.
    29  // Compared to [slog.SetDefault], it does not set the log package's default
    30  // logger to l's handler, instead it sets it's output to an underlying
    31  // zap.Logger returned by L().
    32  func SetSlogDefault(l *slog.Logger) {
    33  	slog.SetDefault(l)
    34  	replaceLogDefault(L().Logger)
    35  }
    36  
    37  // NewSlogLogger creates a new slog.Logger.
    38  func NewSlogLogger(options ...func(*SlogOptions)) *slog.Logger {
    39  	opts := newSlogOptions(options)
    40  	impl := &slogImpl{
    41  		opts: opts,
    42  		l:    opts.Logger,
    43  		name: opts.Logger.Name(),
    44  	}
    45  	return slog.New(impl)
    46  }
    47  
    48  func newSlogOptions(options []func(*SlogOptions)) *SlogOptions {
    49  	opts := &SlogOptions{}
    50  	for _, f := range options {
    51  		f(opts)
    52  	}
    53  	// Set defaults.
    54  	if opts.Logger == nil {
    55  		opts.Logger = L().Logger
    56  	}
    57  	return opts
    58  }
    59  
    60  // SlogOptions customizes the behavior of slog logger created by NewSlogLogger.
    61  type SlogOptions struct {
    62  	// Logger optionally configures a zap.Logger to use instead of
    63  	// the default logger.
    64  	Logger *zap.Logger
    65  
    66  	// DisableCaller stops annotating logs with calling function's file
    67  	// name and line number. By default, all logs are annotated.
    68  	DisableCaller bool
    69  
    70  	// ReplaceAttr is called to rewrite an attribute before it is logged.
    71  	// The attribute's value has been resolved (see [slog.Value.Resolve]).
    72  	// If ReplaceAttr returns a zero ReplaceResult, the attribute is discarded.
    73  	//
    74  	// ReplaceAttr may return a single field by ReplaceResult.Field, or return
    75  	// multi fields by ReplaceResult.Multi.
    76  	// User may use this option to check error attribute and convert to a
    77  	// zap.Error field to unify the key for errors, if the error contains
    78  	// stack information, the function can also get it and add the
    79  	// stacktrace to log.
    80  	//
    81  	// Note, for simplicity and better performance, it is different with
    82  	// [slog.HandlerOptions.ReplaceAttr], only attributes directly passed to
    83  	// [slog.Logger.With] and the log methods
    84  	// (such as [slog.Logger.Log], [slog.Logger.Info], etc.)
    85  	// are passed to this function, the builtin attributes and nested attributes
    86  	// in groups are not.
    87  	// The first argument is a list of currently open groups that added by
    88  	// [slog.Logger.WithGroup].
    89  	ReplaceAttr func(groups []string, a slog.Attr) (rr ReplaceResult)
    90  }
    91  
    92  // ReplaceResult is a result returned by SlogOptions.ReplaceAttr.
    93  // If there is only one field, set it to Field, else set multi fields to Multi.
    94  // The logger checks both Field and Multi to append to result log.
    95  //
    96  // Note that the field Multi must not contain values whose Type is
    97  // UnknownType or SkipType.
    98  type ReplaceResult struct {
    99  	Field zapcore.Field
   100  	Multi []zapcore.Field
   101  }
   102  
   103  func (rr ReplaceResult) hasValidField() bool {
   104  	return (rr.Field.Type != zapcore.UnknownType && rr.Field != zap.Skip()) || len(rr.Multi) > 0
   105  }
   106  
   107  type slogImpl struct {
   108  	opts      *SlogOptions
   109  	l         *zap.Logger
   110  	name      string   // logger name
   111  	allGroups []string // all groups started with WithGroup
   112  	groups    []string // groups that not converted to namespace field
   113  }
   114  
   115  func (h *slogImpl) Enabled(ctx context.Context, level slog.Level) bool {
   116  	zLevel := slogToZapLevel(level)
   117  	ctxFunc := globals.Props.cfg.CtxHandler.ChangeLevel
   118  	if ctx == nil || ctxFunc == nil {
   119  		return h.l.Core().Enabled(zLevel)
   120  	}
   121  	if ctxLevel := ctxFunc(ctx); ctxLevel != nil {
   122  		return ctxLevel.Enabled(zLevel)
   123  	}
   124  	return h.l.Core().Enabled(zLevel)
   125  }
   126  
   127  func (h *slogImpl) Handle(ctx context.Context, record slog.Record) error {
   128  	var ctxResult CtxResult
   129  	ctxFunc := globals.Props.cfg.CtxHandler.WithCtx
   130  	if ctx != nil && ctxFunc != nil {
   131  		ctxResult = ctxFunc(ctx)
   132  	}
   133  	core := h.l.Core()
   134  	if ctxResult.Level != nil {
   135  		core = changeLevel(*ctxResult.Level)(core)
   136  	}
   137  
   138  	ent := zapcore.Entry{
   139  		Level:      slogToZapLevel(record.Level),
   140  		Time:       record.Time,
   141  		LoggerName: h.name,
   142  		Message:    record.Message,
   143  	}
   144  	ce := core.Check(ent, nil)
   145  	if ce == nil {
   146  		return nil
   147  	}
   148  
   149  	// Add caller information.
   150  	if record.PC > 0 && !h.opts.DisableCaller {
   151  		var stack [1]uintptr
   152  		stack[0] = record.PC
   153  		frame, _ := runtime.CallersFrames(stack[:]).Next()
   154  		ce.Caller = zapcore.EntryCaller{
   155  			Defined:  true,
   156  			PC:       frame.PC,
   157  			File:     frame.File,
   158  			Line:     frame.Line,
   159  			Function: frame.Function,
   160  		}
   161  	}
   162  
   163  	var addedNamespace bool
   164  	fields := ctxResult.Fields
   165  	if record.NumAttrs() > 0 {
   166  		guessCap := len(fields) + record.NumAttrs() + len(h.groups) + 2
   167  		fields = make([]zap.Field, 0, guessCap)
   168  		fields = append(fields, ctxResult.Fields...)
   169  	}
   170  	record.Attrs(func(attr slog.Attr) bool {
   171  		rr := h.convertAttrToField(attr)
   172  		if !addedNamespace && len(h.groups) > 0 && rr.hasValidField() {
   173  			// Namespace are added only if at least one field is present
   174  			// to avoid creating empty groups.
   175  			fields = h.appendGroups(fields)
   176  			addedNamespace = true
   177  		}
   178  		if rr.Field.Type != zapcore.UnknownType {
   179  			fields = append(fields, rr.Field)
   180  		}
   181  		if len(rr.Multi) > 0 {
   182  			fields = append(fields, rr.Multi...)
   183  		}
   184  		return true
   185  	})
   186  
   187  	ce.Write(fields...)
   188  	return nil
   189  }
   190  
   191  func (h *slogImpl) WithAttrs(attrs []slog.Attr) slog.Handler {
   192  	guessCap := len(attrs) + len(h.groups) + 2
   193  	fields := make([]zapcore.Field, 0, guessCap)
   194  	var addedNamespace bool
   195  	for _, attr := range attrs {
   196  		rr := h.convertAttrToField(attr)
   197  		if !addedNamespace && len(h.groups) > 0 && rr.hasValidField() {
   198  			// Namespace are added only if at least one field is present
   199  			// to avoid creating empty groups.
   200  			fields = h.appendGroups(fields)
   201  			addedNamespace = true
   202  		}
   203  		if rr.Field.Type != zapcore.UnknownType {
   204  			fields = append(fields, rr.Field)
   205  		}
   206  		if len(rr.Multi) > 0 {
   207  			fields = append(fields, rr.Multi...)
   208  		}
   209  	}
   210  	clone := *h
   211  	clone.l = h.l.With(fields...)
   212  	if addedNamespace {
   213  		clone.groups = nil
   214  	}
   215  	return &clone
   216  }
   217  
   218  func (h *slogImpl) appendGroups(fields []zapcore.Field) []zapcore.Field {
   219  	for _, g := range h.groups {
   220  		fields = append(fields, zap.Namespace(g))
   221  	}
   222  	return fields
   223  }
   224  
   225  func (h *slogImpl) WithGroup(name string) slog.Handler {
   226  	// If the name is empty, WithGroup returns the receiver.
   227  	if name == "" {
   228  		return h
   229  	}
   230  
   231  	newGroups := make([]string, len(h.allGroups)+1)
   232  	copy(newGroups, h.allGroups)
   233  	newGroups[len(h.allGroups)] = name
   234  
   235  	clone := *h
   236  	clone.allGroups = newGroups
   237  	clone.groups = newGroups[len(h.allGroups)-len(h.groups):]
   238  	return &clone
   239  }
   240  
   241  func (h *slogImpl) convertAttrToField(attr slog.Attr) ReplaceResult {
   242  	// Optionally replace attrs.
   243  	// attr.Value is resolved before calling ReplaceAttr, so the user doesn't have to.
   244  	if attr.Value.Kind() == slog.KindLogValuer {
   245  		attr.Value = attr.Value.Resolve()
   246  	}
   247  	if repl := h.opts.ReplaceAttr; repl != nil {
   248  		return repl(h.allGroups, attr)
   249  	}
   250  	return ReplaceResult{Field: ConvertAttrToField(attr)}
   251  }
   252  
   253  func (h *slogImpl) GetUnderlying() *zap.Logger {
   254  	return h.opts.Logger
   255  }
   256  
   257  func slogToZapLevel(l slog.Level) zapcore.Level {
   258  	switch {
   259  	case l >= slog.LevelError:
   260  		return ErrorLevel
   261  	case l >= slog.LevelWarn:
   262  		return WarnLevel
   263  	case l >= slog.LevelInfo:
   264  		return InfoLevel
   265  	case l >= slog.LevelDebug:
   266  		return DebugLevel
   267  	default:
   268  		return TraceLevel
   269  	}
   270  }
   271  
   272  // groupObject holds all the Attrs saved in a slog.GroupValue.
   273  type groupObject []slog.Attr
   274  
   275  func (gs groupObject) MarshalLogObject(enc zapcore.ObjectEncoder) error {
   276  	for _, attr := range gs {
   277  		ConvertAttrToField(attr).AddTo(enc)
   278  	}
   279  	return nil
   280  }
   281  
   282  // ConvertAttrToField converts a slog.Attr to a zap.Field.
   283  func ConvertAttrToField(attr slog.Attr) zapcore.Field {
   284  	if attr.Equal(slog.Attr{}) {
   285  		// Ignore empty attrs.
   286  		return zap.Skip()
   287  	}
   288  
   289  	switch attr.Value.Kind() {
   290  	case slog.KindBool:
   291  		return zap.Bool(attr.Key, attr.Value.Bool())
   292  	case slog.KindDuration:
   293  		return zap.Duration(attr.Key, attr.Value.Duration())
   294  	case slog.KindFloat64:
   295  		return zap.Float64(attr.Key, attr.Value.Float64())
   296  	case slog.KindInt64:
   297  		return zap.Int64(attr.Key, attr.Value.Int64())
   298  	case slog.KindString:
   299  		return zap.String(attr.Key, attr.Value.String())
   300  	case slog.KindTime:
   301  		return zap.Time(attr.Key, attr.Value.Time())
   302  	case slog.KindUint64:
   303  		return zap.Uint64(attr.Key, attr.Value.Uint64())
   304  	case slog.KindGroup:
   305  		grpAttrs := attr.Value.Group()
   306  		if len(grpAttrs) == 0 {
   307  			return zap.Skip()
   308  		}
   309  		if attr.Key == "" {
   310  			// Inlines recursively.
   311  			return zap.Inline(groupObject(grpAttrs))
   312  		}
   313  		return zap.Object(attr.Key, groupObject(grpAttrs))
   314  	case slog.KindLogValuer:
   315  		return ConvertAttrToField(slog.Attr{
   316  			Key:   attr.Key,
   317  			Value: attr.Value.Resolve(),
   318  		})
   319  	default:
   320  		return zap.Any(attr.Key, attr.Value.Any())
   321  	}
   322  }