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
5package slog
6
7import (
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.
20type 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.
27func 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.
42func (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.
48func (h *TextHandler) WithAttrs(attrs []Attr) Handler {
49 return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
50}
51
52func (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.
92func (h *TextHandler) Handle(_ context.Context, r Record) error {
93 return h.commonHandler.handle(r)
94}
95
96func 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.
127func 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
139func 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}