design/56345-structured-logging.md: update with latest API changes
Bring the design doc in line with recent minor changes to slog.
Change-Id: If9c1e2cc7381323d08057dc29a30e89926fd768d
Reviewed-on: https://go-review.googlesource.com/c/proposal/+/467440
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/design/56345-structured-logging.md b/design/56345-structured-logging.md
index efe35d3..e3431c9 100644
--- a/design/56345-structured-logging.md
+++ b/design/56345-structured-logging.md
@@ -196,15 +196,21 @@
type Handler interface {
// Enabled reports whether the handler handles records at the given level.
// The handler ignores records whose level is lower.
- // Enabled is called early, before any arguments are processed,
+ // It is called early, before any arguments are processed,
// to save effort if the log event should be discarded.
- Enabled(Level) bool
+ // The Logger's context is passed so Enabled can use its values
+ // to make a decision. The context may be nil.
+ Enabled(context.Context, Level) bool
// Handle handles the Record.
// It will only be called if Enabled returns true.
// Handle methods that produce output should observe the following rules:
// - If r.Time is the zero time, ignore the time.
- // - If an Attr's key is the empty string, ignore the Attr.
+ // - If an Attr's key is the empty string and the value is not a group,
+ // ignore the Attr.
+ // - If a group's key is empty, inline the group's Attrs.
+ // - If a group has no Attrs (even if it has a non-empty key),
+ // ignore it.
Handle(r Record) error
// WithAttrs returns a new Handler whose attributes consist of
@@ -229,6 +235,8 @@
// should behave like
//
// logger.LogAttrs(level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2)))
+ //
+ // If the name is empty, WithGroup returns the receiver.
WithGroup(name string) Handler
}
```
@@ -256,13 +264,16 @@
// Canceling the context should not affect record processing.
Context context.Context
+ // The program counter at the time the record was constructed, as determined
+ // by runtime.Callers. If zero, no program counter is available.
+ //
+ // The only valid use for this value is as an argument to
+ // [runtime.CallersFrames]. In particular, it must not be passed to
+ // [runtime.FuncForPC].
+ PC uintptr
+
// Has unexported fields.
}
-
-func (r Record) SourceLine() (file string, line int)
- SourceLine returns the file and line of the log event. If the Record
- was created without the necessary information, or if the location is
- unavailable, it returns ("", 0).
```
Records have two methods for accessing the sequence of `Attr`s. This API allows
@@ -281,17 +292,16 @@
a `Record` directly and add attributes to it:
```
-func NewRecord(t time.Time, level Level, msg string, calldepth int, ctx context.Context) Record
- NewRecord creates a Record from the given arguments. Use Record.AddAttrs
- to add attributes to the Record. If calldepth is greater than zero,
- Record.SourceLine will return the file and line number at that depth,
- where 1 means the caller of NewRecord.
+func NewRecord(t time.Time, level Level, msg string, pc uintptr, ctx context.Context) Record
+ NewRecord creates a Record from the given arguments. Use Record.AddAttrs to
+ add attributes to the Record.
NewRecord is intended for logging APIs that want to support a Handler as a
backend.
func (r *Record) AddAttrs(attrs ...Attr)
- AddAttrs appends the given attrs to the Record's list of Attrs.
+ AddAttrs appends the given Attrs to the Record's list of Attrs. It resolves
+ the Attrs before doing so.
```
Copies of a `Record` share state. A `Record` should not be modified after
@@ -501,9 +511,8 @@
}
```
-If the Go value in a `Value` implements `LogValuer`,
-then a `Handler` should use the result of calling `LogValue`.
-It can use `Value.Resolve` to do so.
+
+`Value.Resolve` can be used to call the `LogValue` method.
```
func (v Value) Resolve() Value
@@ -513,7 +522,12 @@
not to be of Kind LogValuerKind.
```
-As an example, a type could obscure its value in log output like so:
+The Attrs passed to a `Handler.WithAttrs`, and the Attrs obtained
+via `Record.Attrs`, have already been resolved, that is, replaced
+with a call to `Resolve`.
+
+As an example of `LogValuer`, a type could obscure its value in log output like
+so:
```
type Password string
@@ -609,8 +623,8 @@
Warn logs at LevelWarn.
func (l *Logger) Error(msg string, err error, args ...any)
- Error logs at LevelError. If err is non-nil, Error appends Any("err",
- err) to the list of attributes.
+ Error logs at LevelError. If err is non-nil, Error adds Any("err",
+ err) before the list of attributes.
```
The `Log` and `LogAttrs` methods have variants that take a call depth, so other
@@ -853,8 +867,8 @@
// If ReplaceAttr returns an Attr with Key == "", the attribute is discarded.
//
// The built-in attributes with keys "time", "level", "source", and "msg"
- // are passed to this function, except that time and level are omitted
- // if zero, and source is omitted if AddSourceLine is false.
+ // are passed to this function, except that time is omitted
+ // if zero, and source is omitted if AddSource is false.
//
// The first argument is a list of currently open groups that contain the
// Attr. It must not be retained or modified. ReplaceAttr is never called
@@ -974,6 +988,11 @@
func LogAttrs(level Level, msg string, attrs ...Attr)
LogAttrs calls Logger.LogAttrs on the default logger.
+func NewLogLogger(h Handler, level Level) *log.Logger
+ 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 SetDefault(l *Logger)
SetDefault makes l the default Logger. After this call, output from the
log package's default Logger (as with log.Print, etc.) will be logged at
@@ -1034,15 +1053,21 @@
type Handler interface {
// Enabled reports whether the handler handles records at the given level.
// The handler ignores records whose level is lower.
- // Enabled is called early, before any arguments are processed,
+ // It is called early, before any arguments are processed,
// to save effort if the log event should be discarded.
- Enabled(Level) bool
+ // The Logger's context is passed so Enabled can use its values
+ // to make a decision. The context may be nil.
+ Enabled(context.Context, Level) bool
// Handle handles the Record.
// It will only be called if Enabled returns true.
// Handle methods that produce output should observe the following rules:
// - If r.Time is the zero time, ignore the time.
- // - If an Attr's key is the empty string, ignore the Attr.
+ // - If an Attr's key is the empty string and the value is not a group,
+ // ignore the Attr.
+ // - If a group's key is empty, inline the group's Attrs.
+ // - If a group has no Attrs (even if it has a non-empty key),
+ // ignore it.
Handle(r Record) error
// WithAttrs returns a new Handler whose attributes consist of
@@ -1067,6 +1092,8 @@
// should behave like
//
// logger.LogAttrs(level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2)))
+ //
+ // If the name is empty, WithGroup returns the receiver.
WithGroup(name string) Handler
}
A Handler handles log records produced by a Logger..
@@ -1098,8 +1125,8 @@
// If ReplaceAttr returns an Attr with Key == "", the attribute is discarded.
//
// The built-in attributes with keys "time", "level", "source", and "msg"
- // are passed to this function, except that time and level are omitted
- // if zero, and source is omitted if AddSourceLine is false.
+ // are passed to this function, except that time is omitted
+ // if zero, and source is omitted if AddSource is false.
//
// The first argument is a list of currently open groups that contain the
// Attr. It must not be retained or modified. ReplaceAttr is never called
@@ -1141,7 +1168,7 @@
NewJSONHandler creates a JSONHandler that writes to w, using the default
options.
-func (h *JSONHandler) Enabled(level Level) bool
+func (h *JSONHandler) Enabled(_ context.Context, level Level) bool
Enabled reports whether the handler handles records at the given level.
The handler ignores records whose level is lower.
@@ -1167,12 +1194,13 @@
- Floating-point NaNs and infinities are formatted as one of the strings
"NaN", "+Inf" or "-Inf".
- Levels are formatted as with Level.String.
+ - HTML characters are not escaped.
Each call to Handle results in a single serialized call to io.Writer.Write.
func (h *JSONHandler) WithAttrs(attrs []Attr) Handler
- With returns a new JSONHandler whose attributes consists of h's attributes
- followed by attrs.
+ WithAttrs returns a new JSONHandler whose attributes consists of h's
+ attributes followed by attrs.
func (h *JSONHandler) WithGroup(name string) Handler
@@ -1180,16 +1208,16 @@
Kind is the kind of a Value.
const (
- AnyKind Kind = iota
- BoolKind
- DurationKind
- Float64Kind
- Int64Kind
- StringKind
- TimeKind
- Uint64Kind
- GroupKind
- LogValuerKind
+ KindAny Kind = iota
+ KindBool
+ KindDuration
+ KindFloat64
+ KindInt64
+ KindString
+ KindTime
+ KindUint64
+ KindGroup
+ KindLogValuer
)
func (k Kind) String() string
@@ -1225,6 +1253,11 @@
Level returns the receiver. It implements Leveler.
func (l Level) MarshalJSON() ([]byte, error)
+ MarshalJSON implements encoding/json.Marshaler by quoting the output of
+ Level.String.
+
+func (l Level) MarshalText() ([]byte, error)
+ MarshalText implements encoding.TextMarshaler by calling Level.String.
func (l Level) String() string
String returns a name for the level. If the level has a name, then that
@@ -1234,6 +1267,18 @@
LevelWarn.String() => "WARN"
(LevelInfo+2).String() => "INFO+2"
+func (l *Level) UnmarshalJSON(data []byte) error
+ UnmarshalJSON implements encoding/json.Unmarshaler It accepts any string
+ produced by Level.MarshalJSON, ignoring case. It also accepts numeric
+ offsets that would result in a different string on output. For example,
+ "Error-8" would marshal as "INFO".
+
+func (l *Level) UnmarshalText(data []byte) error
+ UnmarshalText implements encoding.TextUnmarshaler. It accepts any string
+ produced by Level.MarshalText, ignoring case. It also accepts numeric
+ offsets that would result in a different string on output. For example,
+ "Error-8" would marshal as "INFO".
+
type LevelVar struct {
// Has unexported fields.
}
@@ -1244,11 +1289,18 @@
func (v *LevelVar) Level() Level
Level returns v's level.
+func (v *LevelVar) MarshalText() ([]byte, error)
+ MarshalText implements encoding.TextMarshaler by calling Level.MarshalText.
+
func (v *LevelVar) Set(l Level)
Set sets v's level to l.
func (v *LevelVar) String() string
+func (v *LevelVar) UnmarshalText(data []byte) error
+ UnmarshalText implements encoding.TextUnmarshaler by calling
+ Level.UnmarshalText.
+
type Leveler interface {
Level() Level
}
@@ -1296,8 +1348,8 @@
Enabled reports whether l emits log records at the given level.
func (l *Logger) Error(msg string, err error, args ...any)
- Error logs at LevelError. If err is non-nil, Error appends Any(ErrorKey,
- err) to the list of attributes.
+ Error logs at LevelError. If err is non-nil, Error adds Any(ErrorKey,
+ err) before the list of attributes.
func (l *Logger) Handler() Handler
Handler returns l's Handler.
@@ -1333,12 +1385,11 @@
Warn logs at LevelWarn.
func (l *Logger) With(args ...any) *Logger
- With returns a new Logger that includes the given arguments, converted to
- Attrs as in Logger.Log. The Attrs will be added to each output from the
- Logger. The new Logger shares the old Logger's context.
-
- The new Logger's handler is the result of calling WithAttrs on the
- receiver's handler.
+ 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. The new Logger shares the old Logger's context. The
+ new Logger's handler is the result of calling WithAttrs on the receiver's
+ handler.
func (l *Logger) WithContext(ctx context.Context) *Logger
WithContext returns a new Logger with the same handler as the receiver and
@@ -1367,26 +1418,33 @@
// Canceling the context should not affect record processing.
Context context.Context
+ // The program counter at the time the record was constructed, as determined
+ // by runtime.Callers. If zero, no program counter is available.
+ //
+ // The only valid use for this value is as an argument to
+ // [runtime.CallersFrames]. In particular, it must not be passed to
+ // [runtime.FuncForPC].
+ PC uintptr
+
// Has unexported fields.
}
A Record holds information about a log event. Copies of a Record share
state. Do not modify a Record after handing out a copy to it. Use
Record.Clone to create a copy with no shared state.
-func NewRecord(t time.Time, level Level, msg string, calldepth int, ctx context.Context) Record
- NewRecord creates a Record from the given arguments. Use Record.AddAttrs
- to add attributes to the Record. If calldepth is greater than zero,
- Record.SourceLine will return the file and line number at that depth,
- where 1 means the caller of NewRecord.
+func NewRecord(t time.Time, level Level, msg string, pc uintptr, ctx context.Context) Record
+ NewRecord creates a Record from the given arguments. Use Record.AddAttrs to
+ add attributes to the Record.
NewRecord is intended for logging APIs that want to support a Handler as a
backend.
func (r *Record) AddAttrs(attrs ...Attr)
- AddAttrs appends the given attrs to the Record's list of Attrs.
+ AddAttrs appends the given Attrs to the Record's list of Attrs. It resolves
+ the Attrs before doing so.
func (r Record) Attrs(f func(Attr))
- Attrs calls f on each Attr in the Record.
+ Attrs calls f on each Attr in the Record. The Attrs are already resolved.
func (r Record) Clone() Record
Clone returns a copy of the record with no shared state. The original record
@@ -1395,11 +1453,6 @@
func (r Record) NumAttrs() int
NumAttrs returns the number of attributes in the Record.
-func (r Record) SourceLine() (file string, line int)
- SourceLine returns the file and line of the log event. If the Record
- was created without the necessary information, or if the location is
- unavailable, it returns ("", 0).
-
type TextHandler struct {
// Has unexported fields.
}
@@ -1410,7 +1463,7 @@
NewTextHandler creates a TextHandler that writes to w, using the default
options.
-func (h *TextHandler) Enabled(level Level) bool
+func (h *TextHandler) Enabled(_ context.Context, level Level) bool
Enabled reports whether the handler handles records at the given level.
The handler ignores records whose level is lower.
@@ -1446,8 +1499,8 @@
Each call to Handle results in a single serialized call to io.Writer.Write.
func (h *TextHandler) WithAttrs(attrs []Attr) Handler
- With returns a new TextHandler whose attributes consists of h's attributes
- followed by attrs.
+ WithAttrs returns a new TextHandler whose attributes consists of h's
+ attributes followed by attrs.
func (h *TextHandler) WithGroup(name string) Handler
@@ -1467,10 +1520,10 @@
or Float64. The width of the original numeric type is not preserved.
Given a time.Time or time.Duration value, AnyValue returns a Value of kind
- TimeKind or DurationKind. The monotonic time is not preserved.
+ KindTime or KindDuration. The monotonic time is not preserved.
For nil, or values of all other types, including named types whose
- underlying type is numeric, AnyValue returns a value of kind AnyKind.
+ underlying type is numeric, AnyValue returns a value of kind KindAny.
func BoolValue(v bool) Value
BoolValue returns a Value for a bool.
@@ -1492,7 +1545,7 @@
IntValue returns a Value for an int.
func StringValue(value string) Value
- String returns a new Value for a string.
+ StringValue returns a new Value for a string.
func TimeValue(v time.Time) Value
TimeValue returns a Value for a time.Time. It discards the monotonic
@@ -1518,7 +1571,7 @@
Float64 returns v's value as a float64. It panics if v is not a float64.
func (v Value) Group() []Attr
- Group returns v's value as a []Attr. It panics if v's Kind is not GroupKind.
+ Group returns v's value as a []Attr. It panics if v's Kind is not KindGroup.
func (v Value) Int64() int64
Int64 returns v's value as an int64. It panics if v is not a signed integer.
@@ -1532,9 +1585,10 @@
func (v Value) Resolve() Value
Resolve repeatedly calls LogValue on v while it implements LogValuer, and
- returns the result. If the number of LogValue calls exceeds a threshold, a
+ returns the result. If v resolves to a group, the group's attributes' values
+ are also resolved. If the number of LogValue calls exceeds a threshold, a
Value containing an error is returned. Resolve's return value is guaranteed
- not to be of Kind LogValuerKind.
+ not to be of Kind KindLogValuer.
func (v Value) String() string
String returns Value's value as a string, formatted like fmt.Sprint.