mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-14 21:53:52 +00:00
apiserver: preserve stack trace in handler panic beyond timeout handler
This commit is contained in:
@@ -63,7 +63,11 @@ func HandleCrash(additionalHandlers ...func(interface{})) {
|
|||||||
// logPanic logs the caller tree when a panic occurs.
|
// logPanic logs the caller tree when a panic occurs.
|
||||||
func logPanic(r interface{}) {
|
func logPanic(r interface{}) {
|
||||||
callers := getCallers(r)
|
callers := getCallers(r)
|
||||||
|
if _, ok := r.(string); ok {
|
||||||
|
klog.Errorf("Observed a panic: %s\n%v", r, callers)
|
||||||
|
} else {
|
||||||
klog.Errorf("Observed a panic: %#v (%v)\n%v", r, r, callers)
|
klog.Errorf("Observed a panic: %#v (%v)\n%v", r, r, callers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCallers(r interface{}) string {
|
func getCallers(r interface{}) string {
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -91,16 +92,23 @@ func (t *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(chan interface{})
|
errCh := make(chan interface{})
|
||||||
tw := newTimeoutWriter(w)
|
tw := newTimeoutWriter(w)
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
result <- recover()
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
const size = 64 << 10
|
||||||
|
buf := make([]byte, size)
|
||||||
|
buf = buf[:runtime.Stack(buf, false)]
|
||||||
|
err = fmt.Sprintf("%v\n%s", err, buf)
|
||||||
|
}
|
||||||
|
errCh <- err
|
||||||
}()
|
}()
|
||||||
t.handler.ServeHTTP(tw, r)
|
t.handler.ServeHTTP(tw, r)
|
||||||
}()
|
}()
|
||||||
select {
|
select {
|
||||||
case err := <-result:
|
case err := <-errCh:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@@ -18,10 +18,12 @@ package filters
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -72,16 +74,21 @@ func TestTimeout(t *testing.T) {
|
|||||||
sendResponse := make(chan string, 1)
|
sendResponse := make(chan string, 1)
|
||||||
doPanic := make(chan struct{}, 1)
|
doPanic := make(chan struct{}, 1)
|
||||||
writeErrors := make(chan error, 1)
|
writeErrors := make(chan error, 1)
|
||||||
|
gotPanic := make(chan interface{}, 1)
|
||||||
timeout := make(chan time.Time, 1)
|
timeout := make(chan time.Time, 1)
|
||||||
resp := "test response"
|
resp := "test response"
|
||||||
timeoutErr := apierrors.NewServerTimeout(schema.GroupResource{Group: "foo", Resource: "bar"}, "get", 0)
|
timeoutErr := apierrors.NewServerTimeout(schema.GroupResource{Group: "foo", Resource: "bar"}, "get", 0)
|
||||||
record := &recorder{}
|
record := &recorder{}
|
||||||
|
|
||||||
handler := newHandler(sendResponse, doPanic, writeErrors)
|
handler := newHandler(sendResponse, doPanic, writeErrors)
|
||||||
ts := httptest.NewServer(WithPanicRecovery(WithTimeout(handler,
|
ts := httptest.NewServer(withPanicRecovery(
|
||||||
func(req *http.Request) (*http.Request, <-chan time.Time, func(), *apierrors.StatusError) {
|
WithTimeout(handler, func(req *http.Request) (*http.Request, <-chan time.Time, func(), *apierrors.StatusError) {
|
||||||
return req, timeout, record.Record, timeoutErr
|
return req, timeout, record.Record, timeoutErr
|
||||||
})))
|
}), func(w http.ResponseWriter, req *http.Request, err interface{}) {
|
||||||
|
gotPanic <- err
|
||||||
|
http.Error(w, "This request caused apiserver to panic. Look in the logs for details.", http.StatusInternalServerError)
|
||||||
|
}),
|
||||||
|
)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
// No timeouts
|
// No timeouts
|
||||||
@@ -140,4 +147,13 @@ func TestTimeout(t *testing.T) {
|
|||||||
if res.StatusCode != http.StatusInternalServerError {
|
if res.StatusCode != http.StatusInternalServerError {
|
||||||
t.Errorf("got res.StatusCode %d; expected %d due to panic", res.StatusCode, http.StatusInternalServerError)
|
t.Errorf("got res.StatusCode %d; expected %d due to panic", res.StatusCode, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
case err := <-gotPanic:
|
||||||
|
msg := fmt.Sprintf("%v", err)
|
||||||
|
if !strings.Contains(msg, "newHandler") {
|
||||||
|
t.Errorf("expected line with root cause panic in the stack trace, but didn't: %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(30 * time.Second):
|
||||||
|
t.Fatalf("expected to see a handler panic, but didn't")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ package filters
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
|
||||||
@@ -28,10 +27,16 @@ import (
|
|||||||
|
|
||||||
// WithPanicRecovery wraps an http Handler to recover and log panics.
|
// WithPanicRecovery wraps an http Handler to recover and log panics.
|
||||||
func WithPanicRecovery(handler http.Handler) http.Handler {
|
func WithPanicRecovery(handler http.Handler) http.Handler {
|
||||||
|
return withPanicRecovery(handler, func(w http.ResponseWriter, req *http.Request, err interface{}) {
|
||||||
|
http.Error(w, "This request caused apiserver to panic. Look in the logs for details.", http.StatusInternalServerError)
|
||||||
|
klog.Errorf("apiserver panic'd on %v %v", req.Method, req.RequestURI)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withPanicRecovery(handler http.Handler, crashHandler func(http.ResponseWriter, *http.Request, interface{})) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
defer runtime.HandleCrash(func(err interface{}) {
|
defer runtime.HandleCrash(func(err interface{}) {
|
||||||
http.Error(w, "This request caused apiserver to panic. Look in the logs for details.", http.StatusInternalServerError)
|
crashHandler(w, req, err)
|
||||||
klog.Errorf("apiserver panic'd on %v %v: %v\n%s\n", req.Method, req.RequestURI, err, debug.Stack())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
logger := httplog.NewLogged(req, &w)
|
logger := httplog.NewLogged(req, &w)
|
||||||
|
Reference in New Issue
Block a user