package cli import ( "emperror.dev/errors" "fmt" "github.com/apex/log" "github.com/apex/log/handlers/cli" color2 "github.com/fatih/color" "github.com/mattn/go-colorable" "io" "os" "sync" "time" ) var Default = New(os.Stderr, true) var bold = color2.New(color2.Bold) var Strings = [...]string{ log.DebugLevel: "DEBUG", log.InfoLevel: " INFO", log.WarnLevel: " WARN", log.ErrorLevel: "ERROR", log.FatalLevel: "FATAL", } type Handler struct { mu sync.Mutex Writer io.Writer Padding int } func New(w io.Writer, useColors bool) *Handler { if f, ok := w.(*os.File); ok { if useColors { return &Handler{Writer: colorable.NewColorable(f), Padding: 2} } } return &Handler{Writer: colorable.NewNonColorable(w), Padding: 2} } type tracer interface { StackTrace() errors.StackTrace } // HandleLog implements log.Handler. func (h *Handler) HandleLog(e *log.Entry) error { color := cli.Colors[e.Level] level := Strings[e.Level] names := e.Fields.Names() h.mu.Lock() defer h.mu.Unlock() color.Fprintf(h.Writer, "%s: [%s] %-25s", bold.Sprintf("%*s", h.Padding+1, level), time.Now().Format(time.StampMilli), e.Message) for _, name := range names { if name == "source" { continue } fmt.Fprintf(h.Writer, " %s=%v", color.Sprint(name), e.Fields.Get(name)) } fmt.Fprintln(h.Writer) for _, name := range names { if name != "error" { continue } var br = color2.New(color2.Bold, color2.FgRed) if err, ok := e.Fields.Get("error").(error); ok { fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Stacktrace:"), getErrorStack(err, false)) } else { fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Invalid Error:"), err) } } return nil } func getErrorStack(err error, i bool) errors.StackTrace { e, ok := err.(tracer) if !ok { if i { // Just abort out of this and return a stacktrace leading up to this point. It isn't perfect // but it'll at least include what function lead to this being called which we can then handle. if e, ok = errors.WrapIf(err, "failed to generate stacktrace for caught error").(tracer); ok { return e.StackTrace() } // The errors.WrapIf did not return a interface compatible with `tracer`, so // we don't have an easy way to get the stacktrace, this should probably be changed // at some point, but without this the application may panic when handling some errors. return nil } return getErrorStack(errors.WrapIf(err, err.Error()), true) } st := e.StackTrace() l := len(st) // If this was an internal stack generation we're going to skip over the top four items in the stack // trace since they'll point to the error that was generated by this function. f := 0 if i { f = 5 } if i && l > 9 { l = 9 } else if !i && l > 5 { l = 5 } return st[f:l] }