recovery.go 4.2 KB

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