Skip to content

Commit 0022bd3

Browse files
committed
log/slog: remove special float handling from JSONHandler
Remove the special-case handling of NaN and infinities from appendJSONValue, making JSONHandler behave almost exactly like a json.Encoder without HTML escaping. The only differences are: - Encoding errors are turned into strings, instead of causing the Handle method to fail. - Values of type `error` are displayed as strings by calling their `Error` method. Fixes #59345. Change-Id: Id06bd952bbfef596e864bd5fd3f9f4f178f738c9 Reviewed-on: https://go-review.googlesource.com/c/go/+/486855 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 0212b80 commit 0022bd3

File tree

2 files changed

+26
-36
lines changed

2 files changed

+26
-36
lines changed

src/log/slog/json_handler.go

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"fmt"
1313
"io"
1414
"log/slog/internal/buffer"
15-
"math"
1615
"strconv"
1716
"time"
1817
"unicode/utf8"
@@ -75,12 +74,16 @@ func (h *JSONHandler) WithGroup(name string) Handler {
7574
// To modify these or other attributes, or remove them from the output, use
7675
// [HandlerOptions.ReplaceAttr].
7776
//
78-
// Values are formatted as with encoding/json.Marshal, with the following
79-
// exceptions:
80-
// - Floating-point NaNs and infinities are formatted as one of the strings
81-
// "NaN", "Infinity" or "-Infinity".
82-
// - Levels are formatted as with Level.String.
83-
// - HTML characters are not escaped.
77+
// Values are formatted as with an [encoding/json.Encoder] with SetEscapeHTML(false),
78+
// with two exceptions.
79+
//
80+
// First, an Attr whose Value is of type error is formatted as a string, by
81+
// calling its Error method. Only errors in Attrs receive this special treatment,
82+
// not errors embedded in structs, slices, maps or other data structures that
83+
// are processed by the encoding/json package.
84+
//
85+
// Second, an encoding failure does not cause Handle to return an error.
86+
// Instead, the error message is formatted as a string.
8487
//
8588
// Each call to Handle results in a single serialized call to io.Writer.Write.
8689
func (h *JSONHandler) Handle(_ context.Context, r Record) error {
@@ -108,22 +111,11 @@ func appendJSONValue(s *handleState, v Value) error {
108111
case KindUint64:
109112
*s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10)
110113
case KindFloat64:
111-
f := v.Float64()
112-
// json.Marshal fails on special floats, so handle them here.
113-
switch {
114-
case math.IsInf(f, 1):
115-
s.buf.WriteString(`"Infinity"`)
116-
case math.IsInf(f, -1):
117-
s.buf.WriteString(`"-Infinity"`)
118-
case math.IsNaN(f):
119-
s.buf.WriteString(`"NaN"`)
120-
default:
121-
// json.Marshal is funny about floats; it doesn't
122-
// always match strconv.AppendFloat. So just call it.
123-
// That's expensive, but floats are rare.
124-
if err := appendJSONMarshal(s.buf, f); err != nil {
125-
return err
126-
}
114+
// json.Marshal is funny about floats; it doesn't
115+
// always match strconv.AppendFloat. So just call it.
116+
// That's expensive, but floats are rare.
117+
if err := appendJSONMarshal(s.buf, v.Float64()); err != nil {
118+
return err
127119
}
128120
case KindBool:
129121
*s.buf = strconv.AppendBool(*s.buf, v.Bool())
@@ -163,9 +155,7 @@ func appendJSONMarshal(buf *buffer.Buffer, v any) error {
163155
// It does not surround the string in quotation marks.
164156
//
165157
// Modified from encoding/json/encode.go:encodeState.string,
166-
// with escapeHTML set to true.
167-
//
168-
// TODO: review whether HTML escaping is necessary.
158+
// with escapeHTML set to false.
169159
func appendEscapedJSONString(buf []byte, s string) []byte {
170160
char := func(b byte) { buf = append(buf, b) }
171161
str := func(s string) { buf = append(buf, s...) }

src/log/slog/json_handler_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ type jsonMarshalerError struct {
7474
func (jsonMarshalerError) Error() string { return "oops" }
7575

7676
func TestAppendJSONValue(t *testing.T) {
77-
// On most values, jsonAppendAttrValue should agree with json.Marshal.
77+
// jsonAppendAttrValue should always agree with json.Marshal.
7878
for _, value := range []any{
7979
"hello",
8080
`"[{escape}]"`,
@@ -89,8 +89,9 @@ func TestAppendJSONValue(t *testing.T) {
8989
testTime,
9090
jsonMarshaler{"xyz"},
9191
jsonMarshalerError{jsonMarshaler{"pqr"}},
92+
LevelWarn,
9293
} {
93-
got := jsonValueString(t, AnyValue(value))
94+
got := jsonValueString(AnyValue(value))
9495
want, err := marshalJSON(value)
9596
if err != nil {
9697
t.Fatal(err)
@@ -117,24 +118,23 @@ func TestJSONAppendAttrValueSpecial(t *testing.T) {
117118
value any
118119
want string
119120
}{
120-
{math.NaN(), `"NaN"`},
121-
{math.Inf(+1), `"Infinity"`},
122-
{math.Inf(-1), `"-Infinity"`},
123-
{LevelWarn, `"WARN"`},
121+
{math.NaN(), `"!ERROR:json: unsupported value: NaN"`},
122+
{math.Inf(+1), `"!ERROR:json: unsupported value: +Inf"`},
123+
{math.Inf(-1), `"!ERROR:json: unsupported value: -Inf"`},
124+
{io.EOF, `"EOF"`},
124125
} {
125-
got := jsonValueString(t, AnyValue(test.value))
126+
got := jsonValueString(AnyValue(test.value))
126127
if got != test.want {
127128
t.Errorf("%v: got %s, want %s", test.value, got, test.want)
128129
}
129130
}
130131
}
131132

132-
func jsonValueString(t *testing.T, v Value) string {
133-
t.Helper()
133+
func jsonValueString(v Value) string {
134134
var buf []byte
135135
s := &handleState{h: &commonHandler{json: true}, buf: (*buffer.Buffer)(&buf)}
136136
if err := appendJSONValue(s, v); err != nil {
137-
t.Fatal(err)
137+
s.appendError(err)
138138
}
139139
return string(buf)
140140
}

0 commit comments

Comments
 (0)