Skip to content

Commit 5c715a9

Browse files
committed
slog: TextHandler checks long strings, quotes non-printing runes
TextHandler checks the full length of all strings for whether they need to be quoted, because the cost of doing so (when done quickly) is significantly less than the cost of quoting. Also, it quotes strings containing non-printing characters. Change-Id: I2bb3a66f4fc1f60b0fc58acc53a2f5b973522cc7 Reviewed-on: https://go-review.googlesource.com/c/exp/+/429838 Reviewed-by: Alan Donovan <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]>
1 parent 93e26a7 commit 5c715a9

File tree

2 files changed

+56
-12
lines changed

2 files changed

+56
-12
lines changed

slog/text_handler.go

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"fmt"
1010
"io"
1111
"strconv"
12-
"strings"
1312
"time"
1413
"unicode"
14+
"unicode/utf8"
1515

1616
"golang.org/x/exp/slog/internal/buffer"
1717
)
@@ -69,8 +69,8 @@ func (h *TextHandler) With(attrs []Attr) Handler {
6969
// If a value implements [encoding.TextMarshaler], the result of MarshalText is
7070
// written. Otherwise, the result of fmt.Sprint is written.
7171
//
72-
// Keys and values are quoted if they are long or contain Unicode space
73-
// characters, '"' or '='.
72+
// Keys and values are quoted if they contain Unicode space characters,
73+
// non-printing characters, '"' or '='.
7474
//
7575
// Each call to Handle results in a single serialized call to
7676
// io.Writer.Write.
@@ -95,19 +95,10 @@ func (a *textAppender) appendString(s string) {
9595
}
9696
}
9797

98-
func needsQuoting(s string) bool {
99-
return len(s) > maxCheckQuoteSize ||
100-
strings.IndexFunc(s, func(r rune) bool {
101-
return unicode.IsSpace(r) || r == '"' || r == '='
102-
}) >= 0
103-
}
104-
10598
func (a *textAppender) appendStart() {}
10699
func (a *textAppender) appendEnd() {}
107100
func (a *textAppender) appendSep() { a.buf().WriteByte(' ') }
108101

109-
const maxCheckQuoteSize = 80
110-
111102
func (a *textAppender) appendTime(t time.Time) error {
112103
*a.buf() = appendTimeRFC3339Millis(*a.buf(), t)
113104
return nil
@@ -144,3 +135,36 @@ func (ap *textAppender) appendAttrValue(a Attr) error {
144135
}
145136
return nil
146137
}
138+
139+
func needsQuoting(s string) bool {
140+
for i := 0; i < len(s); {
141+
b := s[i]
142+
if b < utf8.RuneSelf {
143+
if needsQuotingSet[b] {
144+
return true
145+
}
146+
i++
147+
continue
148+
}
149+
r, size := utf8.DecodeRuneInString(s[i:])
150+
if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
151+
return true
152+
}
153+
i += size
154+
}
155+
return false
156+
}
157+
158+
var needsQuotingSet = [utf8.RuneSelf]bool{
159+
'"': true,
160+
'=': true,
161+
}
162+
163+
func init() {
164+
for i := 0; i < utf8.RuneSelf; i++ {
165+
r := rune(i)
166+
if unicode.IsSpace(r) || !unicode.IsPrint(r) {
167+
needsQuotingSet[i] = true
168+
}
169+
}
170+
}

slog/text_handler_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,23 @@ func TestTextHandlerAlloc(t *testing.T) {
132132
h := NewTextHandler(io.Discard)
133133
wantAllocs(t, 0, func() { h.Handle(r) })
134134
}
135+
136+
func TestNeedsQuoting(t *testing.T) {
137+
for _, test := range []struct {
138+
in string
139+
want bool
140+
}{
141+
{"", false},
142+
{"ab", false},
143+
{"a=b", true},
144+
{`"ab"`, true},
145+
{"\a\b", true},
146+
{"a\tb", true},
147+
{"µåπ", false},
148+
} {
149+
got := needsQuoting(test.in)
150+
if got != test.want {
151+
t.Errorf("%q: got %t, want %t", test.in, got, test.want)
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)