193 lines
4.4 KiB
Go
193 lines
4.4 KiB
Go
|
package middleware
|
||
|
|
||
|
// The original work was derived from Goji's middleware, source:
|
||
|
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"runtime/debug"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// Recoverer is a middleware that recovers from panics, logs the panic (and a
|
||
|
// backtrace), and returns a HTTP 500 (Internal Server Error) status if
|
||
|
// possible. Recoverer prints a request ID if one is provided.
|
||
|
//
|
||
|
// Alternatively, look at https://github.com/pressly/lg middleware pkgs.
|
||
|
func Recoverer(next http.Handler) http.Handler {
|
||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||
|
defer func() {
|
||
|
if rvr := recover(); rvr != nil && rvr != http.ErrAbortHandler {
|
||
|
|
||
|
logEntry := GetLogEntry(r)
|
||
|
if logEntry != nil {
|
||
|
logEntry.Panic(rvr, debug.Stack())
|
||
|
} else {
|
||
|
PrintPrettyStack(rvr)
|
||
|
}
|
||
|
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
next.ServeHTTP(w, r)
|
||
|
}
|
||
|
|
||
|
return http.HandlerFunc(fn)
|
||
|
}
|
||
|
|
||
|
func PrintPrettyStack(rvr interface{}) {
|
||
|
debugStack := debug.Stack()
|
||
|
s := prettyStack{}
|
||
|
out, err := s.parse(debugStack, rvr)
|
||
|
if err == nil {
|
||
|
os.Stderr.Write(out)
|
||
|
} else {
|
||
|
// print stdlib output as a fallback
|
||
|
os.Stderr.Write(debugStack)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type prettyStack struct {
|
||
|
}
|
||
|
|
||
|
func (s prettyStack) parse(debugStack []byte, rvr interface{}) ([]byte, error) {
|
||
|
var err error
|
||
|
useColor := true
|
||
|
buf := &bytes.Buffer{}
|
||
|
|
||
|
cW(buf, false, bRed, "\n")
|
||
|
cW(buf, useColor, bCyan, " panic: ")
|
||
|
cW(buf, useColor, bBlue, "%v", rvr)
|
||
|
cW(buf, false, bWhite, "\n \n")
|
||
|
|
||
|
// process debug stack info
|
||
|
stack := strings.Split(string(debugStack), "\n")
|
||
|
lines := []string{}
|
||
|
|
||
|
// locate panic line, as we may have nested panics
|
||
|
for i := len(stack) - 1; i > 0; i-- {
|
||
|
lines = append(lines, stack[i])
|
||
|
if strings.HasPrefix(stack[i], "panic(0x") {
|
||
|
lines = lines[0 : len(lines)-2] // remove boilerplate
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// reverse
|
||
|
for i := len(lines)/2 - 1; i >= 0; i-- {
|
||
|
opp := len(lines) - 1 - i
|
||
|
lines[i], lines[opp] = lines[opp], lines[i]
|
||
|
}
|
||
|
|
||
|
// decorate
|
||
|
for i, line := range lines {
|
||
|
lines[i], err = s.decorateLine(line, useColor, i)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, l := range lines {
|
||
|
fmt.Fprintf(buf, "%s", l)
|
||
|
}
|
||
|
return buf.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
func (s prettyStack) decorateLine(line string, useColor bool, num int) (string, error) {
|
||
|
line = strings.TrimSpace(line)
|
||
|
if strings.HasPrefix(line, "\t") || strings.Contains(line, ".go:") {
|
||
|
return s.decorateSourceLine(line, useColor, num)
|
||
|
} else if strings.HasSuffix(line, ")") {
|
||
|
return s.decorateFuncCallLine(line, useColor, num)
|
||
|
} else {
|
||
|
if strings.HasPrefix(line, "\t") {
|
||
|
return strings.Replace(line, "\t", " ", 1), nil
|
||
|
} else {
|
||
|
return fmt.Sprintf(" %s\n", line), nil
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s prettyStack) decorateFuncCallLine(line string, useColor bool, num int) (string, error) {
|
||
|
idx := strings.LastIndex(line, "(")
|
||
|
if idx < 0 {
|
||
|
return "", errors.New("not a func call line")
|
||
|
}
|
||
|
|
||
|
buf := &bytes.Buffer{}
|
||
|
pkg := line[0:idx]
|
||
|
// addr := line[idx:]
|
||
|
method := ""
|
||
|
|
||
|
idx = strings.LastIndex(pkg, string(os.PathSeparator))
|
||
|
if idx < 0 {
|
||
|
idx = strings.Index(pkg, ".")
|
||
|
method = pkg[idx:]
|
||
|
pkg = pkg[0:idx]
|
||
|
} else {
|
||
|
method = pkg[idx+1:]
|
||
|
pkg = pkg[0 : idx+1]
|
||
|
idx = strings.Index(method, ".")
|
||
|
pkg += method[0:idx]
|
||
|
method = method[idx:]
|
||
|
}
|
||
|
pkgColor := nYellow
|
||
|
methodColor := bGreen
|
||
|
|
||
|
if num == 0 {
|
||
|
cW(buf, useColor, bRed, " -> ")
|
||
|
pkgColor = bMagenta
|
||
|
methodColor = bRed
|
||
|
} else {
|
||
|
cW(buf, useColor, bWhite, " ")
|
||
|
}
|
||
|
cW(buf, useColor, pkgColor, "%s", pkg)
|
||
|
cW(buf, useColor, methodColor, "%s\n", method)
|
||
|
// cW(buf, useColor, nBlack, "%s", addr)
|
||
|
return buf.String(), nil
|
||
|
}
|
||
|
|
||
|
func (s prettyStack) decorateSourceLine(line string, useColor bool, num int) (string, error) {
|
||
|
idx := strings.LastIndex(line, ".go:")
|
||
|
if idx < 0 {
|
||
|
return "", errors.New("not a source line")
|
||
|
}
|
||
|
|
||
|
buf := &bytes.Buffer{}
|
||
|
path := line[0 : idx+3]
|
||
|
lineno := line[idx+3:]
|
||
|
|
||
|
idx = strings.LastIndex(path, string(os.PathSeparator))
|
||
|
dir := path[0 : idx+1]
|
||
|
file := path[idx+1:]
|
||
|
|
||
|
idx = strings.Index(lineno, " ")
|
||
|
if idx > 0 {
|
||
|
lineno = lineno[0:idx]
|
||
|
}
|
||
|
fileColor := bCyan
|
||
|
lineColor := bGreen
|
||
|
|
||
|
if num == 1 {
|
||
|
cW(buf, useColor, bRed, " -> ")
|
||
|
fileColor = bRed
|
||
|
lineColor = bMagenta
|
||
|
} else {
|
||
|
cW(buf, false, bWhite, " ")
|
||
|
}
|
||
|
cW(buf, useColor, bWhite, "%s", dir)
|
||
|
cW(buf, useColor, fileColor, "%s", file)
|
||
|
cW(buf, useColor, lineColor, "%s", lineno)
|
||
|
if num == 1 {
|
||
|
cW(buf, false, bWhite, "\n")
|
||
|
}
|
||
|
cW(buf, false, bWhite, "\n")
|
||
|
|
||
|
return buf.String(), nil
|
||
|
}
|