recovery.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package gin
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "net/http"
  8. "runtime"
  9. )
  10. var (
  11. dunno = []byte("???")
  12. centerDot = []byte("·")
  13. dot = []byte(".")
  14. slash = []byte("/")
  15. )
  16. // stack returns a nicely formated stack frame, skipping skip frames
  17. func stack(skip int) []byte {
  18. buf := new(bytes.Buffer) // the returned data
  19. // As we loop, we open files and read them. These variables record the currently
  20. // loaded file.
  21. var lines [][]byte
  22. var lastFile string
  23. for i := skip; ; i++ { // Skip the expected number of frames
  24. pc, file, line, ok := runtime.Caller(i)
  25. if !ok {
  26. break
  27. }
  28. // Print this much at least. If we can't find the source, it won't show.
  29. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
  30. if file != lastFile {
  31. data, err := ioutil.ReadFile(file)
  32. if err != nil {
  33. continue
  34. }
  35. lines = bytes.Split(data, []byte{'\n'})
  36. lastFile = file
  37. }
  38. fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
  39. }
  40. return buf.Bytes()
  41. }
  42. // source returns a space-trimmed slice of the n'th line.
  43. func source(lines [][]byte, n int) []byte {
  44. n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
  45. if n < 0 || n >= len(lines) {
  46. return dunno
  47. }
  48. return bytes.TrimSpace(lines[n])
  49. }
  50. // function returns, if possible, the name of the function containing the PC.
  51. func function(pc uintptr) []byte {
  52. fn := runtime.FuncForPC(pc)
  53. if fn == nil {
  54. return dunno
  55. }
  56. name := []byte(fn.Name())
  57. // The name includes the path name to the package, which is unnecessary
  58. // since the file name is already included. Plus, it has center dots.
  59. // That is, we see
  60. // runtime/debug.*T·ptrmethod
  61. // and want
  62. // *T.ptrmethod
  63. // Also the package path might contains dot (e.g. code.google.com/...),
  64. // so first eliminate the path prefix
  65. if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
  66. name = name[lastslash+1:]
  67. }
  68. if period := bytes.Index(name, dot); period >= 0 {
  69. name = name[period+1:]
  70. }
  71. name = bytes.Replace(name, centerDot, dot, -1)
  72. return name
  73. }
  74. // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
  75. // While Martini is in development mode, Recovery will also output the panic as HTML.
  76. func Recovery() HandlerFunc {
  77. return func(c *Context) {
  78. defer func() {
  79. if err := recover(); err != nil {
  80. stack := stack(3)
  81. log.Printf("PANIC: %s\n%s", err, stack)
  82. c.Writer.WriteHeader(http.StatusInternalServerError)
  83. }
  84. }()
  85. c.Next()
  86. }
  87. }