Skip to content

Commit

Permalink
slog: add context arguments
Browse files Browse the repository at this point in the history
Implement a more direct way to pass a context to a Handler.

- Add {Info,Debug,Warn,Error}Ctx functions and methods, that take a context.

- Add an initial context argument to Log and LogAttrs.

- Remove Logger.WithContext, Logger.Context and the Record.Context
  field: the output functions will pass their context argument
  directly to the Handler.

This CL also includes a somewhat unrelated set of changes:

- Add the Record.Add(...any) method, which behaves like Record.AddAttrs
  but processes its arguments like Logger.Log.

- Remove LogDepth and LogAttrsDepth, since they can now be implemented
  with exported API.

A later CL will update the package documentation.

Change-Id: I9216615598e1582a28fd48d78e85ff019d223924
Reviewed-on: https://go-review.googlesource.com/c/exp/+/469856
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
  • Loading branch information
jba committed Feb 24, 2023
1 parent 50820d9 commit 806bf56
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 122 deletions.
8 changes: 4 additions & 4 deletions slog/benchmarks/benchmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func BenchmarkAttrs(b *testing.B) {
// should only be from Duration.String and Time.String.
"5 args",
func() {
logger.LogAttrs(slog.LevelInfo, TestMessage,
logger.LogAttrs(nil, slog.LevelInfo, TestMessage,
slog.String("string", TestString),
slog.Int("status", TestInt),
slog.Duration("duration", TestDuration),
Expand All @@ -53,7 +53,7 @@ func BenchmarkAttrs(b *testing.B) {
{
"5 args ctx",
func() {
logger.WithContext(ctx).LogAttrs(slog.LevelInfo, TestMessage,
logger.LogAttrs(ctx, slog.LevelInfo, TestMessage,
slog.String("string", TestString),
slog.Int("status", TestInt),
slog.Duration("duration", TestDuration),
Expand All @@ -65,7 +65,7 @@ func BenchmarkAttrs(b *testing.B) {
{
"10 args",
func() {
logger.LogAttrs(slog.LevelInfo, TestMessage,
logger.LogAttrs(nil, slog.LevelInfo, TestMessage,
slog.String("string", TestString),
slog.Int("status", TestInt),
slog.Duration("duration", TestDuration),
Expand All @@ -82,7 +82,7 @@ func BenchmarkAttrs(b *testing.B) {
{
"40 args",
func() {
logger.LogAttrs(slog.LevelInfo, TestMessage,
logger.LogAttrs(nil, slog.LevelInfo, TestMessage,
slog.String("string", TestString),
slog.Int("status", TestInt),
slog.Duration("duration", TestDuration),
Expand Down
6 changes: 3 additions & 3 deletions slog/example_custom_levels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ func ExampleHandlerOptions_customLevels() {
}.NewTextHandler(os.Stdout)

logger := slog.New(th)
logger.Log(LevelEmergency, "missing pilots")
logger.Log(nil, LevelEmergency, "missing pilots")
logger.Error("failed to start engines", fmt.Errorf("missing fuel"))
logger.Warn("falling back to default value")
logger.Log(LevelNotice, "all systems are running")
logger.Log(nil, LevelNotice, "all systems are running")
logger.Info("initiating launch")
logger.Debug("starting background job")
logger.Log(LevelTrace, "button clicked")
logger.Log(nil, LevelTrace, "button clicked")

// Output:
// sev=EMERGENCY msg="missing pilots"
Expand Down
19 changes: 14 additions & 5 deletions slog/example_depth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,26 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"

"golang.org/x/exp/slog"
)

// Infof is an example of a user-defined logging function that wraps slog.
// The log record contains the source position of the caller of Infof.
func Infof(format string, args ...any) {
// Use LogDepth to adjust source line information to point to the caller of Infof.
// The 1 passed to LogDepth refers to the caller of LogDepth, namely this function.
slog.Default().LogDepth(1, slog.LevelInfo, fmt.Sprintf(format, args...))
l := slog.Default()
if !l.Enabled(nil, slog.LevelInfo) {
return
}
var pcs [1]uintptr
runtime.Callers(2, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf(format, args...), pcs[0])
_ = l.Handler().Handle(nil, r)
}

func ExampleLogger_LogDepth() {
func Example_wrapping() {
defer func(l *slog.Logger) { slog.SetDefault(l) }(slog.Default())

replace := func(groups []string, a slog.Attr) slog.Attr {
Expand All @@ -37,5 +46,5 @@ func ExampleLogger_LogDepth() {
Infof("message, %s", "formatted")

// Output:
// level=INFO source=example_depth_test.go:37 msg="message, formatted"
// level=INFO source=example_depth_test.go:46 msg="message, formatted"
}
4 changes: 2 additions & 2 deletions slog/json_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func BenchmarkJSONHandler(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
l.LogAttrs(LevelInfo, "this is a typical log message",
l.LogAttrs(nil, LevelInfo, "this is a typical log message",
String("module", "github.com/google/go-cmp"),
String("version", "v1.23.4"),
Int("count", 23),
Expand Down Expand Up @@ -232,7 +232,7 @@ func BenchmarkPreformatting(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
l.LogAttrs(LevelInfo, "this is a typical log message",
l.LogAttrs(nil, LevelInfo, "this is a typical log message",
String("module", "github.com/google/go-cmp"),
String("version", "v1.23.4"),
Int("count", 23),
Expand Down
99 changes: 64 additions & 35 deletions slog/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ func (w *handlerWriter) Write(buf []byte) (int, error) {
// that begins "With".
type Logger struct {
handler Handler // for structured logging
ctx context.Context
}

func (l *Logger) clone() *Logger {
Expand All @@ -84,9 +83,6 @@ func (l *Logger) clone() *Logger {
// Handler returns l's Handler.
func (l *Logger) Handler() Handler { return l.handler }

// Context returns l's context, which may be nil.
func (l *Logger) Context() context.Context { return l.ctx }

// With returns a new Logger that includes the given arguments, converted to
// Attrs as in [Logger.Log] and resolved.
// The Attrs will be added to each output from the Logger.
Expand Down Expand Up @@ -120,15 +116,6 @@ func (l *Logger) WithGroup(name string) *Logger {

}

// WithContext returns a new Logger with the same handler
// as the receiver and the given context.
// It uses the same handler as the original.
func (l *Logger) WithContext(ctx context.Context) *Logger {
c := l.clone()
c.ctx = ctx
return c
}

// New creates a new Logger with the given non-nil Handler and a nil context.
func New(h Handler) *Logger {
if h == nil {
Expand All @@ -142,9 +129,9 @@ func With(args ...any) *Logger {
return Default().With(args...)
}

// Enabled reports whether l emits log records at the given level.
func (l *Logger) Enabled(level Level) bool {
return l.Handler().Enabled(l.ctx, level)
// Enabled reports whether l emits log records at the given context and level.
func (l *Logger) Enabled(ctx context.Context, level Level) bool {
return l.Handler().Enabled(ctx, level)
}

// NewLogLogger returns a new log.Logger such that each call to its Output method
Expand All @@ -164,73 +151,115 @@ func NewLogLogger(h Handler, level Level) *log.Logger {
// 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(1, level, msg, args...)
func (l *Logger) Log(ctx context.Context, level Level, msg string, args ...any) {
l.logDepth(ctx, 1, level, msg, args...)
}

func (l *Logger) logPC(err error, pc uintptr, level Level, msg string, args ...any) {
func (l *Logger) logPC(ctx context.Context, err error, pc uintptr, level Level, msg string, args ...any) {
r := NewRecord(time.Now(), level, msg, pc)
if err != nil {
r.front[0] = Any(ErrorKey, err)
r.nFront++
}
r.setAttrsFromArgs(args)
_ = l.Handler().Handle(l.ctx, r)
r.Add(args...)
_ = l.Handler().Handle(ctx, r)
}

// 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(1, level, msg, attrs...)
func (l *Logger) LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
l.logAttrsDepth(ctx, 1, level, msg, attrs...)
}

// Debug logs at LevelDebug.
func (l *Logger) Debug(msg string, args ...any) {
l.LogDepth(1, LevelDebug, msg, args...)
l.logDepth(nil, 1, LevelDebug, msg, args...)
}

// DebugCtx logs at LevelDebug with the given context.
func (l *Logger) DebugCtx(ctx context.Context, msg string, args ...any) {
l.logDepth(ctx, 1, LevelDebug, msg, args...)
}

// Info logs at LevelInfo.
func (l *Logger) Info(msg string, args ...any) {
l.LogDepth(1, LevelInfo, msg, args...)
l.logDepth(nil, 1, LevelInfo, msg, args...)
}

// InfoCtx logs at LevelInfo with the given context.
func (l *Logger) InfoCtx(ctx context.Context, msg string, args ...any) {
l.logDepth(ctx, 1, LevelInfo, msg, args...)
}

// Warn logs at LevelWarn.
func (l *Logger) Warn(msg string, args ...any) {
l.LogDepth(1, LevelWarn, msg, args...)
l.logDepth(nil, 1, LevelWarn, msg, args...)
}

// WarnCtx logs at LevelWarn with the given context.
func (l *Logger) WarnCtx(ctx context.Context, msg string, args ...any) {
l.logDepth(ctx, 1, LevelWarn, msg, args...)
}

// Error logs at LevelError.
// If err is non-nil, Error adds Any(ErrorKey, err)
// before the list of attributes.
func (l *Logger) Error(msg string, err error, args ...any) {
l.logDepthErr(err, 1, LevelError, msg, args...)
l.logDepthErr(nil, err, 1, LevelError, msg, args...)
}

// ErrorCtx logs at LevelError with the given context.
// If err is non-nil, it adds Any(ErrorKey, err)
// before the list of attributes.
func (l *Logger) ErrorCtx(ctx context.Context, msg string, err error, args ...any) {
l.logDepthErr(ctx, err, 1, LevelError, msg, args...)
}

// Debug calls Logger.Debug on the default logger.
func Debug(msg string, args ...any) {
Default().LogDepth(1, LevelDebug, msg, args...)
Default().logDepth(nil, 1, LevelDebug, msg, args...)
}

// DebugCtx calls Logger.DebugCtx on the default logger.
func DebugCtx(ctx context.Context, msg string, args ...any) {
Default().logDepth(ctx, 1, LevelDebug, msg, args...)
}

// Info calls Logger.Info on the default logger.
func Info(msg string, args ...any) {
Default().LogDepth(1, LevelInfo, msg, args...)
Default().logDepth(nil, 1, LevelInfo, msg, args...)
}

// InfoCtx calls Logger.InfoCtx on the default logger.
func InfoCtx(ctx context.Context, msg string, args ...any) {
Default().logDepth(ctx, 1, LevelInfo, msg, args...)
}

// Warn calls Logger.Warn on the default logger.
func Warn(msg string, args ...any) {
Default().LogDepth(1, LevelWarn, msg, args...)
Default().logDepth(nil, 1, LevelWarn, msg, args...)
}

// WarnCtx calls Logger.WarnCtx on the default logger.
func WarnCtx(ctx context.Context, msg string, args ...any) {
Default().logDepth(ctx, 1, LevelWarn, msg, args...)
}

// Error calls Logger.Error on the default logger.
func Error(msg string, err error, args ...any) {
Default().logDepthErr(err, 1, LevelError, msg, args...)
Default().logDepthErr(nil, err, 1, LevelError, msg, args...)
}

// ErrorCtx calls Logger.ErrorCtx on the default logger.
func ErrorCtx(ctx context.Context, msg string, err error, args ...any) {
Default().logDepthErr(ctx, err, 1, LevelError, msg, args...)
}

// Log calls Logger.Log on the default logger.
func Log(level Level, msg string, args ...any) {
Default().LogDepth(1, level, msg, args...)
func Log(ctx context.Context, level Level, msg string, args ...any) {
Default().logDepth(ctx, 1, level, msg, args...)
}

// LogAttrs calls Logger.LogAttrs on the default logger.
func LogAttrs(level Level, msg string, attrs ...Attr) {
Default().LogAttrsDepth(1, level, msg, attrs...)
func LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
Default().logAttrsDepth(ctx, 1, level, msg, attrs...)
}
Loading

0 comments on commit 806bf56

Please sign in to comment.