diff --git a/pkg/client/debugging.go b/pkg/client/debugging.go new file mode 100644 index 00000000000..75cce864b2e --- /dev/null +++ b/pkg/client/debugging.go @@ -0,0 +1,135 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "fmt" + "net/http" + "time" + + "github.com/golang/glog" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" +) + +// RequestInfo keeps track of information about a request/response combination +type RequestInfo struct { + RequestHeaders http.Header + RequestVerb string + RequestURL string + + ResponseStatus string + ResponseHeaders http.Header + ResponseErr error + + Duration time.Duration +} + +// NewRequestInfo creates a new RequestInfo based on an http request +func NewRequestInfo(req *http.Request) *RequestInfo { + reqInfo := &RequestInfo{} + reqInfo.RequestURL = req.URL.String() + reqInfo.RequestVerb = req.Method + reqInfo.RequestHeaders = req.Header + + return reqInfo +} + +// Complete adds information about the response to the RequestInfo +func (r *RequestInfo) Complete(response *http.Response, err error) { + if err != nil { + r.ResponseErr = err + return + } + r.ResponseStatus = response.Status + r.ResponseHeaders = response.Header +} + +// ToCurl returns a string that can be run as a command in a terminal (minus the body) +func (r RequestInfo) ToCurl() string { + headers := "" + for key, values := range map[string][]string(r.RequestHeaders) { + for _, value := range values { + headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, value)) + } + } + + return fmt.Sprintf("curl -k -v -X%s %s %s", r.RequestVerb, headers, r.RequestURL) +} + +// DebuggingRoundTripper will display information about the requests passing through it based on what is configured +type DebuggingRoundTripper struct { + delegatedRoundTripper http.RoundTripper + + Levels util.StringSet +} + +const ( + JustURL string = "url" + URLTiming string = "urltiming" + CurlCommand string = "curlcommand" + RequestHeaders string = "requestheaders" + ResponseStatus string = "responsestatus" + ResponseHeaders string = "responseheaders" +) + +func NewDebuggingRoundTripper(rt http.RoundTripper, levels ...string) *DebuggingRoundTripper { + return &DebuggingRoundTripper{rt, util.NewStringSet(levels...)} +} + +func (rt *DebuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + reqInfo := NewRequestInfo(req) + + if rt.Levels.Has(JustURL) { + glog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL) + } + if rt.Levels.Has(CurlCommand) { + glog.Infof("%s", reqInfo.ToCurl()) + + } + if rt.Levels.Has(RequestHeaders) { + glog.Infof("Request Headers:") + for key, values := range reqInfo.RequestHeaders { + for _, value := range values { + glog.Infof(" %s: %s", key, value) + } + } + } + + startTime := time.Now() + response, err := rt.delegatedRoundTripper.RoundTrip(req) + reqInfo.Duration = time.Since(startTime) + + reqInfo.Complete(response, err) + + if rt.Levels.Has(URLTiming) { + glog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond)) + } + if rt.Levels.Has(ResponseStatus) { + glog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond)) + } + if rt.Levels.Has(ResponseHeaders) { + glog.Infof("Response Headers:") + for key, values := range reqInfo.ResponseHeaders { + for _, value := range values { + glog.Infof(" %s: %s", key, value) + } + } + } + + return response, err +} diff --git a/pkg/client/helper.go b/pkg/client/helper.go index 137396e3d11..36508a25fa3 100644 --- a/pkg/client/helper.go +++ b/pkg/client/helper.go @@ -353,6 +353,18 @@ func TransportFor(config *Config) (http.RoundTripper, error) { transport = http.DefaultTransport } } + + switch { + case bool(glog.V(9)): + transport = NewDebuggingRoundTripper(transport, CurlCommand, URLTiming, ResponseHeaders) + case bool(glog.V(8)): + transport = NewDebuggingRoundTripper(transport, JustURL, RequestHeaders, ResponseStatus, ResponseHeaders) + case bool(glog.V(7)): + transport = NewDebuggingRoundTripper(transport, JustURL, RequestHeaders, ResponseStatus) + case bool(glog.V(6)): + transport = NewDebuggingRoundTripper(transport, URLTiming) + } + if config.WrapTransport != nil { transport = config.WrapTransport(transport) } diff --git a/pkg/client/request.go b/pkg/client/request.go index 7c42cbaa09c..b7f82263df9 100644 --- a/pkg/client/request.go +++ b/pkg/client/request.go @@ -459,8 +459,10 @@ func (r *Request) Body(obj interface{}) *Request { r.err = err return r } + glog.V(8).Infof("Request Body: %s", string(data)) r.body = bytes.NewBuffer(data) case []byte: + glog.V(8).Infof("Request Body: %s", string(t)) r.body = bytes.NewBuffer(t) case io.Reader: r.body = t @@ -470,6 +472,7 @@ func (r *Request) Body(obj interface{}) *Request { r.err = err return r } + glog.V(8).Infof("Request Body: %s", string(data)) r.body = bytes.NewBuffer(data) default: r.err = fmt.Errorf("unknown type used for body: %+v", obj) @@ -756,6 +759,8 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu body = data } } + glog.V(8).Infof("Response Body: %s", string(body)) + // Did the server give us a status response? isStatusResponse := false var status api.Status @@ -811,6 +816,8 @@ func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *h body = data } } + glog.V(8).Infof("Response Body: %s", string(body)) + message := "unknown" if isTextResponse(resp) { message = strings.TrimSpace(string(body))