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