forked from golang/exp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The Logger type and its methods. Change-Id: I2447ec7a3dab5c10c8851adcc2b5ae8e7b8e210d Reviewed-on: https://go-review.googlesource.com/c/exp/+/429436 Reviewed-by: Alan Donovan <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]>
- Loading branch information
Showing
3 changed files
with
582 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
// Copyright 2022 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package slog | ||
|
||
import ( | ||
"log" | ||
"sync/atomic" | ||
"time" | ||
) | ||
|
||
var defaultLogger atomic.Value | ||
|
||
func init() { | ||
defaultLogger.Store(&Logger{ | ||
handler: &defaultHandler{}, | ||
}) | ||
} | ||
|
||
// Default returns the default Logger. | ||
func Default() *Logger { return defaultLogger.Load().(*Logger) } | ||
|
||
// SetDefault makes l the default Logger. | ||
// After this call, output from the log package's default Logger | ||
// (as with [log.Print], etc.) will be logged at InfoLevel using l's Handler. | ||
func SetDefault(l *Logger) { | ||
defaultLogger.Store(l) | ||
log.SetOutput(&handlerWriter{l.Handler(), log.Flags()}) | ||
log.SetFlags(0) // we want just the log message, no time or location | ||
} | ||
|
||
// handlerWriter is an io.Writer that calls a Handler. | ||
// It is used to link the default log.Logger to the default slog.Logger. | ||
type handlerWriter struct { | ||
h Handler | ||
flags int | ||
} | ||
|
||
func (w *handlerWriter) Write(buf []byte) (int, error) { | ||
var depth int | ||
if w.flags&(log.Lshortfile|log.Llongfile) != 0 { | ||
depth = 2 | ||
} | ||
// Remove final newline. | ||
origLen := len(buf) // Report that the entire buf was written. | ||
if len(buf) > 0 && buf[len(buf)-1] == '\n' { | ||
buf = buf[:len(buf)-1] | ||
} | ||
r := MakeRecord(time.Now(), InfoLevel, string(buf), depth) | ||
return origLen, w.h.Handle(r) | ||
} | ||
|
||
// A Logger records structured information about each call to its | ||
// Log, Debug, Info, Warn, and Error methods. | ||
// For each call, it creates a Record and passes it to a Handler. | ||
// | ||
// Loggers are immutable; to create a new one, call [New] or [Logger.With]. | ||
type Logger struct { | ||
handler Handler // for structured logging | ||
} | ||
|
||
// Handler returns l's Handler. | ||
func (l *Logger) Handler() Handler { return l.handler } | ||
|
||
// With returns a new Logger whose handler's attributes are a concatenation of | ||
// l's attributes and the given arguments, converted to Attrs as in | ||
// [Logger.Log]. | ||
func (l *Logger) With(attrs ...any) *Logger { | ||
return &Logger{handler: l.handler.With(argsToAttrs(attrs))} | ||
} | ||
|
||
func argsToAttrs(args []any) []Attr { | ||
var r Record | ||
setAttrs(&r, args) | ||
return r.Attrs() | ||
} | ||
|
||
// New creates a new Logger with the given Handler. | ||
func New(h Handler) *Logger { return &Logger{handler: h} } | ||
|
||
// With calls Logger.With on the default logger. | ||
func With(attrs ...any) *Logger { | ||
return Default().With(attrs...) | ||
} | ||
|
||
// Enabled reports whether l emits log records at the given level. | ||
func (l *Logger) Enabled(level Level) bool { | ||
return l.Handler().Enabled(level) | ||
} | ||
|
||
// Log emits a log record with the current time and the given level and message. | ||
// The Record's Attrs consist of the Logger's attributes followed by | ||
// the Attrs specified by args. | ||
// | ||
// The attribute arguments are processed as follows: | ||
// - If an argument is an Attr, it is used as is. | ||
// - If an argument is a string and this is not the last argument, | ||
// the following argument is treated as the value and the two are combined | ||
// into an Attr. | ||
// - Otherwise, the argument is treated as a value with key "!BADKEY". | ||
func (l *Logger) Log(level Level, msg string, args ...any) { | ||
l.LogDepth(0, level, msg, args...) | ||
} | ||
|
||
// LogDepth is like [Logger.Log], but accepts a call depth to adjust the | ||
// file and line number in the log record. 0 refers to the caller | ||
// of LogDepth; 1 refers to the caller's caller; and so on. | ||
func (l *Logger) LogDepth(calldepth int, level Level, msg string, args ...any) { | ||
if !l.Enabled(level) { | ||
return | ||
} | ||
r := l.makeRecord(msg, level, calldepth) | ||
setAttrs(&r, args) | ||
_ = l.Handler().Handle(r) | ||
} | ||
|
||
var useSourceLine = true | ||
|
||
// Temporary, for benchmarking. | ||
// Eventually, getting the pc should be fast. | ||
func disableSourceLine() { useSourceLine = false } | ||
|
||
func (l *Logger) makeRecord(msg string, level Level, depth int) Record { | ||
if useSourceLine { | ||
depth += 5 | ||
} | ||
return MakeRecord(time.Now(), level, msg, depth) | ||
} | ||
|
||
const badKey = "!BADKEY" | ||
|
||
func setAttrs(r *Record, args []any) { | ||
var attr Attr | ||
for len(args) > 0 { | ||
attr, args = argsToAttr(args) | ||
r.AddAttr(attr) | ||
} | ||
} | ||
|
||
// argsToAttrs turns a prefix of the args slice into an Attr and returns | ||
// the unused portion of the slice. | ||
// If args[0] is an Attr, it returns it. | ||
// If args[0] is a string, it treats the first two elements as | ||
// a key-value pair. | ||
// Otherwise, it treats args[0] as a value with a missing key. | ||
func argsToAttr(args []any) (Attr, []any) { | ||
switch x := args[0].(type) { | ||
case string: | ||
if len(args) == 1 { | ||
return String(badKey, x), nil | ||
} | ||
return Any(x, args[1]), args[2:] | ||
|
||
case Attr: | ||
return x, args[1:] | ||
|
||
default: | ||
return Any(badKey, x), args[1:] | ||
} | ||
} | ||
|
||
// LogAttrs is a more efficient version of [Logger.Log] that accepts only Attrs. | ||
func (l *Logger) LogAttrs(level Level, msg string, attrs ...Attr) { | ||
l.LogAttrsDepth(0, level, msg, attrs...) | ||
} | ||
|
||
// LogAttrsDepth is like [Logger.LogAttrs], but accepts a call depth argument | ||
// which it interprets like [Logger.LogDepth]. | ||
func (l *Logger) LogAttrsDepth(calldepth int, level Level, msg string, attrs ...Attr) { | ||
if !l.Enabled(level) { | ||
return | ||
} | ||
r := l.makeRecord(msg, level, calldepth) | ||
r.addAttrs(attrs) | ||
_ = l.Handler().Handle(r) | ||
} | ||
|
||
// Debug logs at DebugLevel. | ||
func (l *Logger) Debug(msg string, args ...any) { | ||
l.LogDepth(0, DebugLevel, msg, args...) | ||
} | ||
|
||
// Info logs at InfoLevel. | ||
func (l *Logger) Info(msg string, args ...any) { | ||
l.LogDepth(0, InfoLevel, msg, args...) | ||
} | ||
|
||
// Warn logs at WarnLevel. | ||
func (l *Logger) Warn(msg string, args ...any) { | ||
l.LogDepth(0, WarnLevel, msg, args...) | ||
} | ||
|
||
// Error logs at ErrorLevel. | ||
// If err is non-nil, Error appends Any("err", err) | ||
// to the list of attributes. | ||
func (l *Logger) Error(msg string, err error, args ...any) { | ||
if err != nil { | ||
// TODO: avoid the copy. | ||
args = append(args[:len(args):len(args)], Any("err", err)) | ||
} | ||
l.LogDepth(0, ErrorLevel, msg, args...) | ||
} | ||
|
||
// Debug calls Logger.Debug on the default logger. | ||
func Debug(msg string, args ...any) { | ||
Default().LogDepth(0, DebugLevel, msg, args...) | ||
} | ||
|
||
// Info calls Logger.Info on the default logger. | ||
func Info(msg string, args ...any) { | ||
Default().LogDepth(0, InfoLevel, msg, args...) | ||
} | ||
|
||
// Warn calls Logger.Warn on the default logger. | ||
func Warn(msg string, args ...any) { | ||
Default().LogDepth(0, WarnLevel, msg, args...) | ||
} | ||
|
||
// Error calls Logger.Error on the default logger. | ||
func Error(msg string, err error, args ...any) { | ||
if err != nil { | ||
// TODO: avoid the copy. | ||
args = append(args[:len(args):len(args)], Any("err", err)) | ||
} | ||
Default().LogDepth(0, ErrorLevel, msg, args...) | ||
} | ||
|
||
// Log calls Logger.Log on the default logger. | ||
func Log(level Level, msg string, args ...any) { | ||
Default().LogDepth(0, level, msg, args...) | ||
} | ||
|
||
// LogAttrs calls Logger.LogAttrs on the default logger. | ||
func LogAttrs(level Level, msg string, attrs ...Attr) { | ||
Default().LogAttrsDepth(0, level, msg, attrs...) | ||
} |
Oops, something went wrong.