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.

1 // Part of this implementation was taken from https://github.com/uber-go/zap/blob/99f1811d5d2a52264a9c82505a74c2709b077a73/exp/zapslog/handler.go
2
3 package log
4
5 import (
6 "context"
7 "log/slog"
8
9 "go.temporal.io/server/common/log/tag"
10 "go.uber.org/zap"
11 "go.uber.org/zap/zapcore"
12 )
13
14 type handler struct {
15 zapLogger *zap.Logger
16 logger Logger
17 tags []tag.Tag
18 group string
19 }
20
21 var _ slog.Handler = (*handler)(nil)
22
23 // SLogWrapper allows extracting an underlying slog logger.
24 type SLogWrapper interface {
25 SLog() *slog.Logger
26 }
27
28 // NewSlogLogger creates an slog.Logger from a given logger.
29 > func NewSlogLogger(logger Logger) *slog.Logger { slog.go ×4
30 > // Try extracting and underlying slog logger (e.g. for Temporal CLI).
31 > if sl, ok := logger.(SLogWrapper); ok {
32 return sl.SLog()
33 }
34 > logger = withIncreasedSkip(logger, 3) slog.go ×4
35 > return slog.New(&handler{logger: logger, zapLogger: extractZapLogger(logger), group: "", tags: nil})
36 }
37
38 // Enabled reports whether the handler handles records at the given level.
39 > func (h *handler) Enabled(_ context.Context, level slog.Level) bool { slog.go ×16
40 > if h.zapLogger == nil {
41 return true
42 }
43 > return h.zapLogger.Core().Enabled(convertSlogToZapLevel(level)) slog.go ×16
44 }
45
46 // Handle implements slog.Handler.
47 > func (h *handler) Handle(_ context.Context, record slog.Record) error { slog.go ×16
48 > tags := make([]tag.Tag, len(h.tags), len(h.tags)+record.NumAttrs())
49 > copy(tags, h.tags)
50 > record.Attrs(func(attr slog.Attr) bool {
51 > tags = append(tags, tag.Zap(convertAttrToField(h.prependGroup(attr))))
52 > return true
53 > })
54 // Not capturing the log location and stack trace here. We seem to not need this functionality since our zapLogger
55 // adds the logging-call-at tag.
56 > switch record.Level { slog.go ×16
57 case slog.LevelDebug:
58 h.logger.Debug(record.Message, tags...)
59 > case slog.LevelInfo: slog.go ×16
60 > h.logger.Info(record.Message, tags...)
61 case slog.LevelWarn:
62 h.logger.Warn(record.Message, tags...)
63 case slog.LevelError:
64 h.logger.Error(record.Message, tags...)
65 default:
66 }
67 > return nil slog.go ×16
68 }
69
70 // WithAttrs implements slog.Handler.
71 > func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler { slog.go ×16
72 > tags := make([]tag.Tag, len(h.tags), len(h.tags)+len(attrs))
73 > copy(tags, h.tags)
74 > for _, attr := range attrs {
75 > tags = append(tags, tag.Zap(convertAttrToField(h.prependGroup(attr))))
76 > }
77 > return &handler{logger: h.logger, zapLogger: h.zapLogger, tags: tags, group: h.group}
78 }
79
80 // WithGroup implements slog.Handler.
81 > func (h *handler) WithGroup(name string) slog.Handler { slog.go ×16
82 > group := name
83 > if h.group != "" {
84 > group = h.group + "." + name
85 > }
86 > return &handler{logger: h.logger, zapLogger: h.zapLogger, tags: h.tags, group: group}
87 }
88
89 > func (h *handler) prependGroup(attr slog.Attr) slog.Attr { slog.go ×16
90 > if h.group == "" {
91 > return attr
92 > }
93 > return slog.Attr{Key: h.group + "." + attr.Key, Value: attr.Value}
94 }
95
96 > func extractZapLogger(logger Logger) *zap.Logger { slog.go ×4
97 > switch l := logger.(type) {
98 > case *zapLogger: slog.go ×2
99 > return l.zl
100 > case *throttledLogger: slog.go ×16
101 > return extractZapLogger(l.logger)
102 case *withLogger:
103 return extractZapLogger(l.logger)
104 }
105 > return nil fx.go ×44
106 }
107
108 // withIncreasedSkip increases the skip level for the given logger if it embeds a zapLogger.
109 > func withIncreasedSkip(logger Logger, skip int) Logger { slog.go ×4
110 > switch l := logger.(type) {
111 > case *zapLogger: slog.go ×2
112 > return l.Skip(skip)
113 > case *throttledLogger: slog.go ×16
114 > return &throttledLogger{
115 > limiter: l.limiter,
116 > logger: withIncreasedSkip(l.logger, skip),
117 > }
118 case *withLogger:
119 return &withLogger{
120 tags: l.tags,
121 logger: withIncreasedSkip(l.logger, skip),
122 }
123 }
124 // Default to not increasing the skip, it's better to have a logger than not having one.
125 > return logger fx.go ×44
126 }
127
128 // convertSlogToZapLevel maps slog Levels to zap Levels.
129 // Note that there is some room between slog levels while zap levels are continuous, so we can't 1:1 map them.
130 // See also https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.md?pli=1#levels
131 > func convertSlogToZapLevel(l slog.Level) zapcore.Level { slog.go ×16
132 > switch {
133 case l >= slog.LevelError:
134 return zapcore.ErrorLevel
135 case l >= slog.LevelWarn:
136 return zapcore.WarnLevel
137 > case l >= slog.LevelInfo: slog.go ×16
138 > return zapcore.InfoLevel
139 > default:
140 > return zapcore.DebugLevel
141 }
142 }
143
144 // groupObject holds all the Attrs saved in a slog.GroupValue.
145 type groupObject []slog.Attr
146
147 func (gs groupObject) MarshalLogObject(enc zapcore.ObjectEncoder) error {
148 for _, attr := range gs {
149 convertAttrToField(attr).AddTo(enc)
150 }
151 return nil
152 }
153
154 > func convertAttrToField(attr slog.Attr) zapcore.Field { slog.go ×16
155 > if attr.Equal(slog.Attr{}) {
156 // Ignore empty attrs.
157 return zap.Skip()
158 }
159
160 > switch attr.Value.Kind() { slog.go ×16
161 case slog.KindBool:
162 return zap.Bool(attr.Key, attr.Value.Bool())
163 case slog.KindDuration:
164 return zap.Duration(attr.Key, attr.Value.Duration())
165 case slog.KindFloat64:
166 return zap.Float64(attr.Key, attr.Value.Float64())
167 > case slog.KindInt64: slog.go ×16
168 > return zap.Int64(attr.Key, attr.Value.Int64())
169 > case slog.KindString:
170 > return zap.String(attr.Key, attr.Value.String())
171 case slog.KindTime:
172 return zap.Time(attr.Key, attr.Value.Time())
173 case slog.KindUint64:
174 return zap.Uint64(attr.Key, attr.Value.Uint64())
175 case slog.KindGroup:
176 return zap.Object(attr.Key, groupObject(attr.Value.Group()))
177 case slog.KindLogValuer:
178 return convertAttrToField(slog.Attr{
179 Key: attr.Key,
180 // TODO: resolve the value in a lazy way.
181 // This probably needs a new Zap field type
182 // that can be resolved lazily.
183 Value: attr.Value.Resolve(),
184 })
185 default:
186 return zap.Any(attr.Key, attr.Value.Any())
187 }
188 }