recovery.go 2.6 KB

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