Skip to content

Commit dd950f8

Browse files
committed
slog: sync with log/slog
Bring this copy of slog up to date with the changes in the standard library log/slog package. Change-Id: I05d4de21388c4a4761b46b89fb74a1c7258ac06c Reviewed-on: https://go-review.googlesource.com/c/exp/+/494179 Run-TryBot: Jonathan Amsterdam <[email protected]> Reviewed-by: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 47ecfdc commit dd950f8

21 files changed

+249
-306
lines changed

slog/attr.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,26 @@ func Duration(key string, v time.Duration) Attr {
5858
}
5959

6060
// Group returns an Attr for a Group Value.
61-
// The caller must not subsequently mutate the
62-
// argument slice.
61+
// The first argument is the key; the remaining arguments
62+
// are converted to Attrs as in [Logger.Log].
6363
//
64-
// Use Group to collect several Attrs under a single
64+
// Use Group to collect several key-value pairs under a single
6565
// key on a log line, or as the result of LogValue
6666
// in order to log a single value as multiple Attrs.
67-
func Group(key string, as ...Attr) Attr {
68-
return Attr{key, GroupValue(as...)}
67+
func Group(key string, args ...any) Attr {
68+
return Attr{key, GroupValue(argsToAttrSlice(args)...)}
69+
}
70+
71+
func argsToAttrSlice(args []any) []Attr {
72+
var (
73+
attr Attr
74+
attrs []Attr
75+
)
76+
for len(args) > 0 {
77+
attr, args = argsToAttr(args)
78+
attrs = append(attrs, attr)
79+
}
80+
return attrs
6981
}
7082

7183
// Any returns an Attr for the supplied value.

slog/benchmarks/benchmarks_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ func BenchmarkAttrs(b *testing.B) {
3131
{"disabled", disabledHandler{}},
3232
{"async discard", newAsyncHandler()},
3333
{"fastText discard", newFastTextHandler(io.Discard)},
34-
{"Text discard", slog.NewTextHandler(io.Discard)},
35-
{"JSON discard", slog.NewJSONHandler(io.Discard)},
34+
{"Text discard", slog.NewTextHandler(io.Discard, nil)},
35+
{"JSON discard", slog.NewJSONHandler(io.Discard, nil)},
3636
} {
3737
logger := slog.New(handler.h)
3838
b.Run(handler.name, func(b *testing.B) {

slog/doc.go

+40-40
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ For more control over the output format, create a logger with a different handle
4444
This statement uses [New] to create a new logger with a TextHandler
4545
that writes structured records in text form to standard error:
4646
47-
logger := slog.New(slog.NewTextHandler(os.Stderr))
47+
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
4848
4949
[TextHandler] output is a sequence of key=value pairs, easily and unambiguously
5050
parsed by machine. This statement:
@@ -57,14 +57,14 @@ produces this output:
5757
5858
The package also provides [JSONHandler], whose output is line-delimited JSON:
5959
60-
logger := slog.New(slog.NewJSONHandler(os.Stdout))
60+
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
6161
logger.Info("hello", "count", 3)
6262
6363
produces this output:
6464
6565
{"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3}
6666
67-
Both [TextHandler] and [JSONHandler] can be configured with a [HandlerOptions].
67+
Both [TextHandler] and [JSONHandler] can be configured with [HandlerOptions].
6868
There are options for setting the minimum level (see Levels, below),
6969
displaying the source file and line of the log call, and
7070
modifying attributes before they are logged.
@@ -78,38 +78,6 @@ will cause the top-level functions like [Info] to use it.
7878
so that existing applications that use [log.Printf] and related functions
7979
will send log records to the logger's handler without needing to be rewritten.
8080
81-
# Attrs and Values
82-
83-
An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as
84-
alternating keys and values. The statement
85-
86-
slog.Info("hello", slog.Int("count", 3))
87-
88-
behaves the same as
89-
90-
slog.Info("hello", "count", 3)
91-
92-
There are convenience constructors for [Attr] such as [Int], [String], and [Bool]
93-
for common types, as well as the function [Any] for constructing Attrs of any
94-
type.
95-
96-
The value part of an Attr is a type called [Value].
97-
Like an [any], a Value can hold any Go value,
98-
but it can represent typical values, including all numbers and strings,
99-
without an allocation.
100-
101-
For the most efficient log output, use [Logger.LogAttrs].
102-
It is similar to [Logger.Log] but accepts only Attrs, not alternating
103-
keys and values; this allows it, too, to avoid allocation.
104-
105-
The call
106-
107-
logger.LogAttrs(nil, slog.LevelInfo, "hello", slog.Int("count", 3))
108-
109-
is the most efficient way to achieve the same output as
110-
111-
slog.Info("hello", "count", 3)
112-
11381
Some attributes are common to many log calls.
11482
For example, you may wish to include the URL or trace identifier of a server request
11583
with all log events arising from the request.
@@ -149,7 +117,7 @@ a global LevelVar:
149117
150118
Then use the LevelVar to construct a handler, and make it the default:
151119
152-
h := slog.HandlerOptions{Level: programLevel}.NewJSONHandler(os.Stderr)
120+
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel})
153121
slog.SetDefault(slog.New(h))
154122
155123
Now the program can change its logging level with a single statement:
@@ -164,11 +132,11 @@ How this qualification is displayed depends on the handler.
164132
[TextHandler] separates the group and attribute names with a dot.
165133
[JSONHandler] treats each group as a separate JSON object, with the group name as the key.
166134
167-
Use [Group] to create a Group Attr from a name and a list of Attrs:
135+
Use [Group] to create a Group attribute from a name and a list of key-value pairs:
168136
169137
slog.Group("request",
170-
slog.String("method", r.Method),
171-
slog.Any("url", r.URL))
138+
"method", r.Method,
139+
"url", r.URL)
172140
173141
TextHandler would display this group as
174142
@@ -199,7 +167,7 @@ so even if it uses the common key "id", the log line will have distinct keys.
199167
200168
Some handlers may wish to include information from the [context.Context] that is
201169
available at the call site. One example of such information
202-
is the identifier for the current span when tracing is is enabled.
170+
is the identifier for the current span when tracing is enabled.
203171
204172
The [Logger.Log] and [Logger.LogAttrs] methods take a context as a first
205173
argument, as do their corresponding top-level functions.
@@ -212,6 +180,38 @@ in "Ctx" do. For example,
212180
213181
It is recommended to pass a context to an output method if one is available.
214182
183+
# Attrs and Values
184+
185+
An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as
186+
alternating keys and values. The statement
187+
188+
slog.Info("hello", slog.Int("count", 3))
189+
190+
behaves the same as
191+
192+
slog.Info("hello", "count", 3)
193+
194+
There are convenience constructors for [Attr] such as [Int], [String], and [Bool]
195+
for common types, as well as the function [Any] for constructing Attrs of any
196+
type.
197+
198+
The value part of an Attr is a type called [Value].
199+
Like an [any], a Value can hold any Go value,
200+
but it can represent typical values, including all numbers and strings,
201+
without an allocation.
202+
203+
For the most efficient log output, use [Logger.LogAttrs].
204+
It is similar to [Logger.Log] but accepts only Attrs, not alternating
205+
keys and values; this allows it, too, to avoid allocation.
206+
207+
The call
208+
209+
logger.LogAttrs(nil, slog.LevelInfo, "hello", slog.Int("count", 3))
210+
211+
is the most efficient way to achieve the same output as
212+
213+
slog.Info("hello", "count", 3)
214+
215215
# Customizing a type's logging behavior
216216
217217
If a type implements the [LogValuer] interface, the [Value] returned from its LogValue

slog/example_custom_levels_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func ExampleHandlerOptions_customLevels() {
2626
LevelEmergency = slog.Level(12)
2727
)
2828

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

7171
return a
7272
},
73-
}.NewTextHandler(os.Stdout)
73+
})
7474

