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	"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.
21type 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.
28func 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.
44func (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.
50func (h *TextHandler) WithAttrs(attrs []Attr) Handler {
51	return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
52}
53
54func (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.
94func (h *TextHandler) Handle(_ context.Context, r Record) error {
95	return h.commonHandler.handle(r)
96}
97
98func 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.
129func 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
141func 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