270 lines
5.8 KiB
Go
270 lines
5.8 KiB
Go
package log
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
const (
|
|
separator = "="
|
|
indentSeparator = " │ "
|
|
)
|
|
|
|
func (l *Logger) writeIndent(w io.Writer, str string, indent string, newline bool, key string) {
|
|
st := l.styles
|
|
|
|
// kindly borrowed from hclog
|
|
for {
|
|
nl := strings.IndexByte(str, '\n')
|
|
if nl == -1 {
|
|
if str != "" {
|
|
_, _ = w.Write([]byte(indent))
|
|
val := escapeStringForOutput(str, false)
|
|
if valueStyle, ok := st.Values[key]; ok {
|
|
val = valueStyle.Renderer(l.re).Render(val)
|
|
} else {
|
|
val = st.Value.Renderer(l.re).Render(val)
|
|
}
|
|
_, _ = w.Write([]byte(val))
|
|
if newline {
|
|
_, _ = w.Write([]byte{'\n'})
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
_, _ = w.Write([]byte(indent))
|
|
val := escapeStringForOutput(str[:nl], false)
|
|
val = st.Value.Renderer(l.re).Render(val)
|
|
_, _ = w.Write([]byte(val))
|
|
_, _ = w.Write([]byte{'\n'})
|
|
str = str[nl+1:]
|
|
}
|
|
}
|
|
|
|
func needsEscaping(str string) bool {
|
|
for _, b := range str {
|
|
if !unicode.IsPrint(b) || b == '"' {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
const (
|
|
lowerhex = "0123456789abcdef"
|
|
)
|
|
|
|
var bufPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return new(strings.Builder)
|
|
},
|
|
}
|
|
|
|
func escapeStringForOutput(str string, escapeQuotes bool) string {
|
|
// kindly borrowed from hclog
|
|
if !needsEscaping(str) {
|
|
return str
|
|
}
|
|
|
|
bb := bufPool.Get().(*strings.Builder)
|
|
bb.Reset()
|
|
|
|
defer bufPool.Put(bb)
|
|
for _, r := range str {
|
|
if escapeQuotes && r == '"' {
|
|
bb.WriteString(`\"`)
|
|
} else if unicode.IsPrint(r) {
|
|
bb.WriteRune(r)
|
|
} else {
|
|
switch r {
|
|
case '\a':
|
|
bb.WriteString(`\a`)
|
|
case '\b':
|
|
bb.WriteString(`\b`)
|
|
case '\f':
|
|
bb.WriteString(`\f`)
|
|
case '\n':
|
|
bb.WriteString(`\n`)
|
|
case '\r':
|
|
bb.WriteString(`\r`)
|
|
case '\t':
|
|
bb.WriteString(`\t`)
|
|
case '\v':
|
|
bb.WriteString(`\v`)
|
|
default:
|
|
switch {
|
|
case r < ' ':
|
|
bb.WriteString(`\x`)
|
|
bb.WriteByte(lowerhex[byte(r)>>4])
|
|
bb.WriteByte(lowerhex[byte(r)&0xF])
|
|
case !utf8.ValidRune(r):
|
|
r = 0xFFFD
|
|
fallthrough
|
|
case r < 0x10000:
|
|
bb.WriteString(`\u`)
|
|
for s := 12; s >= 0; s -= 4 {
|
|
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
|
|
}
|
|
default:
|
|
bb.WriteString(`\U`)
|
|
for s := 28; s >= 0; s -= 4 {
|
|
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bb.String()
|
|
}
|
|
|
|
func needsQuoting(s string) bool {
|
|
for i := 0; i < len(s); {
|
|
b := s[i]
|
|
if b < utf8.RuneSelf {
|
|
if needsQuotingSet[b] {
|
|
return true
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
r, size := utf8.DecodeRuneInString(s[i:])
|
|
if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
|
|
return true
|
|
}
|
|
i += size
|
|
}
|
|
return false
|
|
}
|
|
|
|
var needsQuotingSet = [utf8.RuneSelf]bool{
|
|
'"': true,
|
|
'=': true,
|
|
}
|
|
|
|
func init() {
|
|
for i := 0; i < utf8.RuneSelf; i++ {
|
|
r := rune(i)
|
|
if unicode.IsSpace(r) || !unicode.IsPrint(r) {
|
|
needsQuotingSet[i] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func writeSpace(w io.Writer, first bool) {
|
|
if !first {
|
|
w.Write([]byte{' '}) //nolint: errcheck
|
|
}
|
|
}
|
|
|
|
func (l *Logger) textFormatter(keyvals ...interface{}) {
|
|
st := l.styles
|
|
lenKeyvals := len(keyvals)
|
|
|
|
for i := 0; i < lenKeyvals; i += 2 {
|
|
firstKey := i == 0
|
|
moreKeys := i < lenKeyvals-2
|
|
|
|
switch keyvals[i] {
|
|
case TimestampKey:
|
|
if t, ok := keyvals[i+1].(time.Time); ok {
|
|
ts := t.Format(l.timeFormat)
|
|
ts = st.Timestamp.Renderer(l.re).Render(ts)
|
|
writeSpace(&l.b, firstKey)
|
|
l.b.WriteString(ts)
|
|
}
|
|
case LevelKey:
|
|
if level, ok := keyvals[i+1].(Level); ok {
|
|
var lvl string
|
|
if lvlStyle, ok := st.Levels[level]; ok {
|
|
lvl = lvlStyle.Renderer(l.re).String()
|
|
}
|
|
if lvl != "" {
|
|
writeSpace(&l.b, firstKey)
|
|
l.b.WriteString(lvl)
|
|
}
|
|
}
|
|
case CallerKey:
|
|
if caller, ok := keyvals[i+1].(string); ok {
|
|
caller = fmt.Sprintf("<%s>", caller)
|
|
caller = st.Caller.Renderer(l.re).Render(caller)
|
|
writeSpace(&l.b, firstKey)
|
|
l.b.WriteString(caller)
|
|
}
|
|
case PrefixKey:
|
|
if prefix, ok := keyvals[i+1].(string); ok {
|
|
prefix = st.Prefix.Renderer(l.re).Render(prefix + ":")
|
|
writeSpace(&l.b, firstKey)
|
|
l.b.WriteString(prefix)
|
|
}
|
|
case MessageKey:
|
|
if msg := keyvals[i+1]; msg != nil {
|
|
m := fmt.Sprint(msg)
|
|
m = st.Message.Renderer(l.re).Render(m)
|
|
writeSpace(&l.b, firstKey)
|
|
l.b.WriteString(m)
|
|
}
|
|
default:
|
|
sep := separator
|
|
indentSep := indentSeparator
|
|
sep = st.Separator.Renderer(l.re).Render(sep)
|
|
indentSep = st.Separator.Renderer(l.re).Render(indentSep)
|
|
key := fmt.Sprint(keyvals[i])
|
|
val := fmt.Sprintf("%+v", keyvals[i+1])
|
|
raw := val == ""
|
|
if raw {
|
|
val = `""`
|
|
}
|
|
if key == "" {
|
|
continue
|
|
}
|
|
actualKey := key
|
|
valueStyle := st.Value
|
|
if vs, ok := st.Values[actualKey]; ok {
|
|
valueStyle = vs
|
|
}
|
|
if keyStyle, ok := st.Keys[key]; ok {
|
|
key = keyStyle.Renderer(l.re).Render(key)
|
|
} else {
|
|
key = st.Key.Renderer(l.re).Render(key)
|
|
}
|
|
|
|
// Values may contain multiple lines, and that format
|
|
// is preserved, with each line prefixed with a " | "
|
|
// to show it's part of a collection of lines.
|
|
//
|
|
// Values may also need quoting, if not all the runes
|
|
// in the value string are "normal", like if they
|
|
// contain ANSI escape sequences.
|
|
if strings.Contains(val, "\n") {
|
|
l.b.WriteString("\n ")
|
|
l.b.WriteString(key)
|
|
l.b.WriteString(sep + "\n")
|
|
l.writeIndent(&l.b, val, indentSep, moreKeys, actualKey)
|
|
} else if !raw && needsQuoting(val) {
|
|
writeSpace(&l.b, firstKey)
|
|
l.b.WriteString(key)
|
|
l.b.WriteString(sep)
|
|
l.b.WriteString(valueStyle.Renderer(l.re).Render(fmt.Sprintf(`"%s"`,
|
|
escapeStringForOutput(val, true))))
|
|
} else {
|
|
val = valueStyle.Renderer(l.re).Render(val)
|
|
writeSpace(&l.b, firstKey)
|
|
l.b.WriteString(key)
|
|
l.b.WriteString(sep)
|
|
l.b.WriteString(val)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a newline to the end of the log message.
|
|
l.b.WriteByte('\n')
|
|
}
|