hmn/logging/logging.go

159 lines
3.6 KiB
Go

package logging
import (
"encoding/json"
"os"
"sort"
"strconv"
"strings"
"git.handmade.network/hmn/hmn/color"
"git.handmade.network/hmn/hmn/oops"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func init() {
zerolog.ErrorStackMarshaler = oops.ZerologStackMarshaler
log.Logger = log.Output(NewPrettyZerologWriter())
}
func Trace() *zerolog.Event {
return log.Trace().Timestamp().Stack()
}
func Debug() *zerolog.Event {
return log.Debug().Timestamp().Stack()
}
func Info() *zerolog.Event {
return log.Info().Timestamp().Stack()
}
func Warn() *zerolog.Event {
return log.Warn().Timestamp().Stack()
}
func Error() *zerolog.Event {
return log.Error().Timestamp().Stack()
}
type PrettyZerologWriter struct {
wd string
}
type PrettyLogEntry struct {
Timestamp string
Level string
Message string
Error string
StackTrace []interface{}
OtherFields []PrettyField
}
type PrettyField struct {
Name string
Value interface{}
}
var ColorFromLevel = map[string]string{
"trace": color.Gray,
"debug": color.Gray,
"info": color.BgBlue,
"warn": color.BgYellow,
"error": color.BgRed,
"fatal": color.BgRed,
"panic": color.BgRed,
}
func NewPrettyZerologWriter() *PrettyZerologWriter {
wd, _ := os.Getwd()
return &PrettyZerologWriter{
wd: wd,
}
}
func (w *PrettyZerologWriter) Write(p []byte) (int, error) {
// TODO: panic recovery so we log _something_
var fields map[string]interface{}
err := json.Unmarshal(p, &fields)
if err != nil {
return os.Stderr.Write(p)
}
var pretty PrettyLogEntry
for name, val := range fields {
switch name {
case zerolog.TimestampFieldName:
pretty.Timestamp = val.(string)
case zerolog.LevelFieldName:
pretty.Level = val.(string)
case zerolog.MessageFieldName:
pretty.Message = val.(string)
case zerolog.ErrorFieldName:
pretty.Error = val.(string)
case zerolog.ErrorStackFieldName:
pretty.StackTrace = val.([]interface{})
default:
pretty.OtherFields = append(pretty.OtherFields, PrettyField{
Name: name,
Value: val,
})
}
}
sort.Slice(pretty.OtherFields, func(i, j int) bool {
return strings.Compare(pretty.OtherFields[i].Name, pretty.OtherFields[j].Name) < 0
})
var b strings.Builder
b.WriteString("---------------------------------------\n")
b.WriteString(pretty.Timestamp)
b.WriteString(" ")
if pretty.Level != "" {
b.WriteString(ColorFromLevel[pretty.Level])
b.WriteString(color.Bold)
b.WriteString(strings.ToUpper(pretty.Level))
b.WriteString(color.Reset)
b.WriteString(": ")
}
b.WriteString(pretty.Message)
b.WriteString("\n")
if pretty.Error != "" {
b.WriteString(" " + color.Bold + color.Red + "ERROR:" + color.Reset + " ")
b.WriteString(pretty.Error)
b.WriteString("\n")
}
if len(pretty.OtherFields) > 0 {
b.WriteString(" " + color.Bold + color.Blue + "Fields:" + color.Reset + "\n")
for _, field := range pretty.OtherFields {
valuePretty, _ := json.MarshalIndent(field.Value, " ", " ")
b.WriteString(" ")
b.WriteString(field.Name)
b.WriteString(": ")
b.WriteString(string(valuePretty))
b.WriteString("\n")
}
}
if pretty.StackTrace != nil {
b.WriteString(" " + color.Bold + color.Blue + "Stack trace:" + color.Reset + "\n")
for _, frame := range pretty.StackTrace {
frameMap := frame.(map[string]interface{})
file := frameMap["file"].(string)
file = strings.Replace(file, w.wd, ".", 1)
b.WriteString(" ")
b.WriteString(frameMap["function"].(string))
b.WriteString(" (")
b.WriteString(file)
b.WriteString(":")
b.WriteString(strconv.Itoa(int(frameMap["line"].(float64))))
b.WriteString(")\n")
}
}
return os.Stderr.Write([]byte(b.String()))
}