recovery.go 2.7 KB

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