1package log
2
3import (
4 "fmt"
5 "io"
6 "strings"
7 "sync"
8 "time"
9 "unicode"
10 "unicode/utf8"
11)
12
13const (
14 separator = "="
15 indentSeparator = " │ "
16)
17
18func (l *Logger) writeIndent(w io.Writer, str string, indent string, newline bool, key string) {
19 st := l.styles
20
21 // kindly borrowed from hclog
22 for {
23 nl := strings.IndexByte(str, '\n')
24 if nl == -1 {
25 if str != "" {
26 _, _ = w.Write([]byte(indent))
27 val := escapeStringForOutput(str, false)
28 if valueStyle, ok := st.Values[key]; ok {
29 val = valueStyle.Render(val)
30 } else {
31 val = st.Value.Render(val)
32 }
33 _, _ = w.Write([]byte(val))
34 if newline {
35 _, _ = w.Write([]byte{'\n'})
36 }
37 }
38 return
39 }
40
41 _, _ = w.Write([]byte(indent))
42 val := escapeStringForOutput(str[:nl], false)
43 val = st.Value.Render(val)
44 _, _ = w.Write([]byte(val))
45 _, _ = w.Write([]byte{'\n'})
46 str = str[nl+1:]
47 }
48}
49
50func needsEscaping(str string) bool {
51 for _, b := range str {
52 if !unicode.IsPrint(b) || b == '"' {
53 return true
54 }
55 }
56
57 return false
58}
59
60const (
61 lowerhex = "0123456789abcdef"
62)
63
64var bufPool = sync.Pool{
65 New: func() interface{} {
66 return new(strings.Builder)
67 },
68}
69
70func escapeStringForOutput(str string, escapeQuotes bool) string {
71 // kindly borrowed from hclog
72 if !needsEscaping(str) {
73 return str
74 }
75
76 bb := bufPool.Get().(*strings.Builder)
77 bb.Reset()
78
79 defer bufPool.Put(bb)
80 for _, r := range str {
81 if escapeQuotes && r == '"' {
82 bb.WriteString(`\"`)
83 } else if unicode.IsPrint(r) {
84 bb.WriteRune(r)
85 } else {
86 switch r {
87 case '\a':
88 bb.WriteString(`\a`)
89 case '\b':
90 bb.WriteString(`\b`)
91 case '\f':
92 bb.WriteString(`\f`)
93 case '\n':
94 bb.WriteString(`\n`)
95 case '\r':
96 bb.WriteString(`\r`)
97 case '\t':
98 bb.WriteString(`\t`)
99 case '\v':
100 bb.WriteString(`\v`)
101 default:
102 switch {
103 case r < ' ':
104 bb.WriteString(`\x`)
105 bb.WriteByte(lowerhex[byte(r)>>4])
106 bb.WriteByte(lowerhex[byte(r)&0xF])
107 case !utf8.ValidRune(r):
108 r = 0xFFFD
109 fallthrough
110 case r < 0x10000:
111 bb.WriteString(`\u`)
112 for s := 12; s >= 0; s -= 4 {
113 bb.WriteByte(lowerhex[r>>uint(s)&0xF])
114 }
115 default:
116 bb.WriteString(`\U`)
117 for s := 28; s >= 0; s -= 4 {
118 bb.WriteByte(lowerhex[r>>uint(s)&0xF])
119 }
120 }
121 }
122 }
123 }
124
125 return bb.String()
126}
127
128func needsQuoting(s string) bool {
129 for i := 0; i < len(s); {
130 b := s[i]
131 if b < utf8.RuneSelf {
132 if needsQuotingSet[b] {
133 return true
134 }
135 i++
136 continue
137 }
138 r, size := utf8.DecodeRuneInString(s[i:])
139 if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
140 return true
141 }
142 i += size
143 }
144 return false
145}
146
147var needsQuotingSet = [utf8.RuneSelf]bool{
148 '"': true,
149 '=': true,
150}
151
152func init() {
153 for i := 0; i < utf8.RuneSelf; i++ {
154 r := rune(i)
155 if unicode.IsSpace(r) || !unicode.IsPrint(r) {
156 needsQuotingSet[i] = true
157 }
158 }
159}
160
161func writeSpace(w io.Writer, first bool) {
162 if !first {
163 w.Write([]byte{' '}) //nolint: errcheck
164 }
165}
166
167func (l *Logger) textFormatter(keyvals ...interface{}) {
168 st := l.styles
169 lenKeyvals := len(keyvals)
170
171 for i := 0; i < lenKeyvals; i += 2 {
172 firstKey := i == 0
173 moreKeys := i < lenKeyvals-2
174
175 switch keyvals[i] {
176 case TimestampKey:
177 if t, ok := keyvals[i+1].(time.Time); ok {
178 ts := t.Format(l.timeFormat)
179 ts = st.Timestamp.Render(ts)
180 writeSpace(&l.b, firstKey)
181 l.b.WriteString(ts)
182 }
183 case LevelKey:
184 if level, ok := keyvals[i+1].(Level); ok {
185 var lvl string
186 lvlStyle, ok := st.Levels[level]
187 if !ok {
188 continue
189 }
190
191 lvl = lvlStyle.String()
192 if lvl != "" {
193 writeSpace(&l.b, firstKey)
194 l.b.WriteString(lvl)
195 }
196 }
197 case CallerKey:
198 if caller, ok := keyvals[i+1].(string); ok {
199 caller = fmt.Sprintf("<%s>", caller)
200 caller = st.Caller.Render(caller)
201 writeSpace(&l.b, firstKey)
202 l.b.WriteString(caller)
203 }
204 case PrefixKey:
205 if prefix, ok := keyvals[i+1].(string); ok {
206 prefix = st.Prefix.Render(prefix + ":")
207 writeSpace(&l.b, firstKey)
208 l.b.WriteString(prefix)
209 }
210 case MessageKey:
211 if msg := keyvals[i+1]; msg != nil {
212 m := fmt.Sprint(msg)
213 m = st.Message.Render(m)
214 writeSpace(&l.b, firstKey)
215 l.b.WriteString(m)
216 }
217 default:
218 sep := separator
219 indentSep := indentSeparator
220 sep = st.Separator.Render(sep)
221 indentSep = st.Separator.Render(indentSep)
222 key := fmt.Sprint(keyvals[i])
223 val := fmt.Sprintf("%+v", keyvals[i+1])
224 raw := val == ""
225 if raw {
226 val = `""`
227 }
228 if key == "" {
229 continue
230 }
231 actualKey := key
232 valueStyle := st.Value
233 if vs, ok := st.Values[actualKey]; ok {
234 valueStyle = vs
235 }
236 if keyStyle, ok := st.Keys[key]; ok {
237 key = keyStyle.Render(key)
238 } else {
239 key = st.Key.Render(key)
240 }
241
242 // Values may contain multiple lines, and that format
243 // is preserved, with each line prefixed with a " | "
244 // to show it's part of a collection of lines.
245 //
246 // Values may also need quoting, if not all the runes
247 // in the value string are "normal", like if they
248 // contain ANSI escape sequences.
249 if strings.Contains(val, "\n") {
250 l.b.WriteString("\n ")
251 l.b.WriteString(key)
252 l.b.WriteString(sep + "\n")
253 l.writeIndent(&l.b, val, indentSep, moreKeys, actualKey)
254 } else if !raw && needsQuoting(val) {
255 writeSpace(&l.b, firstKey)
256 l.b.WriteString(key)
257 l.b.WriteString(sep)
258 l.b.WriteString(valueStyle.Render(fmt.Sprintf(`"%s"`,
259 escapeStringForOutput(val, true))))
260 } else {
261 val = valueStyle.Render(val)
262 writeSpace(&l.b, firstKey)
263 l.b.WriteString(key)
264 l.b.WriteString(sep)
265 l.b.WriteString(val)
266 }
267 }
268 }
269
270 // Add a newline to the end of the log message.
271 l.b.WriteByte('\n')
272}