json_handler.go raw

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