Skip to content

Commit

Permalink
log/slog: built-in handler constructors take options as a second arg
Browse files Browse the repository at this point in the history
There is now one constructor function for each built-in handler, with
signature

    NewXXXHandler(io.Writer, *HandlerOptions) *XXXHandler

Fixes #59339.

Change-Id: Ia02183c5ce0dc15c64e33ad05fd69bca09df2d2d
Reviewed-on: https://go-review.googlesource.com/c/go/+/486415
Reviewed-by: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
  • Loading branch information
jba authored and pull[bot] committed Jun 27, 2023
1 parent c05bb1d commit 0044864
Show file tree
Hide file tree
Showing 16 changed files with 44 additions and 49 deletions.
4 changes: 0 additions & 4 deletions api/next/56345.txt
Expand Up @@ -58,10 +58,8 @@ pkg log/slog, func IntValue(int) Value #56345
pkg log/slog, func Log(context.Context, Level, string, ...interface{}) #56345
pkg log/slog, func LogAttrs(context.Context, Level, string, ...Attr) #56345
pkg log/slog, func New(Handler) *Logger #56345
pkg log/slog, func NewJSONHandler(io.Writer) *JSONHandler #56345
pkg log/slog, func NewLogLogger(Handler, Level) *log.Logger #56345
pkg log/slog, func NewRecord(time.Time, Level, string, uintptr) Record #56345
pkg log/slog, func NewTextHandler(io.Writer) *TextHandler #56345
pkg log/slog, func SetDefault(*Logger) #56345
pkg log/slog, func String(string, string) Attr #56345
pkg log/slog, func StringValue(string) Value #56345
Expand Down Expand Up @@ -105,8 +103,6 @@ pkg log/slog, method (*TextHandler) WithAttrs([]Attr) Handler #56345
pkg log/slog, method (*TextHandler) WithGroup(string) Handler #56345
pkg log/slog, method (Attr) Equal(Attr) bool #56345
pkg log/slog, method (Attr) String() string #56345
pkg log/slog, method (HandlerOptions) NewJSONHandler(io.Writer) *JSONHandler #56345
pkg log/slog, method (HandlerOptions) NewTextHandler(io.Writer) *TextHandler #56345
pkg log/slog, method (Kind) String() string #56345
pkg log/slog, method (Level) Level() Level #56345
pkg log/slog, method (Level) MarshalJSON() ([]uint8, error) #56345
Expand Down
2 changes: 2 additions & 0 deletions api/next/59339.txt
@@ -0,0 +1,2 @@
pkg log/slog, func NewJSONHandler(io.Writer, *HandlerOptions) *JSONHandler #59339
pkg log/slog, func NewTextHandler(io.Writer, *HandlerOptions) *TextHandler #59339
6 changes: 3 additions & 3 deletions src/log/slog/doc.go
Expand Up @@ -44,7 +44,7 @@ For more control over the output format, create a logger with a different handle
This statement uses [New] to create a new logger with a TextHandler
that writes structured records in text form to standard error:
logger := slog.New(slog.NewTextHandler(os.Stderr))
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
[TextHandler] output is a sequence of key=value pairs, easily and unambiguously
parsed by machine. This statement:
Expand All @@ -57,7 +57,7 @@ produces this output:
The package also provides [JSONHandler], whose output is line-delimited JSON:
logger := slog.New(slog.NewJSONHandler(os.Stdout))
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("hello", "count", 3)
produces this output:
Expand Down Expand Up @@ -149,7 +149,7 @@ a global LevelVar:
Then use the LevelVar to construct a handler, and make it the default:
h := slog.HandlerOptions{Level: programLevel}.NewJSONHandler(os.Stderr)
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel})
slog.SetDefault(slog.New(h))
Now the program can change its logging level with a single statement:
Expand Down
4 changes: 2 additions & 2 deletions src/log/slog/example_custom_levels_test.go
Expand Up @@ -25,7 +25,7 @@ func ExampleHandlerOptions_customLevels() {
LevelEmergency = slog.Level(12)
)

