func (s *handleState) appendKey(key string) {
s.buf.WriteString(s.sep)
if s.prefix != nil && len(*s.prefix) > 0 {
- // TODO: optimize by avoiding allocation.
- s.appendString(string(*s.prefix) + key)
+ s.appendTwoStrings(string(*s.prefix), key)
} else {
s.appendString(key)
}
s.sep = s.h.attrSep()
}
+// appendTwoStrings implements appendString(prefix + key), but faster.
+func (s *handleState) appendTwoStrings(x, y string) {
+ buf := *s.buf
+ switch {
+ case s.h.json:
+ buf.WriteByte('"')
+ buf = appendEscapedJSONString(buf, x)
+ buf = appendEscapedJSONString(buf, y)
+ buf.WriteByte('"')
+ case !needsQuoting(x) && !needsQuoting(y):
+ buf.WriteString(x)
+ buf.WriteString(y)
+ default:
+ buf = strconv.AppendQuote(buf, x+y)
+ }
+ *s.buf = buf
+}
+
func (s *handleState) appendString(str string) {
if s.h.json {
s.buf.WriteByte('"')
"bytes"
"context"
"encoding/json"
+ "fmt"
"io"
+ "log/slog/internal/buffer"
"os"
"path/filepath"
"slices"
wantText: "name.first=Perry name.last=Platypus",
wantJSON: `{"name":{"first":"Perry","last":"Platypus"}}`,
},
+ {
+ name: "group and key (or both) needs quoting",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{
+ Group("prefix",
+ String(" needs quoting ", "v"), String("NotNeedsQuoting", "v"),
+ ),
+ Group("prefix needs quoting",
+ String(" needs quoting ", "v"), String("NotNeedsQuoting", "v"),
+ ),
+ },
+ wantText: `msg=message "prefix. needs quoting "=v prefix.NotNeedsQuoting=v "prefix needs quoting. needs quoting "=v "prefix needs quoting.NotNeedsQuoting"=v`,
+ wantJSON: `{"msg":"message","prefix":{" needs quoting ":"v","NotNeedsQuoting":"v"},"prefix needs quoting":{" needs quoting ":"v","NotNeedsQuoting":"v"}}`,
+ },
} {
r := NewRecord(testTime, LevelInfo, "message", callerPC(2))
line := strconv.Itoa(r.source().Line)
l.Info("info", "a", []Attr{Int("i", 1)})
l.Info("info", "a", GroupValue(Int("i", 1)))
}
+
+func BenchmarkAppendKey(b *testing.B) {
+ for _, size := range []int{5, 10, 30, 50, 100} {
+ for _, quoting := range []string{"no_quoting", "pre_quoting", "key_quoting", "both_quoting"} {
+ b.Run(fmt.Sprintf("%s_prefix_size_%d", quoting, size), func(b *testing.B) {
+ var (
+ hs = NewJSONHandler(io.Discard, nil).newHandleState(buffer.New(), false, "")
+ prefix = bytes.Repeat([]byte("x"), size)
+ key = "key"
+ )
+
+ if quoting == "pre_quoting" || quoting == "both_quoting" {
+ prefix[0] = '"'
+ }
+ if quoting == "key_quoting" || quoting == "both_quoting" {
+ key = "ke\""
+ }
+
+ hs.prefix = (*buffer.Buffer)(&prefix)
+
+ for b.Loop() {
+ hs.appendKey(key)
+ hs.buf.Reset()
+ }
+ })
+ }
+ }
+}