recover.go 3.9 KB

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