Merge pull request #79017 from sttts/sttts-apiserver-healthz-stack-trace

apiserver: don't log stack trace on /healthz error
This commit is contained in:
Kubernetes Prow Robot 2019-07-08 14:27:48 -07:00 committed by GitHub
commit 2309b584ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 82 additions and 64 deletions

View File

@ -113,7 +113,7 @@ func handleWebSocketStreams(req *http.Request, w http.ResponseWriter, portForwar
}, },
}) })
conn.SetIdleTimeout(idleTimeout) conn.SetIdleTimeout(idleTimeout)
_, streams, err := conn.Open(httplog.Unlogged(w), req) _, streams, err := conn.Open(httplog.Unlogged(req, w), req)
if err != nil { if err != nil {
err = fmt.Errorf("Unable to upgrade websocket connection: %v", err) err = fmt.Errorf("Unable to upgrade websocket connection: %v", err)
return err return err

View File

@ -95,7 +95,7 @@ func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *Opti
}, },
}) })
conn.SetIdleTimeout(idleTimeout) conn.SetIdleTimeout(idleTimeout)
negotiatedProtocol, streams, err := conn.Open(httplog.Unlogged(w), req) negotiatedProtocol, streams, err := conn.Open(httplog.Unlogged(req, w), req)
if err != nil { if err != nil {
runtime.HandleError(fmt.Errorf("Unable to upgrade websocket connection: %v", err)) runtime.HandleError(fmt.Errorf("Unable to upgrade websocket connection: %v", err))
return nil, false return nil, false

View File

@ -856,7 +856,7 @@ var statusesNoTracePred = httplog.StatusIsNot(
// ServeHTTP responds to HTTP requests on the Kubelet. // ServeHTTP responds to HTTP requests on the Kubelet.
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
defer httplog.NewLogged(req, &w).StacktraceWhen(statusesNoTracePred).Log() handler := httplog.WithLogging(s.restfulCont, statusesNoTracePred)
// monitor http requests // monitor http requests
var serverType string var serverType string
@ -878,7 +878,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
startTime := time.Now() startTime := time.Now()
defer servermetrics.HTTPRequestsDuration.WithLabelValues(method, path, serverType, longRunning).Observe(servermetrics.SinceInSeconds(startTime)) defer servermetrics.HTTPRequestsDuration.WithLabelValues(method, path, serverType, longRunning).Observe(servermetrics.SinceInSeconds(startTime))
s.restfulCont.ServeHTTP(w, req) handler.ServeHTTP(w, req)
} }
// prometheusHostAdapter adapts the HostInterface to the interface expected by the // prometheusHostAdapter adapts the HostInterface to the interface expected by the

View File

@ -165,7 +165,7 @@ func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
metrics.RegisteredWatchers.WithLabelValues(kind.Group, kind.Version, kind.Kind).Inc() metrics.RegisteredWatchers.WithLabelValues(kind.Group, kind.Version, kind.Kind).Inc()
defer metrics.RegisteredWatchers.WithLabelValues(kind.Group, kind.Version, kind.Kind).Dec() defer metrics.RegisteredWatchers.WithLabelValues(kind.Group, kind.Version, kind.Kind).Dec()
w = httplog.Unlogged(w) w = httplog.Unlogged(req, w)
if wsstream.IsWebSocketRequest(req) { if wsstream.IsWebSocketRequest(req) {
w.Header().Set("Content-Type", s.MediaType) w.Header().Set("Content-Type", s.MediaType)

View File

@ -34,14 +34,12 @@ func WithPanicRecovery(handler http.Handler) http.Handler {
} }
func withPanicRecovery(handler http.Handler, crashHandler func(http.ResponseWriter, *http.Request, interface{})) http.Handler { func withPanicRecovery(handler http.Handler, crashHandler func(http.ResponseWriter, *http.Request, interface{})) http.Handler {
handler = httplog.WithLogging(handler, httplog.DefaultStacktracePred)
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{}) {
crashHandler(w, req, err) crashHandler(w, req, err)
}) })
logger := httplog.NewLogged(req, &w)
defer logger.Log()
// Dispatch to the internal handler // Dispatch to the internal handler
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
}) })

