Boilerplate for how I like to write a backend web service.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

192 lines
4.4 KiB

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
}