blob: 10aa6a2b314feb49a1c5aaa0e28ae92fbe795ac2 [file] [log] [blame]
// 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 (
"context"
"log"
loginternal "log/internal"
"log/slog/internal"
"runtime"
"sync/atomic"
"time"
)
var defaultLogger atomic.Pointer[Logger]
var logLoggerLevel LevelVar
// SetLogLoggerLevel controls the level for the bridge to the [log] package.
//
// Before [SetDefault] is called, slog top-level logging functions call the default [log.Logger].
// In that mode, SetLogLoggerLevel sets the minimum level for those calls.
// By default, the minimum level is Info, so calls to [Debug]
// (as well as top-level logging calls at lower levels)
// will not be passed to the log.Logger. After calling
//
// slog.SetLogLoggerLevel(slog.LevelDebug)
//
// calls to [Debug] will be passed to the log.Logger.
//
// After [SetDefault] is called, calls to the default [log.Logger] are passed to the
// slog default handler. In that mode,
// SetLogLoggerLevel sets the level at which those calls are logged.
// That is, after calling
//
// slog.SetLogLoggerLevel(slog.LevelDebug)
//
// A call to [log.Printf] will result in output at level [LevelDebug].
//
// SetLogLoggerLevel returns the previous value.
func SetLogLoggerLevel(level Level) (oldLevel Level) {
oldLevel = logLoggerLevel.Level()
logLoggerLevel.Set(level)
return
}
func init() {
defaultLogger.Store(New(newDefaultHandler(loginternal.DefaultOutput)))
}
// Default returns the default [Logger].
func Default() *Logger { return defaultLogger.Load() }
// SetDefault makes l the default [Logger], which is used by
// the top-level functions [Info], [Debug] and so on.
// After this call, output from the log package's default Logger
// (as with [log.Print], etc.) will be logged using l's Handler,
// at a level controlled by [SetLogLoggerLevel].
func SetDefault(l *Logger) {
defaultLogger.Store(l)
// If the default's handler is a defaultHandler, then don't use a handleWriter,
// or we'll deadlock as they both try to acquire the log default mutex.
// The defaultHandler will use whatever the log default writer is currently
// set to, which is correct.
// This can occur with SetDefault(Default()).
// See TestSetDefault.
if _, ok := l.Handler().(*defaultHandler); !ok {
capturePC := log.Flags()&(log.Lshortfile|log.Llongfile) != 0
log.SetOutput(&handlerWriter{l.Handler(), &logLoggerLevel, capturePC})
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
level Leveler
capturePC bool
}
func (w *handlerWriter) Write(buf []byte) (int, error) {
level := w.level.Level()
if !w.h.Enabled(context.Background(), level) {
return 0, nil
}
var pc uintptr
if !internal.IgnorePC && w.capturePC {
// skip [runtime.Callers, w.Write, Logger.Output, log.Print]
var pcs [1]uintptr
runtime.Callers(4, pcs[:])
pc = pcs[0]
}
// 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 := NewRecord(time.Now(), level, string(buf), pc)
return origLen, w.h.Handle(context.Background(), 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].
//
// To create a new Logger, call [New] or a Logger method
// that begins "With".
type Logger struct {
handler Handler // for structured logging
}
func (l *Logger) clone() *Logger {
c := *l
return &c
}
// Handler returns l's Handler.
func (l *Logger) Handler() Handler { return l.handler }
// With returns a Logger that includes the given attributes
// in each output operation. Arguments are converted to
// attributes as if by [Logger.Log].
func (l *Logger) With(args ...any) *Logger {
if len(args) == 0 {
return l
}
c := l.clone()
c.handler = l.handler.WithAttrs(argsToAttrSlice(args))
return c
}
// WithGroup returns a Logger that starts a group, if name is non-empty.
// The keys of all attributes added to the Logger will be qualified by the given
// name. (How that qualification happens depends on the [Handler.WithGroup]
// method of the Logger's Handler.)
//
// If name is empty, WithGroup returns the receiver.
func (l *Logger) WithGroup(name string) *Logger {
if name == "" {
return l
}
c := l.clone()
c.handler = l.handler.WithGroup(name)
return c
}
// New creates a new Logger with the given non-nil Handler.
func New(h Handler) *Logger {
if h == nil {
panic("nil Handler")
}
return &Logger{handler: h}
}
// With calls [Logger.With] on the default logger.
func With(args ...any) *Logger {
return Default().With(args...)
}
// Enabled reports whether l emits log records at the given context and level.
func (l *Logger) Enabled(ctx context.Context, level Level) bool {
if ctx == nil {
ctx = context.Background()
}
return l.Handler().Enabled(ctx, level)
}
// NewLogLogger returns a new [log.Logger] such that each call to its Output method
// dispatches a Record to the specified handler. The logger acts as a bridge from
// the older log API to newer structured logging handlers.
func NewLogLogger(h Handler, level Level) *log.Logger {
return log.New(&handlerWriter{h, level, true}, "", 0)
}
// 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(ctx context.Context, level Level, msg string, args ...any) {
l.log(ctx, level, msg, args...)
}
// LogAttrs is a more efficient version of [Logger.Log] that accepts only Attrs.
func (l *Logger) LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
l.logAttrs(ctx, level, msg, attrs...)
}
// Debug logs at [LevelDebug].
func (l *Logger) Debug(msg string, args ...any) {
l.log(context.Background(), LevelDebug, msg, args...)
}
// DebugContext logs at [LevelDebug] with the given context.
func (l *Logger) DebugContext(ctx context.Context, msg string, args ...any) {
l.log(ctx, LevelDebug, msg, args...)
}
// Info logs at [LevelInfo].
func (l *Logger) Info(msg string, args ...any) {
l.log(context.Background(), LevelInfo, msg, args...)
}
// InfoContext logs at [LevelInfo] with the given context.
func (l *Logger) InfoContext(ctx context.Context, msg string, args ...any) {
l.log(ctx, LevelInfo, msg, args...)
}
// Warn logs at [LevelWarn].
func (l *Logger) Warn(msg string, args ...any) {
l.log(context.Background(), LevelWarn, msg, args...)
}
// WarnContext logs at [LevelWarn] with the given context.
func (l *Logger) WarnContext(ctx context.Context, msg string, args ...any) {
l.log(ctx, LevelWarn, msg, args...)
}
// Error logs at [LevelError].
func (l *Logger) Error(msg string, args ...any) {
l.log(context.Background(), LevelError, msg, args...)
}
// ErrorContext logs at [LevelError] with the given context.
func (l *Logger) ErrorContext(ctx context.Context, msg string, args ...any) {
l.log(ctx, LevelError, msg, args...)
}
// log is the low-level logging method for methods that take ...any.
// It must always be called directly by an exported logging method
// or function, because it uses a fixed call depth to obtain the pc.
func (l *Logger) log(ctx context.Context, level Level, msg string, args ...any) {
if !l.Enabled(ctx, level) {
return
}
var pc uintptr
if !internal.IgnorePC {
var pcs [1]uintptr
// skip [runtime.Callers, this function, this function's caller]
runtime.Callers(3, pcs[:])
pc = pcs[0]
}
r := NewRecord(time.Now(), level, msg, pc)
r.Add(args...)
if ctx == nil {
ctx = context.Background()
}
_ = l.Handler().Handle(ctx, r)
}
// logAttrs is like [Logger.log], but for methods that take ...Attr.
func (l *Logger) logAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
if !l.Enabled(ctx, level) {
return
}
var pc uintptr
if !internal.IgnorePC {
var pcs [1]uintptr
// skip [runtime.Callers, this function, this function's caller]
runtime.Callers(3, pcs[:])
pc = pcs[0]
}
r := NewRecord(time.Now(), level, msg, pc)
r.AddAttrs(attrs...)
if ctx == nil {
ctx = context.Background()
}
_ = l.Handler().Handle(ctx, r)
}
// Debug calls [Logger.Debug] on the default logger.
func Debug(msg string, args ...any) {
Default().log(context.Background(), LevelDebug, msg, args...)
}
// DebugContext calls [Logger.DebugContext] on the default logger.
func DebugContext(ctx context.Context, msg string, args ...any) {
Default().log(ctx, LevelDebug, msg, args...)
}
// Info calls [Logger.Info] on the default logger.
func Info(msg string, args ...any) {
Default().log(context.Background(), LevelInfo, msg, args...)
}
// InfoContext calls [Logger.InfoContext] on the default logger.
func InfoContext(ctx context.Context, msg string, args ...any) {
Default().log(ctx, LevelInfo, msg, args...)
}
// Warn calls [Logger.Warn] on the default logger.
func Warn(msg string, args ...any) {
Default().log(context.Background(), LevelWarn, msg, args...)
}
// WarnContext calls [Logger.WarnContext] on the default logger.
func WarnContext(ctx context.Context, msg string, args ...any) {
Default().log(ctx, LevelWarn, msg, args...)
}
// Error calls [Logger.Error] on the default logger.
func Error(msg string, args ...any) {
Default().log(context.Background(), LevelError, msg, args...)
}
// ErrorContext calls [Logger.ErrorContext] on the default logger.
func ErrorContext(ctx context.Context, msg string, args ...any) {
Default().log(ctx, LevelError, msg, args...)
}
// Log calls [Logger.Log] on the default logger.
func Log(ctx context.Context, level Level, msg string, args ...any) {
Default().log(ctx, level, msg, args...)
}
// LogAttrs calls [Logger.LogAttrs] on the default logger.
func LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
Default().logAttrs(ctx, level, msg, attrs...)
}