View File

@ -26,6 +26,7 @@ go_library(
deps = [ deps = [
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server/httplog:go_default_library",
"//vendor/k8s.io/klog:go_default_library", "//vendor/k8s.io/klog:go_default_library",
], ],
) )

View File

@ -25,10 +25,10 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"k8s.io/klog"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/server/httplog"
"k8s.io/klog"
) )
// HealthzChecker is a named healthz checker. // HealthzChecker is a named healthz checker.
@ -181,7 +181,7 @@ func handleRootHealthz(checks ...HealthzChecker) http.HandlerFunc {
// always be verbose on failure // always be verbose on failure
if failed { if failed {
klog.V(2).Infof("%vhealthz check failed", verboseOut.String()) klog.V(2).Infof("%vhealthz check failed", verboseOut.String())
http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError) http.Error(httplog.Unlogged(r, w), fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError)
return return
} }

View File

@ -18,6 +18,7 @@ package httplog
import ( import (
"bufio" "bufio"
"context"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@ -34,6 +35,11 @@ type logger interface {
Addf(format string, data ...interface{}) Addf(format string, data ...interface{})
} }
type respLoggerContextKeyType int
// respLoggerContextKey is used to store the respLogger pointer in the request context.
const respLoggerContextKey respLoggerContextKeyType = iota
// Add a layer on top of ResponseWriter, so we can track latency and error // Add a layer on top of ResponseWriter, so we can track latency and error
// message sources. // message sources.
// //
@ -69,47 +75,54 @@ func DefaultStacktracePred(status int) bool {
return (status < http.StatusOK || status >= http.StatusInternalServerError) && status != http.StatusSwitchingProtocols return (status < http.StatusOK || status >= http.StatusInternalServerError) && status != http.StatusSwitchingProtocols
} }
// NewLogged turns a normal response writer into a logged response writer. // WithLogging wraps the handler with logging.
// func WithLogging(handler http.Handler, pred StacktracePred) http.Handler {
// Usage: return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// ctx := req.Context()
// defer NewLogged(req, &w).StacktraceWhen(StatusIsNot(200, 202)).Log() if old := respLoggerFromContext(req); old != nil {
// panic("multiple WithLogging calls!")
// (Only the call to Log() is deferred, so you can set everything up in one line!) }
// rl := newLogged(req, w).StacktraceWhen(pred)
// Note that this *changes* your writer, to route response writing actions req = req.WithContext(context.WithValue(ctx, respLoggerContextKey, rl))
// through the logger.
// defer rl.Log()
// Use LogOf(w).Addf(...) to log something along with the response result. handler.ServeHTTP(rl, req)
func NewLogged(req *http.Request, w *http.ResponseWriter) *respLogger { })
if _, ok := (*w).(*respLogger); ok { }
// Don't double-wrap!
panic("multiple NewLogged calls!") // respLoggerFromContext returns the respLogger or nil.
func respLoggerFromContext(req *http.Request) *respLogger {
ctx := req.Context()
val := ctx.Value(respLoggerContextKey)
if rl, ok := val.(*respLogger); ok {
return rl
} }
rl := &respLogger{ return nil
}
// newLogged turns a normal response writer into a logged response writer.
func newLogged(req *http.Request, w http.ResponseWriter) *respLogger {
return &respLogger{
startTime: time.Now(), startTime: time.Now(),
req: req, req: req,
w: *w, w: w,
logStacktracePred: DefaultStacktracePred, logStacktracePred: DefaultStacktracePred,
} }
*w = rl // hijack caller's writer!
return rl
} }
// LogOf returns the logger hiding in w. If there is not an existing logger // LogOf returns the logger hiding in w. If there is not an existing logger
// then a passthroughLogger will be created which will log to stdout immediately // then a passthroughLogger will be created which will log to stdout immediately
// when Addf is called. // when Addf is called.
func LogOf(req *http.Request, w http.ResponseWriter) logger { func LogOf(req *http.Request, w http.ResponseWriter) logger {
if rl, ok := w.(*respLogger); ok { if rl := respLoggerFromContext(req); rl != nil {
return rl return rl
} }
return &passthroughLogger{} return &passthroughLogger{}
} }
// Unlogged returns the original ResponseWriter, or w if it is not our inserted logger. // Unlogged returns the original ResponseWriter, or w if it is not our inserted logger.
func Unlogged(w http.ResponseWriter) http.ResponseWriter { func Unlogged(req *http.Request, w http.ResponseWriter) http.ResponseWriter {
if rl, ok := w.(*respLogger); ok { if rl := respLoggerFromContext(req); rl != nil {
return rl.w return rl.w
} }
return w return w

View File

@ -56,22 +56,24 @@ func TestStatusIsNot(t *testing.T) {
} }
} }
func TestNewLogged(t *testing.T) { func TestWithLogging(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com", nil) req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
handler := func(w http.ResponseWriter, r *http.Request) { var handler http.Handler
NewLogged(req, &w) handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
handler = WithLogging(WithLogging(handler, DefaultStacktracePred), DefaultStacktracePred)
func() {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("Expected NewLogged to panic") t.Errorf("Expected newLogged to panic")
} }
}() }()
NewLogged(req, &w) w := httptest.NewRecorder()
} handler.ServeHTTP(w, req)
w := httptest.NewRecorder() }()
handler(w, req)
} }
func TestLogOf(t *testing.T) { func TestLogOf(t *testing.T) {
@ -81,21 +83,23 @@ func TestLogOf(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
handler := func(w http.ResponseWriter, r *http.Request) { var want string
var want string var handler http.Handler
if makeLogger { handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
NewLogged(req, &w)
want = "*httplog.respLogger"
} else {
want = "*httplog.passthroughLogger"
}
got := reflect.TypeOf(LogOf(r, w)).String() got := reflect.TypeOf(LogOf(r, w)).String()
if want != got { if want != got {
t.Errorf("Expected %v, got %v", want, got) t.Errorf("Expected %v, got %v", want, got)
} }
})
if makeLogger {
handler = WithLogging(handler, DefaultStacktracePred)
want = "*httplog.respLogger"
} else {
want = "*httplog.passthroughLogger"
} }
w := httptest.NewRecorder() w := httptest.NewRecorder()
handler(w, req) handler.ServeHTTP(w, req)
} }
} }
@ -106,18 +110,20 @@ func TestUnlogged(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
} }
handler := func(w http.ResponseWriter, r *http.Request) {
want := w origWriter := httptest.NewRecorder()
if makeLogger { var handler http.Handler
NewLogged(req, &w) handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
} got := Unlogged(r, w)
got := Unlogged(w) if origWriter != got {
if want != got { t.Errorf("Expected origin writer, got %#v", got)
t.Errorf("Expected %v, got %v", want, got)
} }
})
if makeLogger {
handler = WithLogging(handler, DefaultStacktracePred)
} }
w := httptest.NewRecorder()
handler(w, req) handler.ServeHTTP(origWriter, req)
} }
} }
@ -134,7 +140,7 @@ func TestLoggedStatus(t *testing.T) {
} }
var tw http.ResponseWriter = new(testResponseWriter) var tw http.ResponseWriter = new(testResponseWriter)
logger := NewLogged(req, &tw) logger := newLogged(req, tw)
logger.Write(nil) logger.Write(nil)
if logger.status != http.StatusOK { if logger.status != http.StatusOK {
@ -142,7 +148,7 @@ func TestLoggedStatus(t *testing.T) {
} }
tw = new(testResponseWriter) tw = new(testResponseWriter)
logger = NewLogged(req, &tw) logger = newLogged(req, tw)
logger.WriteHeader(http.StatusForbidden) logger.WriteHeader(http.StatusForbidden)
logger.Write(nil) logger.Write(nil)