From ea2ab71f4be26f006c5b7ba4de8be5bf577c3aea Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sat, 10 Dec 2016 19:24:21 -0500 Subject: [PATCH] Logging request / response body should not allocate Putting string(data) in the critical path causes an allocation which can be very large, and happens regardless of whether logging is enabled. Also, use the more sophisticated logging of body output that avoids causing quoting of JSON output (current behavior is "{\"key\":...}" and should be {"key":...} for readability). --- pkg/client/restclient/request.go | 33 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/pkg/client/restclient/request.go b/pkg/client/restclient/request.go index d5c4384fc97..c2498d42e3d 100644 --- a/pkg/client/restclient/request.go +++ b/pkg/client/restclient/request.go @@ -540,10 +540,10 @@ func (r *Request) Body(obj interface{}) *Request { r.err = err return r } - glog.V(8).Infof("Request Body: %#v", string(data)) + glogBody("Request Body", data) r.body = bytes.NewReader(data) case []byte: - glog.V(8).Infof("Request Body: %#v", string(t)) + glogBody("Request Body", t) r.body = bytes.NewReader(t) case io.Reader: r.body = t @@ -557,7 +557,7 @@ func (r *Request) Body(obj interface{}) *Request { r.err = err return r } - glog.V(8).Infof("Request Body: %#v", string(data)) + glogBody("Request Body", data) r.body = bytes.NewReader(data) r.SetHeader("Content-Type", r.content.ContentType) default: @@ -878,6 +878,7 @@ func (r *Request) DoRaw() ([]byte, error) { var result Result err := r.request(func(req *http.Request, resp *http.Response) { result.body, result.err = ioutil.ReadAll(resp.Body) + glogBody("Response Body", result.body) if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent { result.err = r.transformUnstructuredResponseError(resp, req, result.body) } @@ -897,15 +898,7 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu } } - if glog.V(8) { - if bytes.IndexFunc(body, func(r rune) bool { - return r < 0x0a - }) != -1 { - glog.Infof("Response Body:\n%s", hex.Dump(body)) - } else { - glog.Infof("Response Body: %s", string(body)) - } - } + glogBody("Response Body", body) // verify the content type is accurate contentType := resp.Header.Get("Content-Type") @@ -957,6 +950,21 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu } } +// glogBody logs a body output that could be either JSON or protobuf. It explicitly guards against +// allocating a new string for the body output unless necessary. Uses a simple heuristic to determine +// whether the body is printable. +func glogBody(prefix string, body []byte) { + if glog.V(8) { + if bytes.IndexFunc(body, func(r rune) bool { + return r < 0x0a + }) != -1 { + glog.Infof("%s:\n%s", prefix, hex.Dump(body)) + } else { + glog.Infof("%s: %s", prefix, string(body)) + } + } +} + // maxUnstructuredResponseTextBytes is an upper bound on how much output to include in the unstructured error. const maxUnstructuredResponseTextBytes = 2048 @@ -994,7 +1002,6 @@ func (r *Request) newUnstructuredResponseError(body []byte, isTextResponse bool, if len(body) > maxUnstructuredResponseTextBytes { body = body[:maxUnstructuredResponseTextBytes] } - glog.V(8).Infof("Response Body: %#v", string(body)) message := "unknown" if isTextResponse {