Source file
src/log/slog/json_handler.go
1
2
3
4
5 package slog
6
7 import (
8 "bytes"
9 "context"
10 "encoding/json"
11 "errors"
12 "fmt"
13 "internal/goexperiment"
14 "io"
15 "log/slog/internal/buffer"
16 "strconv"
17 "sync"
18 "time"
19 "unicode/utf8"
20 )
21
22
23
24 type JSONHandler struct {
25 *commonHandler
26 }
27
28
29
30
31 func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler {
32 if opts == nil {
33 opts = &HandlerOptions{}
34 }
35 return &JSONHandler{
36 &commonHandler{
37 json: true,
38 w: w,
39 opts: *opts,
40 mu: &sync.Mutex{},
41 },
42 }
43 }
44
45
46
47 func (h *JSONHandler) Enabled(_ context.Context, level Level) bool {
48 return h.commonHandler.enabled(level)
49 }
50
51
52
53 func (h *JSONHandler) WithAttrs(attrs []Attr) Handler {
54 return &JSONHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
55 }
56
57 func (h *JSONHandler) WithGroup(name string) Handler {
58 return &JSONHandler{commonHandler: h.commonHandler.withGroup(name)}
59 }
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 func (h *JSONHandler) Handle(_ context.Context, r Record) error {
90 return h.commonHandler.handle(r)
91 }
92
93
94 func appendJSONTime(s *handleState, t time.Time) {
95 if y := t.Year(); y < 0 || y >= 10000 {
96
97
98 s.appendError(errors.New("time.Time year outside of range [0,9999]"))
99 return
100 }
101 s.buf.WriteByte('"')
102 *s.buf = t.AppendFormat(*s.buf, time.RFC3339Nano)
103 s.buf.WriteByte('"')
104 }
105
106 func appendJSONValue(s *handleState, v Value) error {
107 switch v.Kind() {
108 case KindString:
109 s.appendString(v.str())
110 case KindInt64:
111 *s.buf = strconv.AppendInt(*s.buf, v.Int64(), 10)
112 case KindUint64:
113 *s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10)
114 case KindFloat64:
115
116
117
118 if err := appendJSONMarshal(s.buf, v.Float64()); err != nil {
119 return err
120 }
121 case KindBool:
122 *s.buf = strconv.AppendBool(*s.buf, v.Bool())
123 case KindDuration:
124
125 *s.buf = strconv.AppendInt(*s.buf, int64(v.Duration()), 10)
126 case KindTime:
127 s.appendTime(v.Time())
128 case KindAny:
129 a := v.Any()
130 _, jm := a.(json.Marshaler)
131 if err, ok := a.(error); ok && !jm {
132 s.appendString(err.Error())
133 } else {
134 return appendJSONMarshal(s.buf, a)
135 }
136 default:
137 panic(fmt.Sprintf("bad kind: %s", v.Kind()))
138 }
139 return nil
140 }
141
142 type jsonEncoder struct {
143 buf *bytes.Buffer
144
145 json *json.Encoder
146 }
147
148 var jsonEncoderPool = &sync.Pool{
149 New: func() any {
150 enc := &jsonEncoder{
151 buf: new(bytes.Buffer),
152 }
153 enc.json = json.NewEncoder(enc.buf)
154 enc.json.SetEscapeHTML(false)
155 return enc
156 },
157 }
158
159 func appendJSONMarshal(buf *buffer.Buffer, v any) error {
160 j := jsonEncoderPool.Get().(*jsonEncoder)
161 defer func() {
162
163 const maxBufferSize = 16 << 10
164 if j.buf.Cap() > maxBufferSize {
165 return
166 }
167 j.buf.Reset()
168 jsonEncoderPool.Put(j)
169 }()
170
171 if err := j.json.Encode(v); err != nil {
172 return err
173 }
174
175 bs := j.buf.Bytes()
176 buf.Write(bs[:len(bs)-1])
177 return nil
178 }
179
180
181
182
183
184
185 func appendEscapedJSONString(buf []byte, s string) []byte {
186 char := func(b byte) { buf = append(buf, b) }
187 str := func(s string) { buf = append(buf, s...) }
188
189 start := 0
190 for i := 0; i < len(s); {
191 if b := s[i]; b < utf8.RuneSelf {
192 if safeSet[b] {
193 i++
194 continue
195 }
196 if start < i {
197 str(s[start:i])
198 }
199 char('\\')
200 switch b {
201 case '\\', '"':
202 char(b)
203 case '\n':
204 char('n')
205 case '\r':
206 char('r')
207 case '\t':
208 char('t')
209 default:
210
211 str(`u00`)
212 char(hex[b>>4])
213 char(hex[b&0xF])
214 }
215 i++
216 start = i
217 continue
218 }
219 c, size := utf8.DecodeRuneInString(s[i:])
220 if c == utf8.RuneError && size == 1 {
221 if start < i {
222 str(s[start:i])
223 }
224 if goexperiment.JSONv2 {
225 str("\ufffd")
226 } else {
227 str(`\ufffd`)
228 }
229 i += size
230 start = i
231 continue
232 }
233
234
235
236
237
238
239
240 if c == '\u2028' || c == '\u2029' {
241 if start < i {
242 str(s[start:i])
243 }
244 str(`\u202`)
245 char(hex[c&0xF])
246 i += size
247 start = i
248 continue
249 }
250 i += size
251 }
252 if start < len(s) {
253 str(s[start:])
254 }
255 return buf
256 }
257
258 const hex = "0123456789abcdef"
259
260
261
262
263
264
265
266
267
268 var safeSet = [utf8.RuneSelf]bool{
269 ' ': true,
270 '!': true,
271 '"': false,
272 '#': true,
273 '$': true,
274 '%': true,
275 '&': true,
276 '\'': true,
277 '(': true,
278 ')': true,
279 '*': true,
280 '+': true,
281 ',': true,
282 '-': true,
283 '.': true,
284 '/': true,
285 '0': true,
286 '1': true,
287 '2': true,
288 '3': true,
289 '4': true,
290 '5': true,
291 '6': true,
292 '7': true,
293 '8': true,
294 '9': true,
295 ':': true,
296 ';': true,
297 '<': true,
298 '=': true,
299 '>': true,
300 '?': true,
301 '@': true,
302 'A': true,
303 'B': true,
304 'C': true,
305 'D': true,
306 'E': true,
307 'F': true,
308 'G': true,
309 'H': true,
310 'I': true,
311 'J': true,
312 'K': true,
313 'L': true,
314 'M': true,
315 'N': true,
316 'O': true,
317 'P': true,
318 'Q': true,
319 'R': true,
320 'S': true,
321 'T': true,
322 'U': true,
323 'V': true,
324 'W': true,
325 'X': true,
326 'Y': true,
327 'Z': true,
328 '[': true,
329 '\\': false,
330 ']': true,
331 '^': true,
332 '_': true,
333 '`': true,
334 'a': true,
335 'b': true,
336 'c': true,
337 'd': true,
338 'e': true,
339 'f': true,
340 'g': true,
341 'h': true,
342 'i': true,
343 'j': true,
344 'k': true,
345 'l': true,
346 'm': true,
347 'n': true,
348 'o': true,
349 'p': true,
350 'q': true,
351 'r': true,
352 's': true,
353 't': true,
354 'u': true,
355 'v': true,
356 'w': true,
357 'x': true,
358 'y': true,
359 'z': true,
360 '{': true,
361 '|': true,
362 '}': true,
363 '~': true,
364 '\u007f': true,
365 }
366
View as plain text