]> Cypherpunks.ru repositories - gostls13.git/commitdiff
log/slog: built-in handler constructors take options as a second arg
authorJonathan Amsterdam <jba@google.com>
Wed, 19 Apr 2023 18:24:33 +0000 (14:24 -0400)
committerJonathan Amsterdam <jba@google.com>
Thu, 4 May 2023 18:32:54 +0000 (18:32 +0000)
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>

16 files changed:
api/next/56345.txt
api/next/59339.txt [new file with mode: 0644]
src/log/slog/doc.go
src/log/slog/example_custom_levels_test.go
src/log/slog/example_level_handler_test.go
src/log/slog/example_logvaluer_secret_test.go
src/log/slog/example_test.go
src/log/slog/example_wrap_test.go
src/log/slog/handler_test.go
src/log/slog/internal/benchmarks/benchmarks_test.go
src/log/slog/json_handler.go
src/log/slog/json_handler_test.go
src/log/slog/logger_test.go
src/log/slog/text_handler.go
src/log/slog/text_handler_test.go
src/testing/slogtest/example_test.go

index fd3893e81d7a6963a9c713450f2e17154d90c5dd..c11ce6871e6bbfcb9e964ca51b4da9168680b60c 100644 (file)
@@ -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
@@ -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
diff --git a/api/next/59339.txt b/api/next/59339.txt
new file mode 100644 (file)
index 0000000..79156e9
--- /dev/null
@@ -0,0 +1,2 @@
+pkg log/slog, func NewJSONHandler(io.Writer, *HandlerOptions) *JSONHandler #59339
+pkg log/slog, func NewTextHandler(io.Writer, *HandlerOptions) *TextHandler #59339
index 5ca14b735b9892186bd3cf841da398198c922ffd..205c40de90607fdade25d4e01026f89e22779ef9 100644 (file)
@@ -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:
@@ -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:
@@ -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:
index 37fa9b37a3e9325b8aa5749a616b90834ee45e5d..2f230320bc57edecc783e7a82db0b5e3a3c7138d 100644 (file)
@@ -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,
@@ -69,7 +69,7 @@ func ExampleHandlerOptions_customLevels() {
 
                        return a
                },
-       }.NewTextHandler(os.Stdout)
+       })
 
        logger := slog.New(th)
        logger.Log(nil, LevelEmergency, "missing pilots")
index 9ddeab336967d020b77c24fe0217196b95f208b8..1ff91d47635faa37454bb9baff81d0d5e9e0aafe 100644 (file)
@@ -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")
index efc22a20e3f59e29211f36f7b0ae6800c256ee0b..51d002079383a5639bfe95f4f60930e73ef07b22 100644 (file)
@@ -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:
index 78b60b3649a99b667594b6669274f46afc13b2f0..a677456689cab05941476609a0f98123ad300216 100644 (file)
@@ -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),
index d422517b6e1556d8dc84dd9a329378ba74c35d92..dcc87b833cd37ba4c734b056804cfc38c4968703 100644 (file)
@@ -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:
index 7b5aac303cde00fe67602be6d697e8e1d589944c..fee611cf6af6d9dea680793194f3f47e10fa762a 100644 (file)
@@ -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
@@ -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"),
@@ -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").
index 5cbd1be29a3dc917132d5f3c657b071e1c8e1442..18643b73e6c8a0f62586311aa2a8d84218917237 100644 (file)
@@ -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) {
index c965a99152cd22f68f461e11bb0348f0ac0e812c..a99a99f1c1a0c589e0193d5066048378edba1afb 100644 (file)
@@ -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,
                },
        }
 }
index 7c683f0d34da0599349ffced1b13037d5f8d5614..d8457cb9eed48f3647104f992bc160b95b699ee3 100644 (file)
@@ -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 {
@@ -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"),
@@ -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++ {
index 2180ea7469282915ccb68f136301d6fcc7453ad9..f7bf7a81fe0c37cfc3fae850a951c1fa00f75147 100644 (file)
@@ -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()
@@ -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)
@@ -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()
@@ -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`)
index 4981eb67d22b82beca28834fe86e5749d6b98f15..75b66b716f3a0bbf355677e1445b4167e9d3dfaf 100644 (file)
@@ -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,
                },
        }
 }
index 87144a770a46f787697aa3ce2aa9ce8b1436516b..9d6301909dc09d33fb657a52c70add82d7c61ab7 100644 (file)
@@ -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 {
@@ -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)
@@ -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")
index 61e4b46e129f712932d60b47536bb19af54c3fc5..0517a4b8574d8c3e457c8557689f91c4cb9a9b77 100644 (file)
@@ -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