| | |
| | |
| | |
| |
|
| | package slog |
| |
|
| | import ( |
| | "bytes" |
| | "context" |
| | "encoding/json" |
| | "errors" |
| | "fmt" |
| | "io" |
| | "log/slog/internal/buffer" |
| | "strconv" |
| | "sync" |
| | "time" |
| | "unicode/utf8" |
| | ) |
| |
|
| | |
| | |
| | type JSONHandler struct { |
| | *commonHandler |
| | } |
| |
|
| | |
| | |
| | |
| | func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler { |
| | if opts == nil { |
| | opts = &HandlerOptions{} |
| | } |
| | return &JSONHandler{ |
| | &commonHandler{ |
| | json: true, |
| | w: w, |
| | opts: *opts, |
| | mu: &sync.Mutex{}, |
| | }, |
| | } |
| | } |
| |
|
| | |
| | |
| | func (h *JSONHandler) Enabled(_ context.Context, level Level) bool { |
| | return h.commonHandler.enabled(level) |
| | } |
| |
|
| | |
| | |
| | func (h *JSONHandler) WithAttrs(attrs []Attr) Handler { |
| | return &JSONHandler{commonHandler: h.commonHandler.withAttrs(attrs)} |
| | } |
| |
|
| | func (h *JSONHandler) WithGroup(name string) Handler { |
| | return &JSONHandler{commonHandler: h.commonHandler.withGroup(name)} |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func (h *JSONHandler) Handle(_ context.Context, r Record) error { |
| | return h.commonHandler.handle(r) |
| | } |
| |
|
| | |
| | func appendJSONTime(s *handleState, t time.Time) { |
| | if y := t.Year(); y < 0 || y >= 10000 { |
| | |
| | |
| | s.appendError(errors.New("time.Time year outside of range [0,9999]")) |
| | } |
| | s.buf.WriteByte('"') |
| | *s.buf = t.AppendFormat(*s.buf, time.RFC3339Nano) |
| | s.buf.WriteByte('"') |
| | } |
| |
|
| | func appendJSONValue(s *handleState, v Value) error { |
| | switch v.Kind() { |
| | case KindString: |
| | s.appendString(v.str()) |
| | case KindInt64: |
| | *s.buf = strconv.AppendInt(*s.buf, v.Int64(), 10) |
| | case KindUint64: |
| | *s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10) |
| | case KindFloat64: |
| | |
| | |
| | |
| | if err := appendJSONMarshal(s.buf, v.Float64()); err != nil { |
| | return err |
| | } |
| | case KindBool: |
| | *s.buf = strconv.AppendBool(*s.buf, v.Bool()) |
| | case KindDuration: |
| | |
| | *s.buf = strconv.AppendInt(*s.buf, int64(v.Duration()), 10) |
| | case KindTime: |
| | s.appendTime(v.Time()) |
| | case KindAny: |
| | a := v.Any() |
| | _, jm := a.(json.Marshaler) |
| | if err, ok := a.(error); ok && !jm { |
| | s.appendString(err.Error()) |
| | } else { |
| | return appendJSONMarshal(s.buf, a) |
| | } |
| | default: |
| | panic(fmt.Sprintf("bad kind: %s", v.Kind())) |
| | } |
| | return nil |
| | } |
| |
|
| | type jsonEncoder struct { |
| | buf *bytes.Buffer |
| | |
| | json *json.Encoder |
| | } |
| |
|
| | var jsonEncoderPool = &sync.Pool{ |
| | New: func() any { |
| | enc := &jsonEncoder{ |
| | buf: new(bytes.Buffer), |
| | } |
| | enc.json = json.NewEncoder(enc.buf) |
| | enc.json.SetEscapeHTML(false) |
| | return enc |
| | }, |
| | } |
| |
|
| | func appendJSONMarshal(buf *buffer.Buffer, v any) error { |
| | j := jsonEncoderPool.Get().(*jsonEncoder) |
| | defer func() { |
| | |
| | const maxBufferSize = 16 << 10 |
| | if j.buf.Cap() > maxBufferSize { |
| | return |
| | } |
| | j.buf.Reset() |
| | jsonEncoderPool.Put(j) |
| | }() |
| |
|
| | if err := j.json.Encode(v); err != nil { |
| | return err |
| | } |
| |
|
| | bs := j.buf.Bytes() |
| | buf.Write(bs[:len(bs)-1]) |
| | return nil |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func appendEscapedJSONString(buf []byte, s string) []byte { |
| | char := func(b byte) { buf = append(buf, b) } |
| | str := func(s string) { buf = append(buf, s...) } |
| |
|
| | start := 0 |
| | for i := 0; i < len(s); { |
| | if b := s[i]; b < utf8.RuneSelf { |
| | if safeSet[b] { |
| | i++ |
| | continue |
| | } |
| | if start < i { |
| | str(s[start:i]) |
| | } |
| | char('\\') |
| | switch b { |
| | case '\\', '"': |
| | char(b) |
| | case '\n': |
| | char('n') |
| | case '\r': |
| | char('r') |
| | case '\t': |
| | char('t') |
| | default: |
| | |
| | str(`u00`) |
| | char(hex[b>>4]) |
| | char(hex[b&0xF]) |
| | } |
| | i++ |
| | start = i |
| | continue |
| | } |
| | c, size := utf8.DecodeRuneInString(s[i:]) |
| | if c == utf8.RuneError && size == 1 { |
| | if start < i { |
| | str(s[start:i]) |
| | } |
| | str(`\ufffd`) |
| | i += size |
| | start = i |
| | continue |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if c == '\u2028' || c == '\u2029' { |
| | if start < i { |
| | str(s[start:i]) |
| | } |
| | str(`\u202`) |
| | char(hex[c&0xF]) |
| | i += size |
| | start = i |
| | continue |
| | } |
| | i += size |
| | } |
| | if start < len(s) { |
| | str(s[start:]) |
| | } |
| | return buf |
| | } |
| |
|
| | const hex = "0123456789abcdef" |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | var safeSet = [utf8.RuneSelf]bool{ |
| | ' ': true, |
| | '!': true, |
| | '"': false, |
| | '#': true, |
| | '$': true, |
| | '%': true, |
| | '&': true, |
| | '\'': true, |
| | '(': true, |
| | ')': true, |
| | '*': true, |
| | '+': true, |
| | ',': true, |
| | '-': true, |
| | '.': true, |
| | '/': true, |
| | '0': true, |
| | '1': true, |
| | '2': true, |
| | '3': true, |
| | '4': true, |
| | '5': true, |
| | '6': true, |
| | '7': true, |
| | '8': true, |
| | '9': true, |
| | ':': true, |
| | ';': true, |
| | '<': true, |
| | '=': true, |
| | '>': true, |
| | '?': true, |
| | '@': true, |
| | 'A': true, |
| | 'B': true, |
| | 'C': true, |
| | 'D': true, |
| | 'E': true, |
| | 'F': true, |
| | 'G': true, |
| | 'H': true, |
| | 'I': true, |
| | 'J': true, |
| | 'K': true, |
| | 'L': true, |
| | 'M': true, |
| | 'N': true, |
| | 'O': true, |
| | 'P': true, |
| | 'Q': true, |
| | 'R': true, |
| | 'S': true, |
| | 'T': true, |
| | 'U': true, |
| | 'V': true, |
| | 'W': true, |
| | 'X': true, |
| | 'Y': true, |
| | 'Z': true, |
| | '[': true, |
| | '\\': false, |
| | ']': true, |
| | '^': true, |
| | '_': true, |
| | '`': true, |
| | 'a': true, |
| | 'b': true, |
| | 'c': true, |
| | 'd': true, |
| | 'e': true, |
| | 'f': true, |
| | 'g': true, |
| | 'h': true, |
| | 'i': true, |
| | 'j': true, |
| | 'k': true, |
| | 'l': true, |
| | 'm': true, |
| | 'n': true, |
| | 'o': true, |
| | 'p': true, |
| | 'q': true, |
| | 'r': true, |
| | 's': true, |
| | 't': true, |
| | 'u': true, |
| | 'v': true, |
| | 'w': true, |
| | 'x': true, |
| | 'y': true, |
| | 'z': true, |
| | '{': true, |
| | '|': true, |
| | '}': true, |
| | '~': true, |
| | '\u007f': true, |
| | } |
| |
|