recovery.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. package negroni
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "os"
  7. "runtime"
  8. "runtime/debug"
  9. "text/template"
  10. )
  11. const (
  12. panicText = "PANIC: %s\n%s"
  13. panicHTML = `<html>
  14. <head><title>PANIC: {{.RecoveredPanic}}</title></head>
  15. <style type="text/css">
  16. html, body {
  17. font-family: Helvetica, Arial, Sans;
  18. color: #333333;
  19. background-color: #ffffff;
  20. margin: 0px;
  21. }
  22. h1 {
  23. color: #ffffff;
  24. background-color: #f14c4c;
  25. padding: 20px;
  26. border-bottom: 1px solid #2b3848;
  27. }
  28. .block {
  29. margin: 2em;
  30. }
  31. .panic-interface {
  32. }
  33. .panic-stack-raw pre {
  34. padding: 1em;
  35. background: #f6f8fa;
  36. border: dashed 1px;
  37. }
  38. .panic-interface-title {
  39. font-weight: bold;
  40. }
  41. </style>
  42. <body>
  43. <h1>Negroni - PANIC</h1>
  44. <div class="panic-interface block">
  45. <h3>{{.RequestDescription}}</h3>
  46. <span class="panic-interface-title">Runtime error:</span> <span class="panic-interface-element">{{.RecoveredPanic}}</span>
  47. </div>
  48. {{ if .Stack }}
  49. <div class="panic-stack-raw block">
  50. <h3>Runtime Stack</h3>
  51. <pre>{{.StackAsString}}</pre>
  52. </div>
  53. {{ end }}
  54. </body>
  55. </html>`
  56. nilRequestMessage = "Request is nil"
  57. )
  58. var panicHTMLTemplate = template.Must(template.New("PanicPage").Parse(panicHTML))
  59. // PanicInformation contains all
  60. // elements for printing stack informations.
  61. type PanicInformation struct {
  62. RecoveredPanic interface{}
  63. Stack []byte
  64. Request *http.Request
  65. }
  66. // StackAsString returns a printable version of the stack
  67. func (p *PanicInformation) StackAsString() string {
  68. return string(p.Stack)
  69. }
  70. // RequestDescription returns a printable description of the url
  71. func (p *PanicInformation) RequestDescription() string {
  72. if p.Request == nil {
  73. return nilRequestMessage
  74. }
  75. var queryOutput string
  76. if p.Request.URL.RawQuery != "" {
  77. queryOutput = "?" + p.Request.URL.RawQuery
  78. }
  79. return fmt.Sprintf("%s %s%s", p.Request.Method, p.Request.URL.Path, queryOutput)
  80. }
  81. // PanicFormatter is an interface on object can implement
  82. // to be able to output the stack trace
  83. type PanicFormatter interface {
  84. // FormatPanicError output the stack for a given answer/response.
  85. // In case the the middleware should not output the stack trace,
  86. // the field `Stack` of the passed `PanicInformation` instance equals `[]byte{}`.
  87. FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation)
  88. }
  89. // TextPanicFormatter output the stack
  90. // as simple text on os.Stdout. If no `Content-Type` is set,
  91. // it will output the data as `text/plain; charset=utf-8`.
  92. // Otherwise, the origin `Content-Type` is kept.
  93. type TextPanicFormatter struct{}
  94. func (t *TextPanicFormatter) FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation) {
  95. if rw.Header().Get("Content-Type") == "" {
  96. rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
  97. }
  98. fmt.Fprintf(rw, panicText, infos.RecoveredPanic, infos.Stack)
  99. }
  100. // HTMLPanicFormatter output the stack inside
  101. // an HTML page. This has been largely inspired by
  102. // https://github.com/go-martini/martini/pull/156/commits.
  103. type HTMLPanicFormatter struct{}
  104. func (t *HTMLPanicFormatter) FormatPanicError(rw http.ResponseWriter, r *http.Request, infos *PanicInformation) {
  105. if rw.Header().Get("Content-Type") == "" {
  106. rw.Header().Set("Content-Type", "text/html; charset=utf-8")
  107. }
  108. panicHTMLTemplate.Execute(rw, infos)
  109. }
  110. // Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one.
  111. type Recovery struct {
  112. Logger ALogger
  113. PrintStack bool
  114. PanicHandlerFunc func(*PanicInformation)
  115. StackAll bool
  116. StackSize int
  117. Formatter PanicFormatter
  118. // Deprecated: Use PanicHandlerFunc instead to receive panic
  119. // error with additional information (see PanicInformation)
  120. ErrorHandlerFunc func(interface{})
  121. }
  122. // NewRecovery returns a new instance of Recovery
  123. func NewRecovery() *Recovery {
  124. return &Recovery{
  125. Logger: log.New(os.Stdout, "[negroni] ", 0),
  126. PrintStack: true,
  127. StackAll: false,
  128. StackSize: 1024 * 8,
  129. Formatter: &TextPanicFormatter{},
  130. }
  131. }
  132. func (rec *Recovery) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  133. defer func() {
  134. if err := recover(); err != nil {
  135. rw.WriteHeader(http.StatusInternalServerError)
  136. stack := make([]byte, rec.StackSize)
  137. stack = stack[:runtime.Stack(stack, rec.StackAll)]
  138. infos := &PanicInformation{RecoveredPanic: err, Request: r}
  139. if rec.PrintStack {
  140. infos.Stack = stack
  141. }
  142. rec.Logger.Printf(panicText, err, stack)
  143. rec.Formatter.FormatPanicError(rw, r, infos)
  144. if rec.ErrorHandlerFunc != nil {
  145. func() {
  146. defer func() {
  147. if err := recover(); err != nil {
  148. rec.Logger.Printf("provided ErrorHandlerFunc panic'd: %s, trace:\n%s", err, debug.Stack())
  149. rec.Logger.Printf("%s\n", debug.Stack())
  150. }
  151. }()
  152. rec.ErrorHandlerFunc(err)
  153. }()
  154. }
  155. if rec.PanicHandlerFunc != nil {
  156. func() {
  157. defer func() {
  158. if err := recover(); err != nil {
  159. rec.Logger.Printf("provided PanicHandlerFunc panic'd: %s, trace:\n%s", err, debug.Stack())
  160. rec.Logger.Printf("%s\n", debug.Stack())
  161. }
  162. }()
  163. rec.PanicHandlerFunc(infos)
  164. }()
  165. }
  166. }
  167. }()
  168. next(rw, r)
  169. }