7575
logger := slog.New(th)
7676
logger.Log(nil, LevelEmergency, "missing pilots")

slog/example_level_handler_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (h *LevelHandler) Handler() slog.Handler {
6464
// Another typical use would be to decrease the log level (to LevelDebug, say)
6565
// during a part of the program that was suspected of containing a bug.
6666
func ExampleHandler_levelHandler() {
67-
th := slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime}.NewTextHandler(os.Stdout)
67+
th := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime})
6868
logger := slog.New(NewLevelHandler(slog.LevelWarn, th))
6969
logger.Info("not printed")
7070
logger.Warn("printed")

slog/example_logvaluer_group_test.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
package slog_test
66

7-
import (
8-
"golang.org/x/exp/slog"
9-
)
7+
import "golang.org/x/exp/slog"
108

119
type Name struct {
1210
First, Last string

slog/example_logvaluer_secret_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ func (Token) LogValue() slog.Value {
2424
// with an alternative representation to avoid revealing secrets.
2525
func ExampleLogValuer_secret() {
2626
t := Token("shhhh!")
27-
logger := slog.New(slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime}.
28-
NewTextHandler(os.Stdout))
27+
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime}))
2928
logger.Info("permission granted", "user", "Perry", "token", t)
3029

3130
// Output:

slog/example_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func ExampleGroup() {
1717
r, _ := http.NewRequest("GET", "localhost", nil)
1818
// ...
1919

20-
logger := slog.New(slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime}.NewTextHandler(os.Stdout))
20+
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: testutil.RemoveTime}))
2121
slog.SetDefault(logger)
2222

