1// Copyright 2022 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package slog 6 7import ( 8 "context" 9 "fmt" 10 "io" 11 "log/slog/internal/buffer" 12 "reflect" 13 "slices" 14 "strconv" 15 "sync" 16 "time" 17) 18 19// A Handler handles log records produced by a Logger. 20// 21// A typical handler may print log records to standard error, 22// or write them to a file or database, or perhaps augment them 23// with additional attributes and pass them on to another handler. 24// 25// Any of the Handler's methods may be called concurrently with itself 26// or with other methods. It is the responsibility of the Handler to 27// manage this concurrency. 28// 29// Users of the slog package should not invoke Handler methods directly. 30// They should use the methods of [Logger] instead. 31type Handler interface { 32 // Enabled reports whether the handler handles records at the given level. 33 // The handler ignores records whose level is lower. 34 // It is called early, before any arguments are processed, 35 // to save effort if the log event should be discarded. 36 // If called from a Logger method, the first argument is the context 37 // passed to that method, or context.Background() if nil was passed 38 // or the method does not take a context. 39 // The context is passed so Enabled can use its values 40 // to make a decision. 41 Enabled(context.Context, Level) bool 42 43 // Handle handles the Record. 44 // It will only be called when Enabled returns true. 45 // The Context argument is as for Enabled. 46 // It is present solely to provide Handlers access to the context's values. 47 // Canceling the context should not affect record processing. 48 // (Among other things, log messages may be necessary to debug a 49 // cancellation-related problem.) 50 // 51 // Handle methods that produce output should observe the following rules: 52 // - If r.Time is the zero time, ignore the time. 53 // - If r.PC is zero, ignore it. 54 // - Attr's values should be resolved. 55 // - If an Attr's key and value are both the zero value, ignore the Attr. 56 // This can be tested with attr.Equal(Attr{}). 57 // - If a group's key is empty, inline the group's Attrs. 58 // - If a group has no Attrs (even if it has a non-empty key), 59 // ignore it. 60 Handle(context.Context, Record) error 61 62 // WithAttrs returns a new Handler whose attributes consist of 63 // both the receiver's attributes and the arguments. 64 // The Handler owns the slice: it may retain, modify or discard it. 65 WithAttrs(attrs []Attr) Handler 66 67 // WithGroup returns a new Handler with the given group appended to 68 // the receiver's existing groups. 69 // The keys of all subsequent attributes, whether added by With or in a 70 // Record, should be qualified by the sequence of group names. 71 // 72 // How this qualification happens is up to the Handler, so long as 73 // this Handler's attribute keys differ from those of another Handler 74 // with a different sequence of group names. 75 // 76 // A Handler should treat WithGroup as starting a Group of Attrs that ends 77 // at the end of the log event. That is, 78 // 79 // logger.WithGroup("s").LogAttrs(ctx, level, msg, slog.Int("a", 1), slog.Int("b", 2)) 80 // 81 // should behave like 82 // 83 // logger.LogAttrs(ctx, level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2))) 84 // 85 // If the name is empty, WithGroup returns the receiver. 86 WithGroup(name string) Handler 87} 88 89type defaultHandler struct { 90 ch *commonHandler 91 // internal.DefaultOutput, except for testing 92 output func(pc uintptr, data []byte) error 93} 94 95func newDefaultHandler(output func(uintptr, []byte) error) *defaultHandler { 96 return &defaultHandler{ 97 ch: &commonHandler{json: false}, 98 output: output, 99 } 100} 101 102func (*defaultHandler) Enabled(_ context.Context, l Level) bool { 103 return l >= logLoggerLevel.Level() 104} 105 106// Collect the level, attributes and message in a string and 107// write it with the default log.Logger. 108// Let the log.Logger handle time and file/line. 109func (h *defaultHandler) Handle(ctx context.Context, r Record) error { 110 buf := buffer.New() 111 buf.WriteString(r.Level.String()) 112 buf.WriteByte(' ') 113 buf.WriteString(r.Message) 114 state := h.ch.newHandleState(buf, true, " ") 115 defer state.free() 116 state.appendNonBuiltIns(r) 117 return h.output(r.PC, *buf) 118} 119 120func (h *defaultHandler) WithAttrs(as []Attr) Handler { 121 return &defaultHandler{h.ch.withAttrs(as), h.output} 122} 123 124func (h *defaultHandler) WithGroup(name string) Handler { 125 return &defaultHandler{h.ch.withGroup(name), h.output} 126} 127 128// HandlerOptions are options for a [TextHandler] or [JSONHandler]. 129// A zero HandlerOptions consists entirely of default values. 130type HandlerOptions struct { 131 // AddSource causes the handler to compute the source code position 132 // of the log statement and add a SourceKey attribute to the output. 133 AddSource bool 134 135 // Level reports the minimum record level that will be logged. 136 // The handler discards records with lower levels. 137 // If Level is nil, the handler assumes LevelInfo. 138 // The handler calls Level.Level for each record processed; 139 // to adjust the minimum level dynamically, use a LevelVar. 140 Level Leveler 141 142 // ReplaceAttr is called to rewrite each non-group attribute before it is logged. 143 // The attribute's value has been resolved (see [Value.Resolve]). 144 // If ReplaceAttr returns a zero Attr, the attribute is discarded. 145 // 146 // The built-in attributes with keys "time", "level", "source", and "msg" 147 // are passed to this function, except that time is omitted 148 // if zero, and source is omitted if AddSource is false. 149 // 150 // The first argument is a list of currently open groups that contain the 151 // Attr. It must not be retained or modified. ReplaceAttr is never called 152 // for Group attributes, only their contents. For example, the attribute 153 // list 154 // 155 // Int("a", 1), Group("g", Int("b", 2)), Int("c", 3) 156 // 157 // results in consecutive calls to ReplaceAttr with the following arguments: 158 // 159 // nil, Int("a", 1) 160 // []string{"g"}, Int("b", 2) 161 // nil, Int("c", 3) 162 // 163 // ReplaceAttr can be used to change the default keys of the built-in 164 // attributes, convert types (for example, to replace a `time.Time` with the 165 // integer seconds since the Unix epoch), sanitize personal information, or 166 // remove attributes from the output. 167 ReplaceAttr func(groups []string, a Attr) Attr 168} 169 170// Keys for "built-in" attributes. 171const ( 172 // TimeKey is the key used by the built-in handlers for the time 173 // when the log method is called. The associated Value is a [time.Time]. 174 TimeKey = "time" 175 // LevelKey is the key used by the built-in handlers for the level 176 // of the log call. The associated value is a [Level]. 177 LevelKey = "level" 178 // MessageKey is the key used by the built-in handlers for the 179 // message of the log call. The associated value is a string. 180 MessageKey = "msg" 181 // SourceKey is the key used by the built-in handlers for the source file 182 // and line of the log call. The associated value is a *[Source]. 183 SourceKey = "source" 184) 185 186type commonHandler struct { 187 json bool // true => output JSON; false => output text 188 opts HandlerOptions 189 preformattedAttrs []byte 190 // groupPrefix is for the text handler only. 191 // It holds the prefix for groups that were already pre-formatted. 192 // A group will appear here when a call to WithGroup is followed by 193 // a call to WithAttrs. 194 groupPrefix string 195 groups []string // all groups started from WithGroup 196 nOpenGroups int // the number of groups opened in preformattedAttrs 197 mu *sync.Mutex 198 w io.Writer 199} 200 201func (h *commonHandler) clone() *commonHandler { 202 // We can't use assignment because we can't copy the mutex. 203 return &commonHandler{ 204 json: h.json, 205 opts: h.opts, 206 preformattedAttrs: slices.Clip(h.preformattedAttrs), 207 groupPrefix: h.groupPrefix, 208 groups: slices.Clip(h.groups), 209 nOpenGroups: h.nOpenGroups, 210 w: h.w, 211 mu: h.mu, // mutex shared among all clones of this handler 212 } 213} 214 215// enabled reports whether l is greater than or equal to the 216// minimum level. 217func (h *commonHandler) enabled(l Level) bool { 218 minLevel := LevelInfo 219 if h.opts.Level != nil { 220 minLevel = h.opts.Level.Level() 221 } 222 return l >= minLevel 223} 224 225func (h *commonHandler) withAttrs(as []Attr) *commonHandler { 226 // We are going to ignore empty groups, so if the entire slice consists of 227 // them, there is nothing to do. 228 if countEmptyGroups(as) == len(as) { 229 return h 230 } 231 h2 := h.clone() 232 // Pre-format the attributes as an optimization. 233 state := h2.newHandleState((*buffer.Buffer)(&h2.preformattedAttrs), false, "") 234 defer state.free() 235 state.prefix.WriteString(h.groupPrefix) 236 if pfa := h2.preformattedAttrs; len(pfa) > 0 { 237 state.sep = h.attrSep() 238 if h2.json && pfa[len(pfa)-1] == '{' { 239 state.sep = "" 240 } 241 } 242 // Remember the position in the buffer, in case all attrs are empty. 243 pos := state.buf.Len() 244 state.openGroups() 245 if !state.appendAttrs(as) { 246 state.buf.SetLen(pos) 247 } else { 248 // Remember the new prefix for later keys. 249 h2.groupPrefix = state.prefix.String() 250 // Remember how many opened groups are in preformattedAttrs, 251 // so we don't open them again when we handle a Record. 252 h2.nOpenGroups = len(h2.groups) 253 } 254 return h2 255} 256 257func (h *commonHandler) withGroup(name string) *commonHandler { 258 h2 := h.clone() 259 h2.groups = append(h2.groups, name) 260 return h2 261} 262 263// handle is the internal implementation of Handler.Handle 264// used by TextHandler and JSONHandler. 265func (h *commonHandler) handle(r Record) error { 266 state := h.newHandleState(buffer.New(), true, "") 267 defer state.free() 268 if h.json { 269 state.buf.WriteByte('{') 270 } 271 // Built-in attributes. They are not in a group. 272 stateGroups := state.groups 273 state.groups = nil // So ReplaceAttrs sees no groups instead of the pre groups. 274 rep := h.opts.ReplaceAttr 275 // time 276 if !r.Time.IsZero() { 277 key := TimeKey 278 val := r.Time.Round(0) // strip monotonic to match Attr behavior 279 if rep == nil { 280 state.appendKey(key) 281 state.appendTime(val) 282 } else { 283 state.appendAttr(Time(key, val)) 284 } 285 } 286 // level 287 key := LevelKey 288 val := r.Level 289 if rep == nil { 290 state.appendKey(key) 291 state.appendString(val.String()) 292 } else { 293 state.appendAttr(Any(key, val)) 294 } 295 // source 296 if h.opts.AddSource { 297 state.appendAttr(Any(SourceKey, r.source())) 298 } 299 key = MessageKey 300 msg := r.Message 301 if rep == nil { 302 state.appendKey(key) 303 state.appendString(msg) 304 } else { 305 state.appendAttr(String(key, msg)) 306 } 307 state.groups = stateGroups // Restore groups passed to ReplaceAttrs. 308 state.appendNonBuiltIns(r) 309 state.buf.WriteByte('\n') 310 311 h.mu.Lock() 312 defer h.mu.Unlock() 313 _, err := h.w.Write(*state.buf) 314 return err 315} 316 317func (s *handleState) appendNonBuiltIns(r Record) { 318 // preformatted Attrs 319 if pfa := s.h.preformattedAttrs; len(pfa) > 0 { 320 s.buf.WriteString(s.sep) 321 s.buf.Write(pfa) 322 s.sep = s.h.attrSep() 323 if s.h.json && pfa[len(pfa)-1] == '{' { 324 s.sep = "" 325 } 326 } 327 // Attrs in Record -- unlike the built-in ones, they are in groups started 328 // from WithGroup. 329 // If the record has no Attrs, don't output any groups. 330 nOpenGroups := s.h.nOpenGroups 331 if r.NumAttrs() > 0 { 332 s.prefix.WriteString(s.h.groupPrefix) 333 // The group may turn out to be empty even though it has attrs (for 334 // example, ReplaceAttr may delete all the attrs). 335 // So remember where we are in the buffer, to restore the position 336 // later if necessary. 337 pos := s.buf.Len() 338 s.openGroups() 339 nOpenGroups = len(s.h.groups) 340 empty := true 341 r.Attrs(func(a Attr) bool { 342 if s.appendAttr(a) { 343 empty = false 344 } 345 return true 346 }) 347 if empty { 348 s.buf.SetLen(pos) 349 nOpenGroups = s.h.nOpenGroups 350 } 351 } 352 if s.h.json { 353 // Close all open groups. 354 for range s.h.groups[:nOpenGroups] { 355 s.buf.WriteByte('}') 356 } 357 // Close the top-level object. 358 s.buf.WriteByte('}') 359 } 360} 361 362// attrSep returns the separator between attributes. 363func (h *commonHandler) attrSep() string { 364 if h.json { 365 return "," 366 } 367 return " " 368} 369 370// handleState holds state for a single call to commonHandler.handle. 371// The initial value of sep determines whether to emit a separator 372// before the next key, after which it stays true. 373type handleState struct { 374 h *commonHandler 375 buf *buffer.Buffer 376 freeBuf bool // should buf be freed? 377 sep string // separator to write before next key 378 prefix *buffer.Buffer // for text: key prefix 379 groups *[]string // pool-allocated slice of active groups, for ReplaceAttr 380} 381 382var groupPool = sync.Pool{New: func() any { 383 s := make([]string, 0, 10) 384 return &s 385}} 386 387func (h *commonHandler) newHandleState(buf *buffer.Buffer, freeBuf bool, sep string) handleState { 388 s := handleState{ 389 h: h, 390 buf: buf, 391 freeBuf: freeBuf, 392 sep: sep, 393 prefix: buffer.New(), 394 } 395 if h.opts.ReplaceAttr != nil { 396 s.groups = groupPool.Get().(*[]string) 397 *s.groups = append(*s.groups, h.groups[:h.nOpenGroups]...) 398 } 399 return s 400} 401 402func (s *handleState) free() { 403 if s.freeBuf { 404 s.buf.Free() 405 } 406 if gs := s.groups; gs != nil { 407 *gs = (*gs)[:0] 408 groupPool.Put(gs) 409 } 410 s.prefix.Free() 411} 412 413func (s *handleState) openGroups() { 414 for _, n := range s.h.groups[s.h.nOpenGroups:] { 415 s.openGroup(n) 416 } 417} 418 419// Separator for group names and keys. 420const keyComponentSep = '.' 421 422// openGroup starts a new group of attributes 423// with the given name. 424func (s *handleState) openGroup(name string) { 425 if s.h.json { 426 s.appendKey(name) 427 s.buf.WriteByte('{') 428 s.sep = "" 429 } else { 430 s.prefix.WriteString(name) 431 s.prefix.WriteByte(keyComponentSep) 432 } 433 // Collect group names for ReplaceAttr. 434 if s.groups != nil { 435 *s.groups = append(*s.groups, name) 436 } 437} 438 439// closeGroup ends the group with the given name. 440func (s *handleState) closeGroup(name string) { 441 if s.h.json { 442 s.buf.WriteByte('}') 443 } else { 444 (*s.prefix) = (*s.prefix)[:len(*s.prefix)-len(name)-1 /* for keyComponentSep */] 445 } 446 s.sep = s.h.attrSep() 447 if s.groups != nil { 448 *s.groups = (*s.groups)[:len(*s.groups)-1] 449 } 450} 451 452// appendAttrs appends the slice of Attrs. 453// It reports whether something was appended. 454func (s *handleState) appendAttrs(as []Attr) bool { 455 nonEmpty := false 456 for _, a := range as { 457 if s.appendAttr(a) { 458 nonEmpty = true 459 } 460 } 461 return nonEmpty 462} 463 464// appendAttr appends the Attr's key and value. 465// It handles replacement and checking for an empty key. 466// It reports whether something was appended. 467func (s *handleState) appendAttr(a Attr) bool { 468 a.Value = a.Value.Resolve() 469 if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != KindGroup { 470 var gs []string 471 if s.groups != nil { 472 gs = *s.groups 473 } 474 // a.Value is resolved before calling ReplaceAttr, so the user doesn't have to. 475 a = rep(gs, a) 476 // The ReplaceAttr function may return an unresolved Attr. 477 a.Value = a.Value.Resolve() 478 } 479 // Elide empty Attrs. 480 if a.isEmpty() { 481 return false 482 } 483 // Special case: Source. 484 if v := a.Value; v.Kind() == KindAny { 485 if src, ok := v.Any().(*Source); ok { 486 if s.h.json { 487 a.Value = src.group() 488 } else { 489 a.Value = StringValue(fmt.Sprintf("%s:%d", src.File, src.Line)) 490 } 491 } 492 } 493 if a.Value.Kind() == KindGroup { 494 attrs := a.Value.Group() 495 // Output only non-empty groups. 496 if len(attrs) > 0 { 497 // The group may turn out to be empty even though it has attrs (for 498 // example, ReplaceAttr may delete all the attrs). 499 // So remember where we are in the buffer, to restore the position 500 // later if necessary. 501 pos := s.buf.Len() 502 // Inline a group with an empty key. 503 if a.Key != "" { 504 s.openGroup(a.Key) 505 } 506 if !s.appendAttrs(attrs) { 507 s.buf.SetLen(pos) 508 return false 509 } 510 if a.Key != "" { 511 s.closeGroup(a.Key) 512 } 513 } 514 } else { 515 s.appendKey(a.Key) 516 s.appendValue(a.Value) 517 } 518 return true 519} 520 521func (s *handleState) appendError(err error) { 522 s.appendString(fmt.Sprintf("!ERROR:%v", err)) 523} 524 525func (s *handleState) appendKey(key string) { 526 s.buf.WriteString(s.sep) 527 if s.prefix != nil && len(*s.prefix) > 0 { 528 // TODO: optimize by avoiding allocation. 529 s.appendString(string(*s.prefix) + key) 530 } else { 531 s.appendString(key) 532 } 533 if s.h.json { 534 s.buf.WriteByte(':') 535 } else { 536 s.buf.WriteByte('=') 537 } 538 s.sep = s.h.attrSep() 539} 540 541func (s *handleState) appendString(str string) { 542 if s.h.json { 543 s.buf.WriteByte('"') 544 *s.buf = appendEscapedJSONString(*s.buf, str) 545 s.buf.WriteByte('"') 546 } else { 547 // text 548 if needsQuoting(str) { 549 *s.buf = strconv.AppendQuote(*s.buf, str) 550 } else { 551 s.buf.WriteString(str) 552 } 553 } 554} 555 556func (s *handleState) appendValue(v Value) { 557 defer func() { 558 if r := recover(); r != nil { 559 // If it panics with a nil pointer, the most likely cases are 560 // an encoding.TextMarshaler or error fails to guard against nil, 561 // in which case "<nil>" seems to be the feasible choice. 562 // 563 // Adapted from the code in fmt/print.go. 564 if v := reflect.ValueOf(v.any); v.Kind() == reflect.Pointer && v.IsNil() { 565 s.appendString("<nil>") 566 return 567 } 568 569 // Otherwise just print the original panic message. 570 s.appendString(fmt.Sprintf("!PANIC: %v", r)) 571 } 572 }() 573 574 var err error 575 if s.h.json { 576 err = appendJSONValue(s, v) 577 } else { 578 err = appendTextValue(s, v) 579 } 580 if err != nil { 581 s.appendError(err) 582 } 583} 584 585func (s *handleState) appendTime(t time.Time) { 586 if s.h.json { 587 appendJSONTime(s, t) 588 } else { 589 *s.buf = appendRFC3339Millis(*s.buf, t) 590 } 591} 592 593func appendRFC3339Millis(b []byte, t time.Time) []byte { 594 // Format according to time.RFC3339Nano since it is highly optimized, 595 // but truncate it to use millisecond resolution. 596 // Unfortunately, that format trims trailing 0s, so add 1/10 millisecond 597 // to guarantee that there are exactly 4 digits after the period. 598 const prefixLen = len("2006-01-02T15:04:05.000") 599 n := len(b) 600 t = t.Truncate(time.Millisecond).Add(time.Millisecond / 10) 601 b = t.AppendFormat(b, time.RFC3339Nano) 602 b = append(b[:n+prefixLen], b[n+prefixLen+1:]...) // drop the 4th digit 603 return b 604} 605