th := slog.HandlerOptions{
th := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
// Set a custom level to show all log output. The default value is
// LevelInfo, which would drop Debug and Trace logs.
Level: LevelTrace,
Expand Down Expand Up @@ -69,7 +69,7 @@ func ExampleHandlerOptions_customLevels() {

return a
},
}.NewTextHandler(os.Stdout)
})

logger := slog.New(th)
logger.Log(nil, LevelEmergency, "missing pilots")
Expand Down
2 changes: 1 addition & 1 deletion src/log/slog/example_level_handler_test.go
Expand Up @@ -63,7 +63,7 @@ func (h *LevelHandler) Handler() slog.Handler {
// Another typical use would be to decrease the log level (to LevelDebug, say)
// during a part of the program that was suspected of containing a bug.
func ExampleHandler_levelHandler() {
th := slog.HandlerOptions{ReplaceAttr: slogtest.RemoveTime}.NewTextHandler(os.Stdout)
th := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: slogtest.RemoveTime})
logger := slog.New(NewLevelHandler(slog.LevelWarn, th))
logger.Info("not printed")
logger.Warn("printed")
Expand Down
3 changes: 1 addition & 2 deletions src/log/slog/example_logvaluer_secret_test.go
Expand Up @@ -23,8 +23,7 @@ func (Token) LogValue() slog.Value {
// with an alternative representation to avoid revealing secrets.
func ExampleLogValuer_secret() {
t := Token("shhhh!")
logger := slog.New(slog.HandlerOptions{ReplaceAttr: slogtest.RemoveTime}.
NewTextHandler(os.Stdout))
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: slogtest.RemoveTime}))
logger.Info("permission granted", "user", "Perry", "token", t)

// Output:
Expand Down
2 changes: 1 addition & 1 deletion src/log/slog/example_test.go
Expand Up @@ -16,7 +16,7 @@ func ExampleGroup() {
r, _ := http.NewRequest("GET", "localhost", nil)
// ...

logger := slog.New(slog.HandlerOptions{ReplaceAttr: slogtest.RemoveTime}.NewTextHandler(os.Stdout))
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: slogtest.RemoveTime}))
logger.Info("finished",
slog.Group("req",
slog.String("method", r.Method),
Expand Down
2 changes: 1 addition & 1 deletion src/log/slog/example_wrap_test.go
Expand Up @@ -39,7 +39,7 @@ func Example_wrapping() {
}
return a
}
logger := slog.New(slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}.NewTextHandler(os.Stdout))
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}))
Infof(logger, "message, %s", "formatted")

