slog: TextHandler quotes []byte
Make TextHandler's output nicer for []byte values: quote them,
like strings, even if they contain binary.
Change-Id: I757823ed7ce7aff3e25b889de925e6252324d9d9
Reviewed-on: https://go-review.googlesource.com/c/exp/+/453495
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/slog/handler_test.go b/slog/handler_test.go
index 9141a41..be4a925 100644
--- a/slog/handler_test.go
+++ b/slog/handler_test.go
@@ -8,6 +8,7 @@
import (
"bytes"
+ "encoding/json"
"io"
"strings"
"testing"
@@ -267,6 +268,20 @@
wantText: "msg=message v=3",
wantJSON: `{"msg":"message","v":3}`,
},
+ {
+ name: "byte slice",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{Any("bs", []byte{1, 2, 3, 4})},
+ wantText: `msg=message bs="\x01\x02\x03\x04"`,
+ wantJSON: `{"msg":"message","bs":"AQIDBA=="}`,
+ },
+ {
+ name: "json.RawMessage",
+ replace: removeKeys(TimeKey, LevelKey),
+ attrs: []Attr{Any("bs", json.RawMessage([]byte("1234")))},
+ wantText: `msg=message bs="1234"`,
+ wantJSON: `{"msg":"message","bs":1234}`,
+ },
} {
r := NewRecord(testTime, InfoLevel, "message", 1, nil)
r.AddAttrs(test.attrs...)
diff --git a/slog/text_handler.go b/slog/text_handler.go
index b4622e5..191da44 100644
--- a/slog/text_handler.go
+++ b/slog/text_handler.go
@@ -8,6 +8,8 @@
"encoding"
"fmt"
"io"
+ "reflect"
+ "strconv"
"unicode"
"unicode/utf8"
)
@@ -103,6 +105,11 @@
s.appendString(string(data))
return nil
}
+ if bs, ok := byteSlice(v.any); ok {
+ // As of Go 1.19, this only allocates for strings longer than 32 bytes.
+ s.buf.WriteString(strconv.Quote(string(bs)))
+ return nil
+ }
s.appendString(fmt.Sprint(v.Any()))
default:
*s.buf = v.append(*s.buf)
@@ -110,6 +117,21 @@
return nil
}
+// byteSlice returns its argument as a []byte if the argument's
+// underlying type is []byte, along with a second return value of true.
+// Otherwise it returns nil, false.
+func byteSlice(a any) ([]byte, bool) {
+ if bs, ok := a.([]byte); ok {
+ return bs, true
+ }
+ // Like Printf's %s, we allow both the slice type and the byte element type to be named.
+ t := reflect.TypeOf(a)
+ if t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
+ return reflect.ValueOf(a).Bytes(), true
+ }
+ return nil, false
+}
+
func needsQuoting(s string) bool {
for i := 0; i < len(s); {
b := s[i]