recovery.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. // Copyright 2013 Martini Authors
  2. // Copyright 2014 The Macaron Authors
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  5. // not use this file except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. package macaron
  16. import (
  17. "bytes"
  18. "fmt"
  19. "io/ioutil"
  20. "log"
  21. "net/http"
  22. "runtime"
  23. "github.com/go-macaron/inject"
  24. )
  25. const (
  26. panicHtml = `<html>
  27. <head><title>PANIC: %s</title>
  28. <meta charset="utf-8" />
  29. <style type="text/css">
  30. html, body {
  31. font-family: "Roboto", sans-serif;
  32. color: #333333;
  33. background-color: #ea5343;
  34. margin: 0px;
  35. }
  36. h1 {
  37. color: #d04526;
  38. background-color: #ffffff;
  39. padding: 20px;
  40. border-bottom: 1px dashed #2b3848;
  41. }
  42. pre {
  43. margin: 20px;
  44. padding: 20px;
  45. border: 2px solid #2b3848;
  46. background-color: #ffffff;
  47. white-space: pre-wrap; /* css-3 */
  48. white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
  49. white-space: -pre-wrap; /* Opera 4-6 */
  50. white-space: -o-pre-wrap; /* Opera 7 */
  51. word-wrap: break-word; /* Internet Explorer 5.5+ */
  52. }
  53. </style>
  54. </head><body>
  55. <h1>PANIC</h1>
  56. <pre style="font-weight: bold;">%s</pre>
  57. <pre>%s</pre>
  58. </body>
  59. </html>`
  60. )
  61. var (
  62. dunno = []byte("???")
  63. centerDot = []byte("·")
  64. dot = []byte(".")
  65. slash = []byte("/")
  66. )
  67. // stack returns a nicely formated stack frame, skipping skip frames
  68. func stack(skip int) []byte {
  69. buf := new(bytes.Buffer) // the returned data
  70. // As we loop, we open files and read them. These variables record the currently
  71. // loaded file.
  72. var lines [][]byte
  73. var lastFile string
  74. for i := skip; ; i++ { // Skip the expected number of frames
  75. pc, file, line, ok := runtime.Caller(i)
  76. if !ok {
  77. break
  78. }
  79. // Print this much at least. If we can't find the source, it won't show.
  80. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
  81. if file != lastFile {
  82. data, err := ioutil.ReadFile(file)
  83. if err != nil {
  84. continue
  85. }
  86. lines = bytes.Split(data, []byte{'\n'})
  87. lastFile = file
  88. }
  89. fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
  90. }
  91. return buf.Bytes()
  92. }
  93. // source returns a space-trimmed slice of the n'th line.
  94. func source(lines [][]byte, n int) []byte {
  95. n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
  96. if n < 0 || n >= len(lines) {
  97. return dunno
  98. }
  99. return bytes.TrimSpace(lines[n])
  100. }
  101. // function returns, if possible, the name of the function containing the PC.
  102. func function(pc uintptr) []byte {
  103. fn := runtime.FuncForPC(pc)
  104. if fn == nil {
  105. return dunno
  106. }
  107. name := []byte(fn.Name())
  108. // The name includes the path name to the package, which is unnecessary
  109. // since the file name is already included. Plus, it has center dots.
  110. // That is, we see
  111. // runtime/debug.*T·ptrmethod
  112. // and want
  113. // *T.ptrmethod
  114. // Also the package path might contains dot (e.g. code.google.com/...),
  115. // so first eliminate the path prefix
  116. if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
  117. name = name[lastslash+1:]
  118. }
  119. if period := bytes.Index(name, dot); period >= 0 {
  120. name = name[period+1:]
  121. }
  122. name = bytes.Replace(name, centerDot, dot, -1)
  123. return name
  124. }
  125. // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
  126. // While Martini is in development mode, Recovery will also output the panic as HTML.
  127. func Recovery() Handler {
  128. return func(c *Context, log *log.Logger) {
  129. defer func() {
  130. if err := recover(); err != nil {
  131. stack := stack(3)
  132. log.Printf("PANIC: %s\n%s", err, stack)
  133. // Lookup the current responsewriter
  134. val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
  135. res := val.Interface().(http.ResponseWriter)
  136. // respond with panic message while in development mode
  137. var body []byte
  138. if Env == DEV {
  139. res.Header().Set("Content-Type", "text/html")
  140. body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
  141. }
  142. res.WriteHeader(http.StatusInternalServerError)
  143. if nil != body {
  144. res.Write(body)
  145. }
  146. }
  147. }()
  148. c.Next()
  149. }
  150. }