Log stack when panic

This commit is contained in:
Ludovic Fernandez 2018-10-12 15:40:03 +02:00 committed by Traefiker Bot
parent 2c7b7cd6ca
commit 0335f6fba9
4 changed files with 43 additions and 19 deletions

View file

@ -2,6 +2,7 @@ package middlewares
import ( import (
"net/http" "net/http"
"runtime"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/urfave/negroni" "github.com/urfave/negroni"
@ -10,7 +11,7 @@ import (
// RecoverHandler recovers from a panic in http handlers // RecoverHandler recovers from a panic in http handlers
func RecoverHandler(next http.Handler) http.Handler { func RecoverHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) { fn := func(w http.ResponseWriter, r *http.Request) {
defer recoverFunc(w) defer recoverFunc(w, r)
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
return http.HandlerFunc(fn) return http.HandlerFunc(fn)
@ -19,23 +20,31 @@ func RecoverHandler(next http.Handler) http.Handler {
// NegroniRecoverHandler recovers from a panic in negroni handlers // NegroniRecoverHandler recovers from a panic in negroni handlers
func NegroniRecoverHandler() negroni.Handler { func NegroniRecoverHandler() negroni.Handler {
fn := func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { fn := func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
defer recoverFunc(w) defer recoverFunc(w, r)
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
return negroni.HandlerFunc(fn) return negroni.HandlerFunc(fn)
} }
func recoverFunc(w http.ResponseWriter) { func recoverFunc(w http.ResponseWriter, r *http.Request) {
if err := recover(); err != nil { if err := recover(); err != nil {
if shouldLogPanic(err) { if !shouldLogPanic(err) {
log.Errorf("Recovered from panic in http handler: %+v", err) log.Debugf("Request has been aborted [%s - %s]: %v", r.RemoteAddr, r.URL, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return
} else {
log.Debugf("HTTP handler has been aborted: %v", err)
} }
log.Errorf("Recovered from panic in HTTP handler [%s - %s]: %+v", r.RemoteAddr, r.URL, err)
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Errorf("Stack: %s", buf)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
} }
} }
// https://github.com/golang/go/blob/a0d6420d8be2ae7164797051ec74fa2a2df466a1/src/net/http/server.go#L1761-L1775
// https://github.com/golang/go/blob/c33153f7b416c03983324b3e8f869ce1116d84bc/src/net/http/httputil/reverseproxy.go#L284 // https://github.com/golang/go/blob/c33153f7b416c03983324b3e8f869ce1116d84bc/src/net/http/httputil/reverseproxy.go#L284
func shouldLogPanic(panicValue interface{}) bool { func shouldLogPanic(panicValue interface{}) bool {
return panicValue != nil && panicValue != http.ErrAbortHandler return panicValue != nil && panicValue != http.ErrAbortHandler

View file

@ -145,7 +145,7 @@ func GoWithRecover(goroutine func(), customRecover func(err interface{})) {
func defaultRecoverGoroutine(err interface{}) { func defaultRecoverGoroutine(err interface{}) {
log.Errorf("Error in Go routine: %s", err) log.Errorf("Error in Go routine: %s", err)
debug.PrintStack() log.Errorf("Stack: %s", debug.Stack())
} }
// OperationWithRecover wrap a backoff operation in a Recover // OperationWithRecover wrap a backoff operation in a Recover

View file

@ -13,9 +13,12 @@ import (
func TestNewPoolContext(t *testing.T) { func TestNewPoolContext(t *testing.T) {
type testKeyType string type testKeyType string
testKey := testKeyType("test") testKey := testKeyType("test")
ctx := context.WithValue(context.Background(), testKey, "test") ctx := context.WithValue(context.Background(), testKey, "test")
p := NewPool(ctx) p := NewPool(ctx)
retCtx := p.Ctx() retCtx := p.Ctx()
retCtxVal, ok := retCtx.Value(testKey).(string) retCtxVal, ok := retCtx.Value(testKey).(string)
if !ok || retCtxVal != "test" { if !ok || retCtxVal != "test" {
t.Errorf("Pool.Ctx() did not return a derived context, got %#v, expected context with test value", retCtx) t.Errorf("Pool.Ctx() did not return a derived context, got %#v, expected context with test value", retCtx)
@ -52,7 +55,8 @@ func (tr *fakeRoutine) routine(stop chan bool) {
func TestPoolWithCtx(t *testing.T) { func TestPoolWithCtx(t *testing.T) {
testRoutine := newFakeRoutine() testRoutine := newFakeRoutine()
tt := []struct {
testCases := []struct {
desc string desc string
fn func(*Pool) fn func(*Pool)
}{ }{
@ -70,19 +74,20 @@ func TestPoolWithCtx(t *testing.T) {
}, },
}, },
} }
for _, tc := range tt {
tc := tc for _, test := range testCases {
t.Run(tc.desc, func(t *testing.T) { test := test
t.Run(test.desc, func(t *testing.T) {
// These subtests cannot be run in parallel, since the testRoutine // These subtests cannot be run in parallel, since the testRoutine
// is shared across the subtests. // is shared across the subtests.
p := NewPool(context.Background()) p := NewPool(context.Background())
timer := time.NewTimer(500 * time.Millisecond) timer := time.NewTimer(500 * time.Millisecond)
defer timer.Stop() defer timer.Stop()
tc.fn(p) test.fn(p)
defer p.Cleanup() defer p.Cleanup()
if len(p.routinesCtx) != 1 { if len(p.routinesCtx) != 1 {
t.Fatalf("After %s, Pool did have %d goroutineCtxs, expected 1", tc.desc, len(p.routinesCtx)) t.Fatalf("After %s, Pool did have %d goroutineCtxs, expected 1", test.desc, len(p.routinesCtx))
} }
testDone := make(chan bool, 1) testDone := make(chan bool, 1)
@ -91,6 +96,7 @@ func TestPoolWithCtx(t *testing.T) {
p.Cleanup() p.Cleanup()
testDone <- true testDone <- true
}() }()
select { select {
case <-timer.C: case <-timer.C:
testRoutine.Lock() testRoutine.Lock()
@ -105,8 +111,9 @@ func TestPoolWithCtx(t *testing.T) {
func TestPoolWithStopChan(t *testing.T) { func TestPoolWithStopChan(t *testing.T) {
testRoutine := newFakeRoutine() testRoutine := newFakeRoutine()
ctx := context.Background()
p := NewPool(ctx) p := NewPool(context.Background())
timer := time.NewTimer(500 * time.Millisecond) timer := time.NewTimer(500 * time.Millisecond)
defer timer.Stop() defer timer.Stop()
@ -121,6 +128,7 @@ func TestPoolWithStopChan(t *testing.T) {
p.Cleanup() p.Cleanup()
testDone <- true testDone <- true
}() }()
select { select {
case <-timer.C: case <-timer.C:
testRoutine.Lock() testRoutine.Lock()
@ -133,8 +141,9 @@ func TestPoolWithStopChan(t *testing.T) {
func TestPoolStartWithStopChan(t *testing.T) { func TestPoolStartWithStopChan(t *testing.T) {
testRoutine := newFakeRoutine() testRoutine := newFakeRoutine()
ctx := context.Background()
p := NewPool(ctx) p := NewPool(context.Background())
timer := time.NewTimer(500 * time.Millisecond) timer := time.NewTimer(500 * time.Millisecond)
defer timer.Stop() defer timer.Stop()

View file

@ -5,19 +5,25 @@ import "testing"
func TestSafe(t *testing.T) { func TestSafe(t *testing.T) {
const ts1 = "test1" const ts1 = "test1"
const ts2 = "test2" const ts2 = "test2"
s := New(ts1) s := New(ts1)
result, ok := s.Get().(string) result, ok := s.Get().(string)
if !ok { if !ok {
t.Fatalf("Safe.Get() failed, got type '%T', expected string", s.Get()) t.Fatalf("Safe.Get() failed, got type '%T', expected string", s.Get())
} }
if result != ts1 { if result != ts1 {
t.Errorf("Safe.Get() failed, got '%s', expected '%s'", result, ts1) t.Errorf("Safe.Get() failed, got '%s', expected '%s'", result, ts1)
} }
s.Set(ts2) s.Set(ts2)
result, ok = s.Get().(string) result, ok = s.Get().(string)
if !ok { if !ok {
t.Fatalf("Safe.Get() after Safe.Set() failed, got type '%T', expected string", s.Get()) t.Fatalf("Safe.Get() after Safe.Set() failed, got type '%T', expected string", s.Get())
} }
if result != ts2 { if result != ts2 {
t.Errorf("Safe.Get() after Safe.Set() failed, got '%s', expected '%s'", result, ts2) t.Errorf("Safe.Get() after Safe.Set() failed, got '%s', expected '%s'", result, ts2)
} }