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')
 | |
| }
 |