recovery.go 4.6 KB

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