1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/vendor/github.com/aybabtme/humanlog/logfmt_handler.go

240 lines
5.0 KiB
Go

package humanlog
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"text/tabwriter"
"time"
"github.com/fatih/color"
"github.com/go-logfmt/logfmt"
)
// LogfmtHandler can handle logs emmited by logrus.TextFormatter loggers.
type LogfmtHandler struct {
buf *bytes.Buffer
out *tabwriter.Writer
truncKV int
Opts *HandlerOptions
Level string
Time time.Time
Message string
Fields map[string]string
last map[string]string
}
func (h *LogfmtHandler) clear() {
h.Level = ""
h.Time = time.Time{}
h.Message = ""
h.last = h.Fields
h.Fields = make(map[string]string)
if h.buf != nil {
h.buf.Reset()
}
}
// CanHandle tells if this line can be handled by this handler.
func (h *LogfmtHandler) TryHandle(d []byte) bool {
if !bytes.ContainsRune(d, '=') {
return false
}
if !h.UnmarshalLogfmt(d) {
h.clear()
return false
}
return true
}
// HandleLogfmt sets the fields of the handler.
func (h *LogfmtHandler) UnmarshalLogfmt(data []byte) bool {
dec := logfmt.NewDecoder(bytes.NewReader(data))
for dec.ScanRecord() {
next_kv:
for dec.ScanKeyval() {
key := dec.Key()
val := dec.Value()
if h.Time.IsZero() {
foundTime := checkEachUntilFound(supportedTimeFields, func(field string) bool {
time, ok := tryParseTime(string(val))
if ok {
h.Time = time
}
return ok
})
if foundTime {
continue next_kv
}
}
if len(h.Message) == 0 {
foundMessage := checkEachUntilFound(supportedMessageFields, func(field string) bool {
if !bytes.Equal(key, []byte(field)) {
return false
}
h.Message = string(val)
return true
})
if foundMessage {
continue next_kv
}
}
if len(h.Level) == 0 {
foundLevel := checkEachUntilFound(supportedLevelFields, func(field string) bool {
if !bytes.Equal(key, []byte(field)) {
return false
}
h.Level = string(val)
return true
})
if foundLevel {
continue next_kv
}
}
h.setField(key, val)
}
}
return dec.Err() == nil
}
// Prettify the output in a logrus like fashion.
func (h *LogfmtHandler) Prettify(skipUnchanged bool) []byte {
defer h.clear()
if h.out == nil {
if h.Opts == nil {
h.Opts = DefaultOptions
}
h.buf = bytes.NewBuffer(nil)
h.out = tabwriter.NewWriter(h.buf, 0, 1, 0, '\t', 0)
}
var (
msgColor *color.Color
msgAbsentColor *color.Color
)
if h.Opts.LightBg {
msgColor = h.Opts.MsgLightBgColor
msgAbsentColor = h.Opts.MsgAbsentLightBgColor
} else {
msgColor = h.Opts.MsgDarkBgColor
msgAbsentColor = h.Opts.MsgAbsentDarkBgColor
}
var msg string
if h.Message == "" {
msg = msgAbsentColor.Sprint("<no msg>")
} else {
msg = msgColor.Sprint(h.Message)
}
lvl := strings.ToUpper(h.Level)[:imin(4, len(h.Level))]
var level string
switch h.Level {
case "debug":
level = h.Opts.DebugLevelColor.Sprint(lvl)
case "info":
level = h.Opts.InfoLevelColor.Sprint(lvl)
case "warn", "warning":
level = h.Opts.WarnLevelColor.Sprint(lvl)
case "error":
level = h.Opts.ErrorLevelColor.Sprint(lvl)
case "fatal", "panic":
level = h.Opts.FatalLevelColor.Sprint(lvl)
default:
level = h.Opts.UnknownLevelColor.Sprint(lvl)
}
var timeColor *color.Color
if h.Opts.LightBg {
timeColor = h.Opts.TimeLightBgColor
} else {
timeColor = h.Opts.TimeDarkBgColor
}
_, _ = fmt.Fprintf(h.out, "%s |%s| %s\t %s",
timeColor.Sprint(h.Time.Format(h.Opts.TimeFormat)),
level,
msg,
strings.Join(h.joinKVs(skipUnchanged, "="), "\t "),
)
_ = h.out.Flush()
return h.buf.Bytes()
}
func (h *LogfmtHandler) setLevel(val []byte) { h.Level = string(val) }
func (h *LogfmtHandler) setMessage(val []byte) { h.Message = string(val) }
func (h *LogfmtHandler) setTime(val []byte) (parsed bool) {
valStr := string(val)
if valFloat, err := strconv.ParseFloat(valStr, 64); err == nil {
h.Time, parsed = tryParseTime(valFloat)
} else {
h.Time, parsed = tryParseTime(string(val))
}
return
}
func (h *LogfmtHandler) setField(key, val []byte) {
if h.Fields == nil {
h.Fields = make(map[string]string)
}
h.Fields[string(key)] = string(val)
}
func (h *LogfmtHandler) joinKVs(skipUnchanged bool, sep string) []string {
kv := make([]string, 0, len(h.Fields))
for k, v := range h.Fields {
if !h.Opts.shouldShowKey(k) {
continue
}
if skipUnchanged {
if lastV, ok := h.last[k]; ok && lastV == v && !h.Opts.shouldShowUnchanged(k) {
continue
}
}
kstr := h.Opts.KeyColor.Sprint(k)
var vstr string
if h.Opts.Truncates && len(v) > h.Opts.TruncateLength {
vstr = v[:h.Opts.TruncateLength] + "..."
} else {
vstr = v
}
vstr = h.Opts.ValColor.Sprint(vstr)
kv = append(kv, kstr+sep+vstr)
}
sort.Strings(kv)
if h.Opts.SortLongest {
sort.Stable(byLongest(kv))
}
return kv
}
type byLongest []string
func (s byLongest) Len() int { return len(s) }
func (s byLongest) Less(i, j int) bool { return len(s[i]) < len(s[j]) }
func (s byLongest) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func imin(a, b int) int {
if a < b {
return a
}
return b
}