mirror of
https://github.com/golang/go
synced 2024-11-17 06:24:48 -07:00
log/slog: require entire Attr to be empty to elide
Specify that Handlers should ignore zero-valued Attrs. Implement that policy in the built-in handlers. Fixes #59282. Change-Id: I4430686b61f49bdac849ee300daaabfac9895849 Reviewed-on: https://go-review.googlesource.com/c/go/+/484095 Run-TryBot: Jonathan Amsterdam <jba@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
b3a194aad6
commit
d528f72d4c
@ -83,6 +83,8 @@ func (a Attr) String() string {
|
|||||||
return fmt.Sprintf("%s=%s", a.Key, a.Value)
|
return fmt.Sprintf("%s=%s", a.Key, a.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isEmpty reports whether a has an empty key and a nil value.
|
||||||
|
// That can be written as Attr{} or Any("", nil).
|
||||||
func (a Attr) isEmpty() bool {
|
func (a Attr) isEmpty() bool {
|
||||||
return a.Key == "" && a.Value.num == 0 && a.Value.any == nil
|
return a.Key == "" && a.Value.num == 0 && a.Value.any == nil
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ func Example_wrapping() {
|
|||||||
replace := func(groups []string, a slog.Attr) slog.Attr {
|
replace := func(groups []string, a slog.Attr) slog.Attr {
|
||||||
// Remove time.
|
// Remove time.
|
||||||
if a.Key == slog.TimeKey && len(groups) == 0 {
|
if a.Key == slog.TimeKey && len(groups) == 0 {
|
||||||
a.Key = ""
|
return slog.Attr{}
|
||||||
}
|
}
|
||||||
// Remove the directory from the source's filename.
|
// Remove the directory from the source's filename.
|
||||||
if a.Key == slog.SourceKey {
|
if a.Key == slog.SourceKey {
|
||||||
|
@ -50,8 +50,8 @@ type Handler interface {
|
|||||||
// Handle methods that produce output should observe the following rules:
|
// Handle methods that produce output should observe the following rules:
|
||||||
// - If r.Time is the zero time, ignore the time.
|
// - If r.Time is the zero time, ignore the time.
|
||||||
// - If r.PC is zero, ignore it.
|
// - If r.PC is zero, ignore it.
|
||||||
// - If an Attr's key is the empty string and the value is not a group,
|
// - If an Attr's key and value are both the zero value, ignore the Attr.
|
||||||
// ignore the Attr.
|
// This can be tested with attr.Equal(Attr{}).
|
||||||
// - If a group's key is empty, inline the group's Attrs.
|
// - 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),
|
// - If a group has no Attrs (even if it has a non-empty key),
|
||||||
// ignore it.
|
// ignore it.
|
||||||
@ -437,26 +437,22 @@ func (s *handleState) closeGroup(name string) {
|
|||||||
// It handles replacement and checking for an empty key.
|
// It handles replacement and checking for an empty key.
|
||||||
// after replacement).
|
// after replacement).
|
||||||
func (s *handleState) appendAttr(a Attr) {
|
func (s *handleState) appendAttr(a Attr) {
|
||||||
v := a.Value
|
if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != KindGroup {
|
||||||
// Elide a non-group with an empty key.
|
|
||||||
if a.Key == "" && v.Kind() != KindGroup {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if rep := s.h.opts.ReplaceAttr; rep != nil && v.Kind() != KindGroup {
|
|
||||||
var gs []string
|
var gs []string
|
||||||
if s.groups != nil {
|
if s.groups != nil {
|
||||||
gs = *s.groups
|
gs = *s.groups
|
||||||
}
|
}
|
||||||
a = rep(gs, Attr{a.Key, v})
|
a = rep(gs, a)
|
||||||
if a.Key == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Although all attributes in the Record are already resolved,
|
// Although all attributes in the Record are already resolved,
|
||||||
// This one came from the user, so it may not have been.
|
// This one came from the user, so it may not have been.
|
||||||
v = a.Value.Resolve()
|
a.Value = a.Value.Resolve()
|
||||||
}
|
}
|
||||||
if v.Kind() == KindGroup {
|
// Elide empty Attrs.
|
||||||
attrs := v.Group()
|
if a.isEmpty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if a.Value.Kind() == KindGroup {
|
||||||
|
attrs := a.Value.Group()
|
||||||
// Output only non-empty groups.
|
// Output only non-empty groups.
|
||||||
if len(attrs) > 0 {
|
if len(attrs) > 0 {
|
||||||
// Inline a group with an empty key.
|
// Inline a group with an empty key.
|
||||||
@ -472,7 +468,7 @@ func (s *handleState) appendAttr(a Attr) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.appendKey(a.Key)
|
s.appendKey(a.Key)
|
||||||
s.appendValue(v)
|
s.appendValue(a.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,12 +108,10 @@ func TestDefaultHandle(t *testing.T) {
|
|||||||
func TestJSONAndTextHandlers(t *testing.T) {
|
func TestJSONAndTextHandlers(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// ReplaceAttr functions
|
|
||||||
|
|
||||||
// remove all Attrs
|
// remove all Attrs
|
||||||
removeAll := func(_ []string, a Attr) Attr { return Attr{} }
|
removeAll := func(_ []string, a Attr) Attr { return Attr{} }
|
||||||
|
|
||||||
attrs := []Attr{String("a", "one"), Int("b", 2), Any("", "ignore me")}
|
attrs := []Attr{String("a", "one"), Int("b", 2), Any("", nil)}
|
||||||
preAttrs := []Attr{Int("pre", 3), String("x", "y")}
|
preAttrs := []Attr{Int("pre", 3), String("x", "y")}
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
@ -131,6 +129,12 @@ func TestJSONAndTextHandlers(t *testing.T) {
|
|||||||
wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2",
|
wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2",
|
||||||
wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2}`,
|
wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "empty key",
|
||||||
|
attrs: append(slices.Clip(attrs), Any("", "v")),
|
||||||
|
wantText: `time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2 ""=v`,
|
||||||
|
wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2,"":"v"}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "cap keys",
|
name: "cap keys",
|
||||||
replace: upperCaseKey,
|
replace: upperCaseKey,
|
||||||
@ -296,7 +300,7 @@ func TestJSONAndTextHandlers(t *testing.T) {
|
|||||||
wantJSON: `{"msg":"message","a":1,"b":2,"c":3,"d":4}`,
|
wantJSON: `{"msg":"message","a":1,"b":2,"c":3,"d":4}`,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
r := NewRecord(testTime, LevelInfo, "message", 1)
|
r := NewRecord(testTime, LevelInfo, "message", 0)
|
||||||
r.AddAttrs(test.attrs...)
|
r.AddAttrs(test.attrs...)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
opts := HandlerOptions{ReplaceAttr: test.replace}
|
opts := HandlerOptions{ReplaceAttr: test.replace}
|
||||||
|
@ -12,7 +12,7 @@ import "log/slog"
|
|||||||
// to make example output deterministic.
|
// to make example output deterministic.
|
||||||
func RemoveTime(groups []string, a slog.Attr) slog.Attr {
|
func RemoveTime(groups []string, a slog.Attr) slog.Attr {
|
||||||
if a.Key == slog.TimeKey && len(groups) == 0 {
|
if a.Key == slog.TimeKey && len(groups) == 0 {
|
||||||
a.Key = ""
|
return slog.Attr{}
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
@ -138,6 +138,9 @@ func byteSlice(a any) ([]byte, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func needsQuoting(s string) bool {
|
func needsQuoting(s string) bool {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
b := s[i]
|
b := s[i]
|
||||||
if b < utf8.RuneSelf {
|
if b < utf8.RuneSelf {
|
||||||
|
@ -185,7 +185,7 @@ func TestNeedsQuoting(t *testing.T) {
|
|||||||
in string
|
in string
|
||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
{"", false},
|
{"", true},
|
||||||
{"ab", false},
|
{"ab", false},
|
||||||
{"a=b", true},
|
{"a=b", true},
|
||||||
{`"ab"`, true},
|
{`"ab"`, true},
|
||||||
|
@ -185,7 +185,6 @@ func TestLogValue(t *testing.T) {
|
|||||||
if !attrsEqual(got2, want2) {
|
if !attrsEqual(got2, want2) {
|
||||||
t.Errorf("got %v, want %v", got2, want2)
|
t.Errorf("got %v, want %v", got2, want2)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZeroTime(t *testing.T) {
|
func TestZeroTime(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user