recovery.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package martini
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "net/http"
  8. "runtime"
  9. "github.com/codegangsta/inject"
  10. )
  11. const (
  12. panicHtml = `<html>
  13. <head><title>PANIC: %s</title>
  14. <style type="text/css">
  15. html, body {
  16. font-family: "Roboto", sans-serif;
  17. color: #333333;
  18. background-color: #ea5343;
  19. margin: 0px;
  20. }
  21. h1 {
  22. color: #d04526;
  23. background-color: #ffffff;
  24. padding: 20px;
  25. border-bottom: 1px dashed #2b3848;
  26. }
  27. pre {
  28. margin: 20px;
  29. padding: 20px;
  30. border: 2px solid #2b3848;
  31. background-color: #ffffff;
  32. }
  33. </style>
  34. </head><body>
  35. <h1>PANIC</h1>
  36. <pre style="font-weight: bold;">%s</pre>
  37. <pre>%s</pre>
  38. </body>
  39. </html>`
  40. )
  41. var (
  42. dunno = []byte("???")
  43. centerDot = []byte("·")
  44. dot = []byte(".")
  45. slash = []byte("/")
  46. )
  47. // stack returns a nicely formated stack frame, skipping skip frames
  48. func stack(skip int) []byte {
  49. buf := new(bytes.Buffer) // the returned data
  50. // As we loop, we open files and read them. These variables record the currently
  51. // loaded file.
  52. var lines [][]byte
  53. var lastFile string
  54. for i := skip; ; i++ { // Skip the expected number of frames
  55. pc, file, line, ok := runtime.Caller(i)
  56. if !ok {
  57. break
  58. }
  59. // Print this much at least. If we can't find the source, it won't show.
  60. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
  61. if file != lastFile {
  62. data, err := ioutil.ReadFile(file)
  63. if err != nil {
  64. continue
  65. }
  66. lines = bytes.Split(data, []byte{'\n'})
  67. lastFile = file
  68. }
  69. fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
  70. }
  71. return buf.Bytes()
  72. }
  73. // source returns a space-trimmed slice of the n'th line.
  74. func source(lines [][]byte, n int) []byte {
  75. n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
  76. if n < 0 || n >= len(lines) {
  77. return dunno
  78. }
  79. return bytes.TrimSpace(lines[n])
  80. }
  81. // function returns, if possible, the name of the function containing the PC.
  82. func function(pc uintptr) []byte {
  83. fn := runtime.FuncForPC(pc)
  84. if fn == nil {
  85. return dunno
  86. }
  87. name := []byte(fn.Name())
  88. // The name includes the path name to the package, which is unnecessary
  89. // since the file name is already included. Plus, it has center dots.
  90. // That is, we see
  91. // runtime/debug.*T·ptrmethod
  92. // and want
  93. // *T.ptrmethod
  94. // Also the package path might contains dot (e.g. code.google.com/...),
  95. // so first eliminate the path prefix
  96. if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
  97. name = name[lastslash+1:]
  98. }
  99. if period := bytes.Index(name, dot); period >= 0 {
  100. name = name[period+1:]
  101. }
  102. name = bytes.Replace(name, centerDot, dot, -1)
  103. return name
  104. }
  105. // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
  106. // While Martini is in development mode, Recovery will also output the panic as HTML.
  107. func Recovery() Handler {
  108. return func(c Context, log *log.Logger) {
  109. defer func() {
  110. if err := recover(); err != nil {
  111. stack := stack(3)
  112. log.Printf("PANIC: %s\n%s", err, stack)
  113. // Lookup the current responsewriter
  114. val := c.Get(inject.InterfaceOf((*http.ResponseWriter)(nil)))
  115. res := val.Interface().(http.ResponseWriter)
  116. // respond with panic message while in development mode
  117. var body []byte
  118. if Env == Dev {
  119. res.Header().Set("Content-Type", "text/html")
  120. body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
  121. } else {
  122. body = []byte("500 Internal Server Error")
  123. }
  124. res.WriteHeader(http.StatusInternalServerError)
  125. if nil != body {
  126. res.Write(body)
  127. }
  128. }
  129. }()
  130. c.Next()
  131. }
  132. }