...

Source file src/github.com/go-chi/chi/middleware/recoverer.go

Documentation: github.com/go-chi/chi/middleware

     1  package middleware
     2  
     3  // The original work was derived from Goji's middleware, source:
     4  // https://github.com/zenazn/goji/tree/master/web/middleware
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"os"
    12  	"runtime/debug"
    13  	"strings"
    14  )
    15  
    16  // Recoverer is a middleware that recovers from panics, logs the panic (and a
    17  // backtrace), and returns a HTTP 500 (Internal Server Error) status if
    18  // possible. Recoverer prints a request ID if one is provided.
    19  //
    20  // Alternatively, look at https://github.com/pressly/lg middleware pkgs.
    21  func Recoverer(next http.Handler) http.Handler {
    22  	fn := func(w http.ResponseWriter, r *http.Request) {
    23  		defer func() {
    24  			if rvr := recover(); rvr != nil && rvr != http.ErrAbortHandler {
    25  
    26  				logEntry := GetLogEntry(r)
    27  				if logEntry != nil {
    28  					logEntry.Panic(rvr, debug.Stack())
    29  				} else {
    30  					PrintPrettyStack(rvr)
    31  				}
    32  
    33  				w.WriteHeader(http.StatusInternalServerError)
    34  			}
    35  		}()
    36  
    37  		next.ServeHTTP(w, r)
    38  	}
    39  
    40  	return http.HandlerFunc(fn)
    41  }
    42  
    43  func PrintPrettyStack(rvr interface{}) {
    44  	debugStack := debug.Stack()
    45  	s := prettyStack{}
    46  	out, err := s.parse(debugStack, rvr)
    47  	if err == nil {
    48  		os.Stderr.Write(out)
    49  	} else {
    50  		// print stdlib output as a fallback
    51  		os.Stderr.Write(debugStack)
    52  	}
    53  }
    54  
    55  type prettyStack struct {
    56  }
    57  
    58  func (s prettyStack) parse(debugStack []byte, rvr interface{}) ([]byte, error) {
    59  	var err error
    60  	useColor := true
    61  	buf := &bytes.Buffer{}
    62  
    63  	cW(buf, false, bRed, "\n")
    64  	cW(buf, useColor, bCyan, " panic: ")
    65  	cW(buf, useColor, bBlue, "%v", rvr)
    66  	cW(buf, false, bWhite, "\n \n")
    67  
    68  	// process debug stack info
    69  	stack := strings.Split(string(debugStack), "\n")
    70  	lines := []string{}
    71  
    72  	// locate panic line, as we may have nested panics
    73  	for i := len(stack) - 1; i > 0; i-- {
    74  		lines = append(lines, stack[i])
    75  		if strings.HasPrefix(stack[i], "panic(0x") {
    76  			lines = lines[0 : len(lines)-2] // remove boilerplate
    77  			break
    78  		}
    79  	}
    80  
    81  	// reverse
    82  	for i := len(lines)/2 - 1; i >= 0; i-- {
    83  		opp := len(lines) - 1 - i
    84  		lines[i], lines[opp] = lines[opp], lines[i]
    85  	}
    86  
    87  	// decorate
    88  	for i, line := range lines {
    89  		lines[i], err = s.decorateLine(line, useColor, i)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  	}
    94  
    95  	for _, l := range lines {
    96  		fmt.Fprintf(buf, "%s", l)
    97  	}
    98  	return buf.Bytes(), nil
    99  }
   100  
   101  func (s prettyStack) decorateLine(line string, useColor bool, num int) (string, error) {
   102  	line = strings.TrimSpace(line)
   103  	if strings.HasPrefix(line, "\t") || strings.Contains(line, ".go:") {
   104  		return s.decorateSourceLine(line, useColor, num)
   105  	} else if strings.HasSuffix(line, ")") {
   106  		return s.decorateFuncCallLine(line, useColor, num)
   107  	} else {
   108  		if strings.HasPrefix(line, "\t") {
   109  			return strings.Replace(line, "\t", "      ", 1), nil
   110  		} else {
   111  			return fmt.Sprintf("    %s\n", line), nil
   112  		}
   113  	}
   114  }
   115  
   116  func (s prettyStack) decorateFuncCallLine(line string, useColor bool, num int) (string, error) {
   117  	idx := strings.LastIndex(line, "(")
   118  	if idx < 0 {
   119  		return "", errors.New("not a func call line")
   120  	}
   121  
   122  	buf := &bytes.Buffer{}
   123  	pkg := line[0:idx]
   124  	// addr := line[idx:]
   125  	method := ""
   126  
   127  	idx = strings.LastIndex(pkg, string(os.PathSeparator))
   128  	if idx < 0 {
   129  		idx = strings.Index(pkg, ".")
   130  		method = pkg[idx:]
   131  		pkg = pkg[0:idx]
   132  	} else {
   133  		method = pkg[idx+1:]
   134  		pkg = pkg[0 : idx+1]
   135  		idx = strings.Index(method, ".")
   136  		pkg += method[0:idx]
   137  		method = method[idx:]
   138  	}
   139  	pkgColor := nYellow
   140  	methodColor := bGreen
   141  
   142  	if num == 0 {
   143  		cW(buf, useColor, bRed, " -> ")
   144  		pkgColor = bMagenta
   145  		methodColor = bRed
   146  	} else {
   147  		cW(buf, useColor, bWhite, "    ")
   148  	}
   149  	cW(buf, useColor, pkgColor, "%s", pkg)
   150  	cW(buf, useColor, methodColor, "%s\n", method)
   151  	// cW(buf, useColor, nBlack, "%s", addr)
   152  	return buf.String(), nil
   153  }
   154  
   155  func (s prettyStack) decorateSourceLine(line string, useColor bool, num int) (string, error) {
   156  	idx := strings.LastIndex(line, ".go:")
   157  	if idx < 0 {
   158  		return "", errors.New("not a source line")
   159  	}
   160  
   161  	buf := &bytes.Buffer{}
   162  	path := line[0 : idx+3]
   163  	lineno := line[idx+3:]
   164  
   165  	idx = strings.LastIndex(path, string(os.PathSeparator))
   166  	dir := path[0 : idx+1]
   167  	file := path[idx+1:]
   168  
   169  	idx = strings.Index(lineno, " ")
   170  	if idx > 0 {
   171  		lineno = lineno[0:idx]
   172  	}
   173  	fileColor := bCyan
   174  	lineColor := bGreen
   175  
   176  	if num == 1 {
   177  		cW(buf, useColor, bRed, " ->   ")
   178  		fileColor = bRed
   179  		lineColor = bMagenta
   180  	} else {
   181  		cW(buf, false, bWhite, "      ")
   182  	}
   183  	cW(buf, useColor, bWhite, "%s", dir)
   184  	cW(buf, useColor, fileColor, "%s", file)
   185  	cW(buf, useColor, lineColor, "%s", lineno)
   186  	if num == 1 {
   187  		cW(buf, false, bWhite, "\n")
   188  	}
   189  	cW(buf, false, bWhite, "\n")
   190  
   191  	return buf.String(), nil
   192  }
   193  

View as plain text