// Output:
Expand Down
10 changes: 5 additions & 5 deletions src/log/slog/handler_test.go
Expand Up @@ -338,8 +338,8 @@ func TestJSONAndTextHandlers(t *testing.T) {
h Handler
want string
}{
{"text", opts.NewTextHandler(&buf), test.wantText},
{"json", opts.NewJSONHandler(&buf), test.wantJSON},
{"text", NewTextHandler(&buf, &opts), test.wantText},
{"json", NewJSONHandler(&buf, &opts), test.wantJSON},
} {
t.Run(handler.name, func(t *testing.T) {
h := handler.h
Expand Down Expand Up @@ -419,7 +419,7 @@ func TestSecondWith(t *testing.T) {
// Verify that a second call to Logger.With does not corrupt
// the original.
var buf bytes.Buffer
h := HandlerOptions{ReplaceAttr: removeKeys(TimeKey)}.NewTextHandler(&buf)
h := NewTextHandler(&buf, &HandlerOptions{ReplaceAttr: removeKeys(TimeKey)})
logger := New(h).With(
String("app", "playground"),
String("role", "tester"),
Expand All @@ -445,14 +445,14 @@ func TestReplaceAttrGroups(t *testing.T) {

var got []ga

h := HandlerOptions{ReplaceAttr: func(gs []string, a Attr) Attr {
h := NewTextHandler(io.Discard, &HandlerOptions{ReplaceAttr: func(gs []string, a Attr) Attr {
v := a.Value.String()
if a.Key == TimeKey {
v = "<now>"
}
got = append(got, ga{strings.Join(gs, ","), a.Key, v})
return a
}}.NewTextHandler(io.Discard)
}})
New(h).
With(Int("a", 1)).
WithGroup("g1").
Expand Down
4 changes: 2 additions & 2 deletions src/log/slog/internal/benchmarks/benchmarks_test.go
Expand Up @@ -32,8 +32,8 @@ func BenchmarkAttrs(b *testing.B) {
{"disabled", disabledHandler{}, false},
{"async discard", newAsyncHandler(), true},
{"fastText discard", newFastTextHandler(io.Discard), false},
{"Text discard", slog.NewTextHandler(io.Discard), false},
{"JSON discard", slog.NewJSONHandler(io.Discard), false},
{"Text discard", slog.NewTextHandler(io.Discard, nil), false},
{"JSON discard", slog.NewJSONHandler(io.Discard, nil), false},
} {
logger := slog.New(handler.h)
b.Run(handler.name, func(b *testing.B) {
Expand Down
15 changes: 7 additions & 8 deletions src/log/slog/json_handler.go
Expand Up @@ -25,18 +25,17 @@ type JSONHandler struct {
}

// NewJSONHandler creates a JSONHandler that writes to w,
// using the default options.
func NewJSONHandler(w io.Writer) *JSONHandler {
return (HandlerOptions{}).NewJSONHandler(w)
}

// NewJSONHandler creates a JSONHandler with the given options that writes to w.
func (opts HandlerOptions) NewJSONHandler(w io.Writer) *JSONHandler {
// using the given options.
// If opts is nil, the default options are used.
func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler {
if opts == nil {
opts = &HandlerOptions{}
}
return &JSONHandler{
&commonHandler{
json: true,
w: w,
opts: opts,
opts: *opts,
},
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/log/slog/json_handler_test.go
Expand Up @@ -39,7 +39,7 @@ func TestJSONHandler(t *testing.T) {
} {
t.Run(test.name, func(t *testing.T) {
var buf bytes.Buffer
h := test.opts.NewJSONHandler(&buf)
h := NewJSONHandler(&buf, &test.opts)
r := NewRecord(testTime, LevelInfo, "m", 0)
r.AddAttrs(Int("a", 1), Any("m", map[string]int{"b": 2}))
if err := h.Handle(context.Background(), r); err != nil {
Expand Down Expand Up @@ -171,7 +171,7 @@ func BenchmarkJSONHandler(b *testing.B) {
}},
} {
b.Run(bench.name, func(b *testing.B) {
l := New(bench.opts.NewJSONHandler(io.Discard)).With(
l := New(NewJSONHandler(io.Discard, &bench.opts)).With(
String("program", "my-test-program"),
String("package", "log/slog"),
String("traceID", "2039232309232309"),
Expand Down Expand Up @@ -236,7 +236,7 @@ func BenchmarkPreformatting(b *testing.B) {
{"struct file", outFile, structAttrs},
} {
b.Run(bench.name, func(b *testing.B) {
l := New(NewJSONHandler(bench.wc)).With(bench.attrs...)
l := New(NewJSONHandler(bench.wc, nil)).With(bench.attrs...)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Expand Down
10 changes: 5 additions & 5 deletions src/log/slog/logger_test.go
Expand Up @@ -27,7 +27,7 @@ const timeRE = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}(Z|[+-]\d{2}:\d{2})`
func TestLogTextHandler(t *testing.T) {
var buf bytes.Buffer

l := New(NewTextHandler(&buf))
l := New(NewTextHandler(&buf, nil))

check := func(want string) {
t.Helper()
Expand Down Expand Up @@ -104,13 +104,13 @@ func TestConnections(t *testing.T) {

// Once slog.SetDefault is called, the direction is reversed: the default
// log.Logger's output goes through the handler.
SetDefault(New(HandlerOptions{AddSource: true}.NewTextHandler(&slogbuf)))
SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{AddSource: true})))
log.Print("msg2")
checkLogOutput(t, slogbuf.String(), "time="+timeRE+` level=INFO source=.*logger_test.go:\d{3} msg=msg2`)

// The default log.Logger always outputs at Info level.
slogbuf.Reset()
SetDefault(New(HandlerOptions{Level: LevelWarn}.NewTextHandler(&slogbuf)))
SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{Level: LevelWarn})))
log.Print("should not appear")
if got := slogbuf.String(); got != "" {
t.Errorf("got %q, want empty", got)
Expand Down Expand Up @@ -352,7 +352,7 @@ func TestLoggerError(t *testing.T) {
}
return a
}
l := New(HandlerOptions{ReplaceAttr: removeTime}.NewTextHandler(&buf))
l := New(NewTextHandler(&buf, &HandlerOptions{ReplaceAttr: removeTime}))
l.Error("msg", "err", io.EOF, "a", 1)
checkLogOutput(t, buf.String(), `level=ERROR msg=msg err=EOF a=1`)
buf.Reset()
Expand All @@ -362,7 +362,7 @@ func TestLoggerError(t *testing.T) {

func TestNewLogLogger(t *testing.T) {
var buf bytes.Buffer
h := NewTextHandler(&buf)
h := NewTextHandler(&buf, nil)
ll := NewLogLogger(h, LevelWarn)
ll.Print("hello")
checkLogOutput(t, buf.String(), "time="+timeRE+` level=WARN msg=hello`)
Expand Down
15 changes: 7 additions & 8 deletions src/log/slog/text_handler.go
Expand Up @@ -22,18 +22,17 @@ type TextHandler struct {
}

// NewTextHandler creates a TextHandler that writes to w,
// using the default options.
func NewTextHandler(w io.Writer) *TextHandler {
return (HandlerOptions{}).NewTextHandler(w)
}

// NewTextHandler creates a TextHandler with the given options that writes to w.
func (opts HandlerOptions) NewTextHandler(w io.Writer) *TextHandler {
// using the given options.
// If opts is nil, the default options are used.
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
if opts == nil {
opts = &HandlerOptions{}
}
return &TextHandler{
&commonHandler{
json: false,
w: w,
opts: opts,
opts: *opts,
},
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/log/slog/text_handler_test.go
Expand Up @@ -82,7 +82,7 @@ func TestTextHandler(t *testing.T) {
} {
t.Run(opts.name, func(t *testing.T) {
var buf bytes.Buffer
h := opts.opts.NewTextHandler(&buf)
h := NewTextHandler(&buf, &opts.opts)
r := NewRecord(testTime, LevelInfo, "a message", 0)
r.AddAttrs(test.attr)
if err := h.Handle(context.Background(), r); err != nil {
Expand Down Expand Up @@ -124,7 +124,7 @@ func (t text) MarshalText() ([]byte, error) {

func TestTextHandlerPreformatted(t *testing.T) {
var buf bytes.Buffer
var h Handler = NewTextHandler(&buf)
var h Handler = NewTextHandler(&buf, nil)
h = h.WithAttrs([]Attr{Duration("dur", time.Minute), Bool("b", true)})
// Also test omitting time.
r := NewRecord(time.Time{}, 0 /* 0 Level is INFO */, "m", 0)
Expand All @@ -145,7 +145,7 @@ func TestTextHandlerAlloc(t *testing.T) {
for i := 0; i < 10; i++ {
r.AddAttrs(Int("x = y", i))
}
var h Handler = NewTextHandler(io.Discard)
var h Handler = NewTextHandler(io.Discard, nil)
wantAllocs(t, 0, func() { h.Handle(context.Background(), r) })

h = h.WithGroup("s")
Expand Down
2 changes: 1 addition & 1 deletion src/testing/slogtest/example_test.go
Expand Up @@ -19,7 +19,7 @@ import (
// format when given a pointer to a map[string]any.
func Example_parsing() {
var buf bytes.Buffer
h := slog.NewJSONHandler(&buf)
h := slog.NewJSONHandler(&buf, nil)

results := func() []map[string]any {
var ms []map[string]any
Expand Down

0 comments on commit 0044864

Please sign in to comment.