go.temporal.io/server/common/log/slog.go
188 LOC · 67 covered · 121 uncovered · 24 ranges · 74 concepts · 4 introducers · 16 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
// Part of this implementation was taken from https://github.com/uber-go/zap/blob/99f1811d5d2a52264a9c82505a74c2709b077a73/exp/zapslog/handler.go
package log
import (
"context"
"log/slog"
"go.temporal.io/server/common/log/tag"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type handler struct {
zapLogger *zap.Logger
logger Logger
tags []tag.Tag
group string
}
var _ slog.Handler = (*handler)(nil)
// SLogWrapper allows extracting an underlying slog logger.
type SLogWrapper interface {
SLog() *slog.Logger
}
// NewSlogLogger creates an slog.Logger from a given logger.
// Try extracting and underlying slog logger (e.g. for Temporal CLI).
if sl, ok := logger.(SLogWrapper); ok {
return sl.SLog()
}
return slog.New(&handler{logger: logger, zapLogger: extractZapLogger(logger), group: "", tags: nil})
}
// Enabled reports whether the handler handles records at the given level.
if h.zapLogger == nil {
return true
}
}
// Handle implements slog.Handler.
tags := make([]tag.Tag, len(h.tags), len(h.tags)+record.NumAttrs())
copy(tags, h.tags)
record.Attrs(func(attr slog.Attr) bool {
tags = append(tags, tag.Zap(convertAttrToField(h.prependGroup(attr))))
return true
})
// Not capturing the log location and stack trace here. We seem to not need this functionality since our zapLogger
// adds the logging-call-at tag.
case slog.LevelDebug:
h.logger.Debug(record.Message, tags...)
h.logger.Info(record.Message, tags...)
case slog.LevelWarn:
h.logger.Warn(record.Message, tags...)
case slog.LevelError:
h.logger.Error(record.Message, tags...)
default:
}
}
// WithAttrs implements slog.Handler.
tags := make([]tag.Tag, len(h.tags), len(h.tags)+len(attrs))
copy(tags, h.tags)
for _, attr := range attrs {
tags = append(tags, tag.Zap(convertAttrToField(h.prependGroup(attr))))
}
return &handler{logger: h.logger, zapLogger: h.zapLogger, tags: tags, group: h.group}
}
// WithGroup implements slog.Handler.
group := name
if h.group != "" {
group = h.group + "." + name
}
return &handler{logger: h.logger, zapLogger: h.zapLogger, tags: h.tags, group: group}
}
if h.group == "" {
return attr
}
return slog.Attr{Key: h.group + "." + attr.Key, Value: attr.Value}
}
switch l := logger.(type) {
return l.zl
return extractZapLogger(l.logger)
case *withLogger:
return extractZapLogger(l.logger)
}
}
// withIncreasedSkip increases the skip level for the given logger if it embeds a zapLogger.
switch l := logger.(type) {
return l.Skip(skip)
return &throttledLogger{
limiter: l.limiter,
logger: withIncreasedSkip(l.logger, skip),
}
case *withLogger:
return &withLogger{
tags: l.tags,
logger: withIncreasedSkip(l.logger, skip),
}
}
// Default to not increasing the skip, it's better to have a logger than not having one.
}
// convertSlogToZapLevel maps slog Levels to zap Levels.
// Note that there is some room between slog levels while zap levels are continuous, so we can't 1:1 map them.
// See also https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.md?pli=1#levels
switch {
case l >= slog.LevelError:
return zapcore.ErrorLevel
case l >= slog.LevelWarn:
return zapcore.WarnLevel
return zapcore.InfoLevel
default:
return zapcore.DebugLevel
}
}
// groupObject holds all the Attrs saved in a slog.GroupValue.
type groupObject []slog.Attr
func (gs groupObject) MarshalLogObject(enc zapcore.ObjectEncoder) error {
for _, attr := range gs {
convertAttrToField(attr).AddTo(enc)
}
return nil
}
if attr.Equal(slog.Attr{}) {
// Ignore empty attrs.
return zap.Skip()
}
case slog.KindBool:
return zap.Bool(attr.Key, attr.Value.Bool())
case slog.KindDuration:
return zap.Duration(attr.Key, attr.Value.Duration())
case slog.KindFloat64:
return zap.Float64(attr.Key, attr.Value.Float64())
return zap.Int64(attr.Key, attr.Value.Int64())
case slog.KindString:
return zap.String(attr.Key, attr.Value.String())
case slog.KindTime:
return zap.Time(attr.Key, attr.Value.Time())
case slog.KindUint64:
return zap.Uint64(attr.Key, attr.Value.Uint64())
case slog.KindGroup:
return zap.Object(attr.Key, groupObject(attr.Value.Group()))
case slog.KindLogValuer:
return convertAttrToField(slog.Attr{
Key: attr.Key,
// TODO: resolve the value in a lazy way.
// This probably needs a new Zap field type
// that can be resolved lazily.
Value: attr.Value.Resolve(),
})
default:
return zap.Any(attr.Key, attr.Value.Any())
}
}