recovery.go 3.1 KB

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