mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-30 15:42:46 +00:00
162 lines
4.7 KiB
Go
162 lines
4.7 KiB
Go
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package slog
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
"unicode"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
// TextHandler is a Handler that writes Records to an io.Writer as a
|
||
|
// sequence of key=value pairs separated by spaces and followed by a newline.
|
||
|
type TextHandler struct {
|
||
|
*commonHandler
|
||
|
}
|
||
|
|
||
|
// NewTextHandler creates a TextHandler that writes to w,
|
||
|
// using the given options.
|
||
|
// If opts is nil, the default options are used.
|
||
|
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
|
||
|
if opts == nil {
|
||
|
opts = &HandlerOptions{}
|
||
|
}
|
||
|
return &TextHandler{
|
||
|
&commonHandler{
|
||
|
json: false,
|
||
|
w: w,
|
||
|
opts: *opts,
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Enabled reports whether the handler handles records at the given level.
|
||
|
// The handler ignores records whose level is lower.
|
||
|
func (h *TextHandler) Enabled(_ context.Context, level Level) bool {
|
||
|
return h.commonHandler.enabled(level)
|
||
|
}
|
||
|
|
||
|
// WithAttrs returns a new TextHandler whose attributes consists
|
||
|
// of h's attributes followed by attrs.
|
||
|
func (h *TextHandler) WithAttrs(attrs []Attr) Handler {
|
||
|
return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
|
||
|
}
|
||
|
|
||
|
func (h *TextHandler) WithGroup(name string) Handler {
|
||
|
return &TextHandler{commonHandler: h.commonHandler.withGroup(name)}
|
||
|
}
|
||
|
|
||
|
// Handle formats its argument Record as a single line of space-separated
|
||
|
// key=value items.
|
||
|
//
|
||
|
// If the Record's time is zero, the time is omitted.
|
||
|
// Otherwise, the key is "time"
|
||
|
// and the value is output in RFC3339 format with millisecond precision.
|
||
|
//
|
||
|
// If the Record's level is zero, the level is omitted.
|
||
|
// Otherwise, the key is "level"
|
||
|
// and the value of [Level.String] is output.
|
||
|
//
|
||
|
// If the AddSource option is set and source information is available,
|
||
|
// the key is "source" and the value is output as FILE:LINE.
|
||
|
//
|
||
|
// The message's key is "msg".
|
||
|
//
|
||
|
// To modify these or other attributes, or remove them from the output, use
|
||
|
// [HandlerOptions.ReplaceAttr].
|
||
|
//
|
||
|
// If a value implements [encoding.TextMarshaler], the result of MarshalText is
|
||
|
// written. Otherwise, the result of fmt.Sprint is written.
|
||
|
//
|
||
|
// Keys and values are quoted with [strconv.Quote] if they contain Unicode space
|
||
|
// characters, non-printing characters, '"' or '='.
|
||
|
//
|
||
|
// Keys inside groups consist of components (keys or group names) separated by
|
||
|
// dots. No further escaping is performed.
|
||
|
// Thus there is no way to determine from the key "a.b.c" whether there
|
||
|
// are two groups "a" and "b" and a key "c", or a single group "a.b" and a key "c",
|
||
|
// or single group "a" and a key "b.c".
|
||
|
// If it is necessary to reconstruct the group structure of a key
|
||
|
// even in the presence of dots inside components, use
|
||
|
// [HandlerOptions.ReplaceAttr] to encode that information in the key.
|
||
|
//
|
||
|
// Each call to Handle results in a single serialized call to
|
||
|
// io.Writer.Write.
|
||
|
func (h *TextHandler) Handle(_ context.Context, r Record) error {
|
||
|
return h.commonHandler.handle(r)
|
||
|
}
|
||
|
|
||
|
func appendTextValue(s *handleState, v Value) error {
|
||
|
switch v.Kind() {
|
||
|
case KindString:
|
||
|
s.appendString(v.str())
|
||
|
case KindTime:
|
||
|
s.appendTime(v.time())
|
||
|
case KindAny:
|
||
|
if tm, ok := v.any.(encoding.TextMarshaler); ok {
|
||
|
data, err := tm.MarshalText()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
// TODO: avoid the conversion to string.
|
||
|
s.appendString(string(data))
|
||
|
return nil
|
||
|
}
|
||
|
if bs, ok := byteSlice(v.any); ok {
|
||
|
// As of Go 1.19, this only allocates for strings longer than 32 bytes.
|
||
|
s.buf.WriteString(strconv.Quote(string(bs)))
|
||
|
return nil
|
||
|
}
|
||
|
s.appendString(fmt.Sprintf("%+v", v.Any()))
|
||
|
default:
|
||
|
*s.buf = v.append(*s.buf)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// byteSlice returns its argument as a []byte if the argument's
|
||
|
// underlying type is []byte, along with a second return value of true.
|
||
|
// Otherwise it returns nil, false.
|
||
|
func byteSlice(a any) ([]byte, bool) {
|
||
|
if bs, ok := a.([]byte); ok {
|
||
|
return bs, true
|
||
|
}
|
||
|
// Like Printf's %s, we allow both the slice type and the byte element type to be named.
|
||
|
t := reflect.TypeOf(a)
|
||
|
if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
|
||
|
return reflect.ValueOf(a).Bytes(), true
|
||
|
}
|
||
|
return nil, false
|
||
|
}
|
||
|
|
||
|
func needsQuoting(s string) bool {
|
||
|
if len(s) == 0 {
|
||
|
return true
|
||
|
}
|
||
|
for i := 0; i < len(s); {
|
||
|
b := s[i]
|
||
|
if b < utf8.RuneSelf {
|
||
|
// Quote anything except a backslash that would need quoting in a
|
||
|
// JSON string, as well as space and '='
|
||
|
if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) {
|
||
|
return true
|
||
|
}
|
||
|
i++
|
||
|
continue
|
||
|
}
|
||
|
r, size := utf8.DecodeRuneInString(s[i:])
|
||
|
if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
|
||
|
return true
|
||
|
}
|
||
|
i += size
|
||
|
}
|
||
|
return false
|
||
|
}
|