Merge lp:~pedronis/ubuntu-push/panic-to-500 into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Merged
Approved by: Samuele Pedroni
Approved revision: 10
Merged at revision: 8
Proposed branch: lp:~pedronis/ubuntu-push/panic-to-500
Merge into: lp:ubuntu-push
Diff against target: 262 lines (+124/-26)
8 files modified
logger/logger.go (+9/-10)
logger/logger_test.go (+20/-8)
server/api/handlers.go (+1/-5)
server/api/middleware.go (+40/-0)
server/api/middleware_test.go (+46/-0)
server/dev/server.go (+1/-0)
server/listener/listener.go (+5/-1)
server/listener/listener_test.go (+2/-2)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/panic-to-500
Reviewer Review Type Date Requested Status
John Lenton (community) Approve
Review via email: mp+201959@code.launchpad.net

Commit message

introduce middleware in the http handling that recovers and converts panics into 500 errors, profit (remove 500 ad hoc code)

Description of the change

introduce middleware in the http handling that recovers and converts panics into 500 errors,

in the process change logger.Recoverf -> logger.PanicStackf which is less magical but more flexible,

profit (remove ad hoc 500 code)

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) wrote :

Neat.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'logger/logger.go'
2--- logger/logger.go 2014-01-14 15:35:20 +0000
3+++ logger/logger.go 2014-01-16 16:26:46 +0000
4@@ -31,8 +31,9 @@
5 Errorf(format string, v ...interface{})
6 // Fatalf logs an error and exists the program with os.Exit(1).
7 Fatalf(format string, v ...interface{})
8- // Recoverf recover from a possible panic and logs it.
9- Recoverf(format string, v ...interface{})
10+ // PanicStackf logs a error message and a stacktrace, for use
11+ // in panic recovery.
12+ PanicStackf(format string, v ...interface{})
13 // Infof logs a info message.
14 Infof(format string, v ...interface{})
15 // Debugf logs a debug message.
16@@ -77,14 +78,12 @@
17 osExit(1)
18 }
19
20-func (lg *simpleLogger) Recoverf(format string, v ...interface{}) {
21- if err := recover(); err != nil {
22- msg := fmt.Sprintf(format, v...)
23- stack := make([]byte, 8*1024) // Stack writes less but doesn't fail
24- stackWritten := runtime.Stack(stack, false)
25- stack = stack[:stackWritten]
26- lg.Printf("ERROR panic %v!! %s:\n%s", err, msg, stack)
27- }
28+func (lg *simpleLogger) PanicStackf(format string, v ...interface{}) {
29+ msg := fmt.Sprintf(format, v...)
30+ stack := make([]byte, 8*1024) // Stack writes less but doesn't fail
31+ stackWritten := runtime.Stack(stack, false)
32+ stack = stack[:stackWritten]
33+ lg.Printf("ERROR(PANIC) %s:\n%s", msg, stack)
34 }
35
36 func (lg *simpleLogger) Infof(format string, v ...interface{}) {
37
38=== modified file 'logger/logger_test.go'
39--- logger/logger_test.go 2014-01-14 15:35:20 +0000
40+++ logger/logger_test.go 2014-01-16 16:26:46 +0000
41@@ -18,8 +18,10 @@
42
43 import (
44 "bytes"
45+ "fmt"
46 . "launchpad.net/gocheck"
47 "os"
48+ "runtime"
49 "testing"
50 )
51
52@@ -95,23 +97,33 @@
53 c.Check(buf.String(), Matches, `.* ERROR e5\n.* DEBUG d5\n.* INFO i5\n`)
54 }
55
56-func panicAndRecover(logger Logger, n int, doPanic bool) {
57- defer logger.Recoverf("%v %d", "panic", n)
58+func panicAndRecover(logger Logger, n int, doPanic bool, line *int, ok *bool) {
59+ defer func() {
60+ if err := recover(); err != nil {
61+ logger.PanicStackf("%v %d", err, n)
62+ }
63+ }()
64+ _, _, *line, *ok = runtime.Caller(0)
65 if doPanic {
66- panic("Troubles")
67+ panic("Troubles") // @ line + 2
68 }
69 }
70
71-func (s *loggerSuite) TestRecoverf(c *C) {
72+func (s *loggerSuite) TestPanicStackfPanicScenario(c *C) {
73 buf := &bytes.Buffer{}
74 logger := NewSimpleLogger(buf, "error")
75- panicAndRecover(logger, 6, true)
76- c.Check(buf.String(), Matches, "(?s).* ERROR panic Troubles!! panic 6:.*panicAndRecover.*")
77+ var line int
78+ var ok bool
79+ panicAndRecover(logger, 6, true, &line, &ok)
80+ c.Assert(ok, Equals, true)
81+ c.Check(buf.String(), Matches, fmt.Sprintf("(?s).* ERROR\\(PANIC\\) Troubles 6:.*panicAndRecover.*logger_test.go:%d.*", line+2))
82 }
83
84-func (s *loggerSuite) TestRecoverfNop(c *C) {
85+func (s *loggerSuite) TestPanicStackfNoPanicScenario(c *C) {
86 buf := &bytes.Buffer{}
87 logger := NewSimpleLogger(buf, "error")
88- panicAndRecover(logger, 6, false)
89+ var line int
90+ var ok bool
91+ panicAndRecover(logger, 6, false, &line, &ok)
92 c.Check(buf.String(), Equals, "")
93 }
94
95=== modified file 'server/api/handlers.go'
96--- server/api/handlers.go 2014-01-14 15:35:20 +0000
97+++ server/api/handlers.go 2014-01-16 16:26:46 +0000
98@@ -25,7 +25,6 @@
99 "launchpad.net/ubuntu-push/logger"
100 "launchpad.net/ubuntu-push/server/broker"
101 "launchpad.net/ubuntu-push/server/store"
102- "log"
103 "net/http"
104 )
105
106@@ -125,10 +124,7 @@
107 func respondError(writer http.ResponseWriter, apiErr *APIError) {
108 wireError, err := json.Marshal(apiErr)
109 if err != nil {
110- // xxx general 500 framework
111- log.Println("The provided string could not be marshaled into an error:", err)
112- http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
113- return
114+ panic(fmt.Errorf("couldn't marshal our own errors: %v", err))
115 }
116 writer.Header().Set("Content-type", JSONMediaType)
117 writer.WriteHeader(apiErr.StatusCode)
118
119=== added file 'server/api/middleware.go'
120--- server/api/middleware.go 1970-01-01 00:00:00 +0000
121+++ server/api/middleware.go 2014-01-16 16:26:46 +0000
122@@ -0,0 +1,40 @@
123+/*
124+ Copyright 2013-2014 Canonical Ltd.
125+
126+ This program is free software: you can redistribute it and/or modify it
127+ under the terms of the GNU General Public License version 3, as published
128+ by the Free Software Foundation.
129+
130+ This program is distributed in the hope that it will be useful, but
131+ WITHOUT ANY WARRANTY; without even the implied warranties of
132+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
133+ PURPOSE. See the GNU General Public License for more details.
134+
135+ You should have received a copy of the GNU General Public License along
136+ with this program. If not, see <http://www.gnu.org/licenses/>.
137+*/
138+
139+package api
140+
141+import (
142+ "fmt"
143+ "launchpad.net/ubuntu-push/logger"
144+ "net/http"
145+)
146+
147+// PanicTo500Handler wraps another handler such that panics are recovered
148+// and 500 reported.
149+func PanicTo500Handler(h http.Handler, logger logger.Logger) http.Handler {
150+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
151+ defer func() {
152+ if err := recover(); err != nil {
153+ logger.PanicStackf("serving http: %v", err)
154+ // best effort
155+ w.Header().Set("Content-Type", "application/json")
156+ w.WriteHeader(500)
157+ fmt.Fprintf(w, `{"error":"internal","message":"INTERNAL SERVER ERROR"}`)
158+ }
159+ }()
160+ h.ServeHTTP(w, req)
161+ })
162+}
163
164=== added file 'server/api/middleware_test.go'
165--- server/api/middleware_test.go 1970-01-01 00:00:00 +0000
166+++ server/api/middleware_test.go 2014-01-16 16:26:46 +0000
167@@ -0,0 +1,46 @@
168+/*
169+ Copyright 2013-2014 Canonical Ltd.
170+
171+ This program is free software: you can redistribute it and/or modify it
172+ under the terms of the GNU General Public License version 3, as published
173+ by the Free Software Foundation.
174+
175+ This program is distributed in the hope that it will be useful, but
176+ WITHOUT ANY WARRANTY; without even the implied warranties of
177+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
178+ PURPOSE. See the GNU General Public License for more details.
179+
180+ You should have received a copy of the GNU General Public License along
181+ with this program. If not, see <http://www.gnu.org/licenses/>.
182+*/
183+
184+package api
185+
186+import (
187+ "bytes"
188+ . "launchpad.net/gocheck"
189+ "launchpad.net/ubuntu-push/logger"
190+ // "launchpad.net/ubuntu-push/testing"
191+ "net/http"
192+ "net/http/httptest"
193+)
194+
195+type middlewareSuite struct{}
196+
197+var _ = Suite(&middlewareSuite{})
198+
199+func (s *middlewareSuite) TestPanicTo500Handler(c *C) {
200+ buf := &bytes.Buffer{}
201+ logger := logger.NewSimpleLogger(buf, "debug")
202+ panicking := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
203+ panic("panic in handler")
204+ })
205+
206+ h := PanicTo500Handler(panicking, logger)
207+ w := httptest.NewRecorder()
208+ h.ServeHTTP(w, nil)
209+ c.Check(w.Code, Equals, 500)
210+ c.Check(buf.String(), Matches, "(?s).* ERROR\\(PANIC\\) serving http: panic in handler:.*")
211+ c.Check(w.Header().Get("Content-Type"), Equals, "application/json")
212+ c.Check(w.Body.String(), Equals, `{"error":"internal","message":"INTERNAL SERVER ERROR"}`)
213+}
214
215=== modified file 'server/dev/server.go'
216--- server/dev/server.go 2014-01-14 15:35:20 +0000
217+++ server/dev/server.go 2014-01-16 16:26:46 +0000
218@@ -55,6 +55,7 @@
219 logger.Fatalf("start http listening: %v", err)
220 }
221 handler := api.MakeHandlersMux(sto, broker, logger)
222+ handler = api.PanicTo500Handler(handler, logger)
223 logger.Infof("listening for http on %v", httpLst.Addr())
224 go func() {
225 err := RunHTTPServe(httpLst, handler, cfg)
226
227=== modified file 'server/listener/listener.go'
228--- server/listener/listener.go 2014-01-14 15:35:20 +0000
229+++ server/listener/listener.go 2014-01-16 16:26:46 +0000
230@@ -79,7 +79,11 @@
231 return err
232 }
233 go func() {
234- defer logger.Recoverf("terminating device connection")
235+ defer func() {
236+ if err := recover(); err != nil {
237+ logger.PanicStackf("terminating device connection on: %v", err)
238+ }
239+ }()
240 session(conn)
241 }()
242 }
243
244=== modified file 'server/listener/listener_test.go'
245--- server/listener/listener_test.go 2014-01-14 15:35:20 +0000
246+++ server/listener/listener_test.go 2014-01-16 16:26:46 +0000
247@@ -227,7 +227,7 @@
248 go func() {
249 errCh <- lst.AcceptLoop(func(conn net.Conn) error {
250 defer conn.Close()
251- panic("session panic")
252+ panic("session crash")
253 }, logger)
254 }()
255 listenerAddr := lst.Addr().String()
256@@ -235,5 +235,5 @@
257 c.Assert(err, Not(IsNil))
258 lst.Close()
259 c.Check(<-errCh, ErrorMatches, ".*use of closed.*")
260- c.Check(buf.String(), Matches, "(?s).*session panic!! terminating device connection:\n.*AcceptLoop.*")
261+ c.Check(buf.String(), Matches, "(?s).* ERROR\\(PANIC\\) terminating device connection on: session crash:.*AcceptLoop.*")
262 }

Subscribers

People subscribed via source and target branches