forked from toolshed/abra
		
	We were running behind and there were quite some deprecations to update. This was mostly in the upstream copy/pasta package but seems quite minimal.
		
			
				
	
	
		
			273 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			273 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 { //nolint:nestif
 | 
						|
			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
 | 
						|
				lvlStyle, ok := st.Levels[level]
 | 
						|
				if !ok {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				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')
 | 
						|
}
 |