另客网go项目公用的代码库

recovery.go 3.1KB

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