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 }