go.temporal.io/server@v1.23.0/common/log/slog.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2023 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2023 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  // Part of this implementation was taken from https://github.com/uber-go/zap/blob/99f1811d5d2a52264a9c82505a74c2709b077a73/exp/zapslog/handler.go
    26  
    27  package log
    28  
    29  import (
    30  	"context"
    31  	"log/slog"
    32  
    33  	"go.temporal.io/server/common/log/tag"
    34  	"go.uber.org/zap"
    35  	"go.uber.org/zap/zapcore"
    36  )
    37  
    38  type handler struct {
    39  	zapLogger *zap.Logger
    40  	logger    Logger
    41  	tags      []tag.Tag
    42  	group     string
    43  }
    44  
    45  var _ slog.Handler = (*handler)(nil)
    46  
    47  // NewSlogLogger creates an slog.Logger from a given logger.
    48  func NewSlogLogger(logger Logger) *slog.Logger {
    49  	logger = withIncreasedSkip(logger, 3)
    50  	return slog.New(&handler{logger: logger, zapLogger: extractZapLogger(logger), group: "", tags: nil})
    51  }
    52  
    53  // Enabled reports whether the handler handles records at the given level.
    54  func (h *handler) Enabled(_ context.Context, level slog.Level) bool {
    55  	if h.zapLogger == nil {
    56  		return true
    57  	}
    58  	return h.zapLogger.Core().Enabled(convertSlogToZapLevel(level))
    59  }
    60  
    61  // Handle implements slog.Handler.
    62  func (h *handler) Handle(_ context.Context, record slog.Record) error {
    63  	tags := make([]tag.Tag, len(h.tags), len(h.tags)+record.NumAttrs())
    64  	copy(tags, h.tags)
    65  	record.Attrs(func(attr slog.Attr) bool {
    66  		tags = append(tags, tag.NewZapTag(convertAttrToField(h.prependGroup(attr))))
    67  		return true
    68  	})
    69  	// Not capturing the log location and stack trace here. We seem to not need this functionality since our zapLogger
    70  	// adds the logging-call-at tag.
    71  	switch record.Level {
    72  	case slog.LevelDebug:
    73  		h.logger.Debug(record.Message, tags...)
    74  	case slog.LevelInfo:
    75  		h.logger.Info(record.Message, tags...)
    76  	case slog.LevelWarn:
    77  		h.logger.Warn(record.Message, tags...)
    78  	case slog.LevelError:
    79  		h.logger.Error(record.Message, tags...)
    80  	default:
    81  	}
    82  	return nil
    83  }
    84  
    85  // WithAttrs implements slog.Handler.
    86  func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler {
    87  	tags := make([]tag.Tag, len(h.tags), len(h.tags)+len(attrs))
    88  	copy(tags, h.tags)
    89  	for _, attr := range attrs {
    90  		tags = append(tags, tag.NewZapTag(convertAttrToField(h.prependGroup(attr))))
    91  	}
    92  	return &handler{logger: h.logger, tags: tags, group: h.group}
    93  }
    94  
    95  // WithGroup implements slog.Handler.
    96  func (h *handler) WithGroup(name string) slog.Handler {
    97  	group := name
    98  	if h.group != "" {
    99  		group = h.group + "." + name
   100  	}
   101  	return &handler{logger: h.logger, tags: h.tags, group: group}
   102  }
   103  
   104  func (h *handler) prependGroup(attr slog.Attr) slog.Attr {
   105  	if h.group == "" {
   106  		return attr
   107  	}
   108  	return slog.Attr{Key: h.group + "." + attr.Key, Value: attr.Value}
   109  }
   110  
   111  func extractZapLogger(logger Logger) *zap.Logger {
   112  	switch l := logger.(type) {
   113  	case *zapLogger:
   114  		return l.zl
   115  	case *throttledLogger:
   116  		return extractZapLogger(l.logger)
   117  	case *withLogger:
   118  		return extractZapLogger(l.logger)
   119  	}
   120  	return nil
   121  }
   122  
   123  // withIncreasedSkip increases the skip level for the given logger if it embeds a zapLogger.
   124  func withIncreasedSkip(logger Logger, skip int) Logger {
   125  	switch l := logger.(type) {
   126  	case *zapLogger:
   127  		return l.Skip(skip)
   128  	case *throttledLogger:
   129  		return &throttledLogger{
   130  			limiter: l.limiter,
   131  			logger:  withIncreasedSkip(l.logger, skip),
   132  		}
   133  	case *withLogger:
   134  		return &withLogger{
   135  			tags:   l.tags,
   136  			logger: withIncreasedSkip(l.logger, skip),
   137  		}
   138  	}
   139  	return nil
   140  }
   141  
   142  // convertSlogToZapLevel maps slog Levels to zap Levels.
   143  // Note that there is some room between slog levels while zap levels are continuous, so we can't 1:1 map them.
   144  // See also https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.md?pli=1#levels
   145  func convertSlogToZapLevel(l slog.Level) zapcore.Level {
   146  	switch {
   147  	case l >= slog.LevelError:
   148  		return zapcore.ErrorLevel
   149  	case l >= slog.LevelWarn:
   150  		return zapcore.WarnLevel
   151  	case l >= slog.LevelInfo:
   152  		return zapcore.InfoLevel
   153  	default:
   154  		return zapcore.DebugLevel
   155  	}
   156  }
   157  
   158  // groupObject holds all the Attrs saved in a slog.GroupValue.
   159  type groupObject []slog.Attr
   160  
   161  func (gs groupObject) MarshalLogObject(enc zapcore.ObjectEncoder) error {
   162  	for _, attr := range gs {
   163  		convertAttrToField(attr).AddTo(enc)
   164  	}
   165  	return nil
   166  }
   167  
   168  func convertAttrToField(attr slog.Attr) zapcore.Field {
   169  	if attr.Equal(slog.Attr{}) {
   170  		// Ignore empty attrs.
   171  		return zap.Skip()
   172  	}
   173  
   174  	switch attr.Value.Kind() {
   175  	case slog.KindBool:
   176  		return zap.Bool(attr.Key, attr.Value.Bool())
   177  	case slog.KindDuration:
   178  		return zap.Duration(attr.Key, attr.Value.Duration())
   179  	case slog.KindFloat64:
   180  		return zap.Float64(attr.Key, attr.Value.Float64())
   181  	case slog.KindInt64:
   182  		return zap.Int64(attr.Key, attr.Value.Int64())
   183  	case slog.KindString:
   184  		return zap.String(attr.Key, attr.Value.String())
   185  	case slog.KindTime:
   186  		return zap.Time(attr.Key, attr.Value.Time())
   187  	case slog.KindUint64:
   188  		return zap.Uint64(attr.Key, attr.Value.Uint64())
   189  	case slog.KindGroup:
   190  		return zap.Object(attr.Key, groupObject(attr.Value.Group()))
   191  	case slog.KindLogValuer:
   192  		return convertAttrToField(slog.Attr{
   193  			Key: attr.Key,
   194  			// TODO: resolve the value in a lazy way.
   195  			// This probably needs a new Zap field type
   196  			// that can be resolved lazily.
   197  			Value: attr.Value.Resolve(),
   198  		})
   199  	default:
   200  		return zap.Any(attr.Key, attr.Value.Any())
   201  	}
   202  }