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