github.com/AndrienkoAleksandr/go@v0.0.19/src/log/slog/json_handler.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package slog 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "io" 14 "log/slog/internal/buffer" 15 "strconv" 16 "time" 17 "unicode/utf8" 18 ) 19 20 // JSONHandler is a Handler that writes Records to an io.Writer as 21 // line-delimited JSON objects. 22 type JSONHandler struct { 23 *commonHandler 24 } 25 26 // NewJSONHandler creates a JSONHandler that writes to w, 27 // using the given options. 28 // If opts is nil, the default options are used. 29 func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler { 30 if opts == nil { 31 opts = &HandlerOptions{} 32 } 33 return &JSONHandler{ 34 &commonHandler{ 35 json: true, 36 w: w, 37 opts: *opts, 38 }, 39 } 40 } 41 42 // Enabled reports whether the handler handles records at the given level. 43 // The handler ignores records whose level is lower. 44 func (h *JSONHandler) Enabled(_ context.Context, level Level) bool { 45 return h.commonHandler.enabled(level) 46 } 47 48 // WithAttrs returns a new JSONHandler whose attributes consists 49 // of h's attributes followed by attrs. 50 func (h *JSONHandler) WithAttrs(attrs []Attr) Handler { 51 return &JSONHandler{commonHandler: h.commonHandler.withAttrs(attrs)} 52 } 53 54 func (h *JSONHandler) WithGroup(name string) Handler { 55 return &JSONHandler{commonHandler: h.commonHandler.withGroup(name)} 56 } 57 58 // Handle formats its argument Record as a JSON object on a single line. 59 // 60 // If the Record's time is zero, the time is omitted. 61 // Otherwise, the key is "time" 62 // and the value is output as with json.Marshal. 63 // 64 // If the Record's level is zero, the level is omitted. 65 // Otherwise, the key is "level" 66 // and the value of [Level.String] is output. 67 // 68 // If the AddSource option is set and source information is available, 69 // the key is "source", and the value is a record of type [Source]. 70 // 71 // The message's key is "msg". 72 // 73 // To modify these or other attributes, or remove them from the output, use 74 // [HandlerOptions.ReplaceAttr]. 75 // 76 // Values are formatted as with an [encoding/json.Encoder] with SetEscapeHTML(false), 77 // with two exceptions. 78 // 79 // First, an Attr whose Value is of type error is formatted as a string, by 80 // calling its Error method. Only errors in Attrs receive this special treatment, 81 // not errors embedded in structs, slices, maps or other data structures that 82 // are processed by the encoding/json package. 83 // 84 // Second, an encoding failure does not cause Handle to return an error. 85 // Instead, the error message is formatted as a string. 86 // 87 // Each call to Handle results in a single serialized call to io.Writer.Write. 88 func (h *JSONHandler) Handle(_ context.Context, r Record) error { 89 return h.commonHandler.handle(r) 90 } 91 92 // Adapted from time.Time.MarshalJSON to avoid allocation. 93 func appendJSONTime(s *handleState, t time.Time) { 94 if y := t.Year(); y < 0 || y >= 10000 { 95 // RFC 3339 is clear that years are 4 digits exactly. 96 // See golang.org/issue/4556#c15 for more discussion. 97 s.appendError(errors.New("time.Time year outside of range [0,9999]")) 98 } 99 s.buf.WriteByte('"') 100 *s.buf = t.AppendFormat(*s.buf, time.RFC3339Nano) 101 s.buf.WriteByte('"') 102 } 103 104 func appendJSONValue(s *handleState, v Value) error { 105 switch v.Kind() { 106 case KindString: 107 s.appendString(v.str()) 108 case KindInt64: 109 *s.buf = strconv.AppendInt(*s.buf, v.Int64(), 10) 110 case KindUint64: 111 *s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10) 112 case KindFloat64: 113 // json.Marshal is funny about floats; it doesn't 114 // always match strconv.AppendFloat. So just call it. 115 // That's expensive, but floats are rare. 116 if err := appendJSONMarshal(s.buf, v.Float64()); err != nil { 117 return err 118 } 119 case KindBool: 120 *s.buf = strconv.AppendBool(*s.buf, v.Bool()) 121 case KindDuration: 122 // Do what json.Marshal does. 123 *s.buf = strconv.AppendInt(*s.buf, int64(v.Duration()), 10) 124 case KindTime: 125 s.appendTime(v.Time()) 126 case KindAny: 127 a := v.Any() 128 _, jm := a.(json.Marshaler) 129 if err, ok := a.(error); ok && !jm { 130 s.appendString(err.Error()) 131 } else { 132 return appendJSONMarshal(s.buf, a) 133 } 134 default: 135 panic(fmt.Sprintf("bad kind: %s", v.Kind())) 136 } 137 return nil 138 } 139 140 func appendJSONMarshal(buf *buffer.Buffer, v any) error { 141 // Use a json.Encoder to avoid escaping HTML. 142 var bb bytes.Buffer 143 enc := json.NewEncoder(&bb) 144 enc.SetEscapeHTML(false) 145 if err := enc.Encode(v); err != nil { 146 return err 147 } 148 bs := bb.Bytes() 149 buf.Write(bs[:len(bs)-1]) // remove final newline 150 return nil 151 } 152 153 // appendEscapedJSONString escapes s for JSON and appends it to buf. 154 // It does not surround the string in quotation marks. 155 // 156 // Modified from encoding/json/encode.go:encodeState.string, 157 // with escapeHTML set to false. 158 func appendEscapedJSONString(buf []byte, s string) []byte { 159 char := func(b byte) { buf = append(buf, b) } 160 str := func(s string) { buf = append(buf, s...) } 161 162 start := 0 163 for i := 0; i < len(s); { 164 if b := s[i]; b < utf8.RuneSelf { 165 if safeSet[b] { 166 i++ 167 continue 168 } 169 if start < i { 170 str(s[start:i]) 171 } 172 char('\\') 173 switch b { 174 case '\\', '"': 175 char(b) 176 case '\n': 177 char('n') 178 case '\r': 179 char('r') 180 case '\t': 181 char('t') 182 default: 183 // This encodes bytes < 0x20 except for \t, \n and \r. 184 str(`u00`) 185 char(hex[b>>4]) 186 char(hex[b&0xF]) 187 } 188 i++ 189 start = i 190 continue 191 } 192 c, size := utf8.DecodeRuneInString(s[i:]) 193 if c == utf8.RuneError && size == 1 { 194 if start < i { 195 str(s[start:i]) 196 } 197 str(`\ufffd`) 198 i += size 199 start = i 200 continue 201 } 202 // U+2028 is LINE SEPARATOR. 203 // U+2029 is PARAGRAPH SEPARATOR. 204 // They are both technically valid characters in JSON strings, 205 // but don't work in JSONP, which has to be evaluated as JavaScript, 206 // and can lead to security holes there. It is valid JSON to 207 // escape them, so we do so unconditionally. 208 // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. 209 if c == '\u2028' || c == '\u2029' { 210 if start < i { 211 str(s[start:i]) 212 } 213 str(`\u202`) 214 char(hex[c&0xF]) 215 i += size 216 start = i 217 continue 218 } 219 i += size 220 } 221 if start < len(s) { 222 str(s[start:]) 223 } 224 return buf 225 } 226 227 var hex = "0123456789abcdef" 228 229 // Copied from encoding/json/tables.go. 230 // 231 // safeSet holds the value true if the ASCII character with the given array 232 // position can be represented inside a JSON string without any further 233 // escaping. 234 // 235 // All values are true except for the ASCII control characters (0-31), the 236 // double quote ("), and the backslash character ("\"). 237 var safeSet = [utf8.RuneSelf]bool{ 238 ' ': true, 239 '!': true, 240 '"': false, 241 '#': true, 242 '$': true, 243 '%': true, 244 '&': true, 245 '\'': true, 246 '(': true, 247 ')': true, 248 '*': true, 249 '+': true, 250 ',': true, 251 '-': true, 252 '.': true, 253 '/': true, 254 '0': true, 255 '1': true, 256 '2': true, 257 '3': true, 258 '4': true, 259 '5': true, 260 '6': true, 261 '7': true, 262 '8': true, 263 '9': true, 264 ':': true, 265 ';': true, 266 '<': true, 267 '=': true, 268 '>': true, 269 '?': true, 270 '@': true, 271 'A': true, 272 'B': true, 273 'C': true, 274 'D': true, 275 'E': true, 276 'F': true, 277 'G': true, 278 'H': true, 279 'I': true, 280 'J': true, 281 'K': true, 282 'L': true, 283 'M': true, 284 'N': true, 285 'O': true, 286 'P': true, 287 'Q': true, 288 'R': true, 289 'S': true, 290 'T': true, 291 'U': true, 292 'V': true, 293 'W': true, 294 'X': true, 295 'Y': true, 296 'Z': true, 297 '[': true, 298 '\\': false, 299 ']': true, 300 '^': true, 301 '_': true, 302 '`': true, 303 'a': true, 304 'b': true, 305 'c': true, 306 'd': true, 307 'e': true, 308 'f': true, 309 'g': true, 310 'h': true, 311 'i': true, 312 'j': true, 313 'k': true, 314 'l': true, 315 'm': true, 316 'n': true, 317 'o': true, 318 'p': true, 319 'q': true, 320 'r': true, 321 's': true, 322 't': true, 323 'u': true, 324 'v': true, 325 'w': true, 326 'x': true, 327 'y': true, 328 'z': true, 329 '{': true, 330 '|': true, 331 '}': true, 332 '~': true, 333 '\u007f': true, 334 }