]> Cypherpunks.ru repositories - gostls13.git/blob - src/log/slog/text_handler.go
log/slog: built-in handler constructors take options as a second arg
[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 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 }