2323
slog.Info("finished",

slog/example_wrap_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ func Example_wrapping() {
3535
}
3636
// Remove the directory from the source's filename.
3737
if a.Key == slog.SourceKey {
38-
a.Value = slog.StringValue(filepath.Base(a.Value.String()))
38+
source := a.Value.Any().(*slog.Source)
39+
source.File = filepath.Base(source.File)
3940
}
4041
return a
4142
}
42-
logger := slog.New(slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}.NewTextHandler(os.Stdout))
43+
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true, ReplaceAttr: replace}))
4344
Infof(logger, "message, %s", "formatted")
4445

4546
// Output:
46-
// level=INFO source=example_wrap_test.go:43 msg="message, formatted"
47+
// level=INFO source=example_wrap_test.go:44 msg="message, formatted"
4748
}

slog/handler.go

+14-42
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type Handler interface {
4141
Enabled(context.Context, Level) bool
4242

4343
// Handle handles the Record.
44-
// It will only be called Enabled returns true.
44+
// It will only be called when Enabled returns true.
4545
// The Context argument is as for Enabled.
4646
// It is present solely to provide Handlers access to the context's values.
4747
// Canceling the context should not affect record processing.
@@ -130,10 +130,8 @@ func (h *defaultHandler) WithGroup(name string) Handler {
130130
// HandlerOptions are options for a TextHandler or JSONHandler.
131131
// A zero HandlerOptions consists entirely of default values.
132132
type HandlerOptions struct {
133-
// When AddSource is true, the handler adds a ("source", "file:line")
134-
// attribute to the output indicating the source code position of the log
135-
// statement. AddSource is false by default to skip the cost of computing
136-
// this information.
133+
// AddSource causes the handler to compute the source code position
134+
// of the log statement and add a SourceKey attribute to the output.
137135
AddSource bool
138136

139137
// Level reports the minimum record level that will be logged.
@@ -285,22 +283,7 @@ func (h *commonHandler) handle(r Record) error {
285283
}
286284
// source
287285
if h.opts.AddSource {
288-
frame := r.frame()
289-
if frame.File != "" {
290-
key := SourceKey
291-
if rep == nil {
292-
state.appendKey(key)
293-
state.appendSource(frame.File, frame.Line)
294-
} else {
295-
buf := buffer.New()
296-
buf.WriteString(frame.File) // TODO: escape?
297-
buf.WriteByte(':')
298-
buf.WritePosInt(frame.Line)
299-
s := buf.String()
300-
buf.Free()
301-
state.appendAttr(String(key, s))
302-
}
303-
}
286+
state.appendAttr(Any(SourceKey, r.source()))
304287
}
305288
key = MessageKey
306289
msg := r.Message
@@ -421,7 +404,6 @@ func (s *handleState) openGroup(name string) {
421404
if s.groups != nil {
422405
*s.groups = append(*s.groups, name)
423406
}
424-
425407
}
426408

427409
// closeGroup ends the group with the given name.
@@ -455,6 +437,16 @@ func (s *handleState) appendAttr(a Attr) {
455437
if a.isEmpty() {
456438
return
457439
}
440+
// Special case: Source.
441+
if v := a.Value; v.Kind() == KindAny {
442+
if src, ok := v.Any().(*Source); ok {
443+
if s.h.json {
444+
a.Value = src.group()
445+
} else {
446+
a.Value = StringValue(fmt.Sprintf("%s:%d", src.File, src.Line))
447+
}
448+
}
449+
}
458450
if a.Value.Kind() == KindGroup {
459451
attrs := a.Value.Group()
460452
// Output only non-empty groups.
@@ -496,26 +488,6 @@ func (s *handleState) appendKey(key string) {
496488
s.sep = s.h.attrSep()
497489
}
498490

499-
func (s *handleState) appendSource(file string, line int) {
500-
if s.h.json {
501-
s.buf.WriteByte('"')
502-
*s.buf = appendEscapedJSONString(*s.buf, file)
503-
s.buf.WriteByte(':')
504-
s.buf.WritePosInt(line)
505-
s.buf.WriteByte('"')
506-
} else {
507-
// text
508-
if needsQuoting(file) {
509-
s.appendString(file + ":" + strconv.Itoa(line))
510-
} else {
511-
// common case: no quoting needed.
512-
s.appendString(file)
513-
s.buf.WriteByte(':')
514-
s.buf.WritePosInt(line)
515-
}
516-
}
517-
}
518-
519491
func (s *handleState) appendString(str string) {
520492
if s.h.json {
521493
s.buf.WriteByte('"')

0 commit comments

Comments
 (0)