Source file src/log/slog/text_handler.go
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 "context" 9 "encoding" 10 "fmt" 11 "io" 12 "reflect" 13 "strconv" 14 "sync" 15 "unicode" 16 "unicode/utf8" 17 ) 18 19 // TextHandler is a [Handler] that writes Records to an [io.Writer] as a 20 // sequence of key=value pairs separated by spaces and followed by a newline. 21 type TextHandler struct { 22 *commonHandler 23 } 24 25 // NewTextHandler creates a [TextHandler] that writes to w, 26 // using the given options. 27 // If opts is nil, the default options are used. 28 func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler { 29 if opts == nil { 30 opts = &HandlerOptions{} 31 } 32 return &TextHandler{ 33 &commonHandler{ 34 json: false, 35 w: w, 36 opts: *opts, 37 mu: &sync.Mutex{}, 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 *TextHandler) Enabled(_ context.Context, level Level) bool { 45 return h.commonHandler.enabled(level) 46 } 47 48 // WithAttrs returns a new [TextHandler] whose attributes consists 49 // of h's attributes followed by attrs. 50 func (h *TextHandler) WithAttrs(attrs []Attr) Handler { 51 return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)} 52 } 53 54 func (h *TextHandler) WithGroup(name string) Handler { 55 return &TextHandler{commonHandler: h.commonHandler.withGroup(name)} 56 } 57 58 // Handle formats its argument [Record] as a single line of space-separated 59 // key=value items. 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 in RFC3339 format with millisecond precision. 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" and the value is output as FILE:LINE. 71 // 72 // The message's key is "msg". 73 // 74 // To modify these or other attributes, or remove them from the output, use 75 // [HandlerOptions.ReplaceAttr]. 76 // 77 // If a value implements [encoding.TextMarshaler], the result of MarshalText is 78 // written. Otherwise, the result of [fmt.Sprint] is written. 79 // 80 // Keys and values are quoted with [strconv.Quote] if they contain Unicode space 81 // characters, non-printing characters, '"' or '='. 82 // 83 // Keys inside groups consist of components (keys or group names) separated by 84 // dots. No further escaping is performed. 85 // Thus there is no way to determine from the key "a.b.c" whether there 86 // are two groups "a" and "b" and a key "c", or a single group "a.b" and a key "c", 87 // or single group "a" and a key "b.c". 88 // If it is necessary to reconstruct the group structure of a key 89 // even in the presence of dots inside components, use 90 // [HandlerOptions.ReplaceAttr] to encode that information in the key. 91 // 92 // Each call to Handle results in a single serialized call to 93 // io.Writer.Write. 94 func (h *TextHandler) Handle(_ context.Context, r Record) error { 95 return h.commonHandler.handle(r) 96 } 97 98 func appendTextValue(s *handleState, v Value) error { 99 switch v.Kind() { 100 case KindString: 101 s.appendString(v.str()) 102 case KindTime: 103 s.appendTime(v.time()) 104 case KindAny: 105 if tm, ok := v.any.(encoding.TextMarshaler); ok { 106 data, err := tm.MarshalText() 107 if err != nil { 108 return err 109 } 110 // TODO: avoid the conversion to string. 111 s.appendString(string(data)) 112 return nil 113 } 114 if bs, ok := byteSlice(v.any); ok { 115 // As of Go 1.19, this only allocates for strings longer than 32 bytes. 116 s.buf.WriteString(strconv.Quote(string(bs))) 117 return nil 118 } 119 s.appendString(fmt.Sprintf("%+v", v.Any())) 120 default: 121 *s.buf = v.append(*s.buf) 122 } 123 return nil 124 } 125 126 // byteSlice returns its argument as a []byte if the argument's 127 // underlying type is []byte, along with a second return value of true. 128 // Otherwise it returns nil, false. 129 func byteSlice(a any) ([]byte, bool) { 130 if bs, ok := a.([]byte); ok { 131 return bs, true 132 } 133 // Like Printf's %s, we allow both the slice type and the byte element type to be named. 134 t := reflect.TypeOf(a) 135 if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 { 136 return reflect.ValueOf(a).Bytes(), true 137 } 138 return nil, false 139 } 140 141 func needsQuoting(s string) bool { 142 if len(s) == 0 { 143 return true 144 } 145 for i := 0; i < len(s); { 146 b := s[i] 147 if b < utf8.RuneSelf { 148 // Quote anything except a backslash that would need quoting in a 149 // JSON string, as well as space and '=' 150 if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) { 151 return true 152 } 153 i++ 154 continue 155 } 156 r, size := utf8.DecodeRuneInString(s[i:]) 157 if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) { 158 return true 159 } 160 i += size 161 } 162 return false 163 } 164