Skip to content

Commit 3dc7706

Browse files
committed
feat(loggin): improved http request panic handling
1 parent 9741af2 commit 3dc7706

File tree

5 files changed

+203
-20
lines changed

5 files changed

+203
-20
lines changed

pkg/api/api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ func Register(r *macaron.Macaron) {
240240
// metrics
241241
r.Get("/metrics", wrap(GetInternalMetrics))
242242

243+
// error test
244+
r.Get("/metrics/error", wrap(GenerateError))
245+
243246
}, reqSignedIn)
244247

245248
// admin api

pkg/api/metrics.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,9 @@ func GetInternalMetrics(c *middleware.Context) Response {
8787
},
8888
}
8989
}
90+
91+
// Genereates a index out of range error
92+
func GenerateError(c *middleware.Context) Response {
93+
var array []string
94+
return Json(200, array[20])
95+
}

pkg/cmd/grafana-server/web.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ func newMacaron() *macaron.Macaron {
2626
m := macaron.New()
2727

2828
m.Use(middleware.Logger())
29-
m.Use(macaron.Recovery())
29+
m.Use(middleware.Recovery())
3030

3131
if setting.EnableGzip {
3232
m.Use(middleware.Gziper())
3333
}
3434

3535
for _, route := range plugins.StaticRoutes {
3636
pluginRoute := path.Join("/public/plugins/", route.PluginId)
37-
log.Debug("Plugins: Adding route %s -> %s", pluginRoute, route.Directory)
37+
logger.Debug("Plugins: Adding route", "route", pluginRoute, "dir", route.Directory)
3838
mapStatic(m, route.Directory, "", pluginRoute)
3939
}
4040

pkg/log/log.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,61 +15,61 @@ import (
1515
"github.com/inconshreveable/log15"
1616
)
1717

18-
var rootLogger log15.Logger
18+
var Root log15.Logger
1919
var loggersToClose []DisposableHandler
2020

2121
func init() {
2222
loggersToClose = make([]DisposableHandler, 0)
23-
rootLogger = log15.Root()
23+
Root = log15.Root()
2424
}
2525

2626
func New(logger string, ctx ...interface{}) Logger {
2727
params := append([]interface{}{"logger", logger}, ctx...)
28-
return rootLogger.New(params...)
28+
return Root.New(params...)
2929
}
3030

3131
func Trace(format string, v ...interface{}) {
32-
rootLogger.Debug(fmt.Sprintf(format, v))
32+
Root.Debug(fmt.Sprintf(format, v))
3333
}
3434

3535
func Debug(format string, v ...interface{}) {
36-
rootLogger.Debug(fmt.Sprintf(format, v))
36+
Root.Debug(fmt.Sprintf(format, v))
3737
}
3838

3939
func Debug2(message string, v ...interface{}) {
40-
rootLogger.Debug(message, v...)
40+
Root.Debug(message, v...)
4141
}
4242

4343
func Info(format string, v ...interface{}) {
44-
rootLogger.Info(fmt.Sprintf(format, v))
44+
Root.Info(fmt.Sprintf(format, v))
4545
}
4646

4747
func Info2(message string, v ...interface{}) {
48-
rootLogger.Info(message, v...)
48+
Root.Info(message, v...)
4949
}
5050

5151
func Warn(format string, v ...interface{}) {
52-
rootLogger.Warn(fmt.Sprintf(format, v))
52+
Root.Warn(fmt.Sprintf(format, v))
5353
}
5454

5555
func Warn2(message string, v ...interface{}) {
56-
rootLogger.Warn(message, v...)
56+
Root.Warn(message, v...)
5757
}
5858

5959
func Error(skip int, format string, v ...interface{}) {
60-
rootLogger.Error(fmt.Sprintf(format, v))
60+
Root.Error(fmt.Sprintf(format, v))
6161
}
6262

6363
func Error2(message string, v ...interface{}) {
64-
rootLogger.Error(message, v...)
64+
Root.Error(message, v...)
6565
}
6666

6767
func Critical(skip int, format string, v ...interface{}) {
68-
rootLogger.Crit(fmt.Sprintf(format, v))
68+
Root.Crit(fmt.Sprintf(format, v))
6969
}
7070

7171
func Fatal(skip int, format string, v ...interface{}) {
72-
rootLogger.Crit(fmt.Sprintf(format, v))
72+
Root.Crit(fmt.Sprintf(format, v))
7373
Close()
7474
os.Exit(1)
7575
}
@@ -95,7 +95,7 @@ func getLogLevel(key string, defaultName string, cfg *ini.File) (string, log15.L
9595

9696
level, ok := logLevels[levelName]
9797
if !ok {
98-
rootLogger.Error("Unknown log level", "level", levelName)
98+
Root.Error("Unknown log level", "level", levelName)
9999
}
100100

101101
return levelName, level
@@ -111,7 +111,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
111111
mode = strings.TrimSpace(mode)
112112
sec, err := cfg.GetSection("log." + mode)
113113
if err != nil {
114-
rootLogger.Error("Unknown log mode", "mode", mode)
114+
Root.Error("Unknown log mode", "mode", mode)
115115
}
116116

117117
// Log level.
@@ -134,7 +134,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
134134
fileHandler.Init()
135135

136136
loggersToClose = append(loggersToClose, fileHandler)
137-
handlers = append(handlers, log15.LazyHandler(fileHandler))
137+
handlers = append(handlers, log15.LvlFilterHandler(level, fileHandler))
138138

139139
// case "conn":
140140
// LogConfigs[i] = util.DynMap{
@@ -170,5 +170,5 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
170170
}
171171
}
172172

173-
rootLogger.SetHandler(log15.MultiHandler(handlers...))
173+
Root.SetHandler(log15.MultiHandler(handlers...))
174174
}

pkg/middleware/recovery.go

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

0 commit comments

Comments
 (0)