recovery.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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. "runtime"
  21. "gopkg.in/macaron.v1"
  22. "github.com/grafana/grafana/pkg/log"
  23. m "github.com/grafana/grafana/pkg/models"
  24. "github.com/grafana/grafana/pkg/setting"
  25. )
  26. var (
  27. dunno = []byte("???")
  28. centerDot = []byte("·")
  29. dot = []byte(".")
  30. slash = []byte("/")
  31. )
  32. // stack returns a nicely formatted stack frame, skipping skip frames
  33. func stack(skip int) []byte {
  34. buf := new(bytes.Buffer) // the returned data
  35. // As we loop, we open files and read them. These variables record the currently
  36. // loaded file.
  37. var lines [][]byte
  38. var lastFile string
  39. for i := skip; ; i++ { // Skip the expected number of frames
  40. pc, file, line, ok := runtime.Caller(i)
  41. if !ok {
  42. break
  43. }
  44. // Print this much at least. If we can't find the source, it won't show.
  45. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
  46. if file != lastFile {
  47. data, err := ioutil.ReadFile(file)
  48. if err != nil {
  49. continue
  50. }
  51. lines = bytes.Split(data, []byte{'\n'})
  52. lastFile = file
  53. }
  54. fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
  55. }
  56. return buf.Bytes()
  57. }
  58. // source returns a space-trimmed slice of the n'th line.
  59. func source(lines [][]byte, n int) []byte {
  60. n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
  61. if n < 0 || n >= len(lines) {
  62. return dunno
  63. }
  64. return bytes.TrimSpace(lines[n])
  65. }
  66. // function returns, if possible, the name of the function containing the PC.
  67. func function(pc uintptr) []byte {
  68. fn := runtime.FuncForPC(pc)
  69. if fn == nil {
  70. return dunno
  71. }
  72. name := []byte(fn.Name())
  73. // The name includes the path name to the package, which is unnecessary
  74. // since the file name is already included. Plus, it has center dots.
  75. // That is, we see
  76. // runtime/debug.*T·ptrmethod
  77. // and want
  78. // *T.ptrmethod
  79. // Also the package path might contains dot (e.g. code.google.com/...),
  80. // so first eliminate the path prefix
  81. if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
  82. name = name[lastslash+1:]
  83. }
  84. if period := bytes.Index(name, dot); period >= 0 {
  85. name = name[period+1:]
  86. }
  87. name = bytes.Replace(name, centerDot, dot, -1)
  88. return name
  89. }
  90. // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
  91. // While Martini is in development mode, Recovery will also output the panic as HTML.
  92. func Recovery() macaron.Handler {
  93. return func(c *macaron.Context) {
  94. defer func() {
  95. if err := recover(); err != nil {
  96. stack := stack(3)
  97. panicLogger := log.Root
  98. // try to get request logger
  99. if ctx, ok := c.Data["ctx"]; ok {
  100. ctxTyped := ctx.(*m.ReqContext)
  101. panicLogger = ctxTyped.Logger
  102. }
  103. panicLogger.Error("Request error", "error", err, "stack", string(stack))
  104. c.Data["Title"] = "Server Error"
  105. c.Data["AppSubUrl"] = setting.AppSubUrl
  106. c.Data["Theme"] = setting.DefaultTheme
  107. if setting.Env == setting.DEV {
  108. if theErr, ok := err.(error); ok {
  109. c.Data["Title"] = theErr.Error()
  110. }
  111. c.Data["ErrorMsg"] = string(stack)
  112. }
  113. ctx, ok := c.Data["ctx"].(*m.ReqContext)
  114. if ok && ctx.IsApiRequest() {
  115. resp := make(map[string]interface{})
  116. resp["message"] = "Internal Server Error - Check the Grafana server logs for the detailed error message."
  117. if c.Data["ErrorMsg"] != nil {
  118. resp["error"] = fmt.Sprintf("%v - %v", c.Data["Title"], c.Data["ErrorMsg"])
  119. } else {
  120. resp["error"] = c.Data["Title"]
  121. }
  122. c.JSON(500, resp)
  123. } else {
  124. c.HTML(500, setting.ERR_TEMPLATE_NAME)
  125. }
  126. }
  127. }()
  128. c.Next()
  129. }
  130. }