|
7 | 7 | "io" |
8 | 8 | "io/ioutil" |
9 | 9 | "net/http" |
| 10 | + "regexp" |
| 11 | + "strings" |
10 | 12 | ) |
11 | 13 |
|
12 | 14 | // ClientOption represents an argument to NewClient |
@@ -34,13 +36,26 @@ func AddHeader(name, value string) ClientOption { |
34 | 36 | } |
35 | 37 |
|
36 | 38 | // VerboseLog enables request/response logging within a RoundTripper |
37 | | -func VerboseLog(out io.Writer) ClientOption { |
| 39 | +func VerboseLog(out io.Writer, logBodies bool) ClientOption { |
38 | 40 | return func(tr http.RoundTripper) http.RoundTripper { |
39 | 41 | return &funcTripper{roundTrip: func(req *http.Request) (*http.Response, error) { |
40 | 42 | fmt.Fprintf(out, "> %s %s\n", req.Method, req.URL.RequestURI()) |
| 43 | + if logBodies && req.Body != nil && inspectableMIMEType(req.Header.Get("Content-type")) { |
| 44 | + newBody := &bytes.Buffer{} |
| 45 | + io.Copy(out, io.TeeReader(req.Body, newBody)) |
| 46 | + fmt.Fprintln(out) |
| 47 | + req.Body = ioutil.NopCloser(newBody) |
| 48 | + } |
41 | 49 | res, err := tr.RoundTrip(req) |
42 | 50 | if err == nil { |
43 | 51 | fmt.Fprintf(out, "< HTTP %s\n", res.Status) |
| 52 | + if logBodies && res.Body != nil && inspectableMIMEType(res.Header.Get("Content-type")) { |
| 53 | + newBody := &bytes.Buffer{} |
| 54 | + // TODO: pretty-print response JSON |
| 55 | + io.Copy(out, io.TeeReader(res.Body, newBody)) |
| 56 | + fmt.Fprintln(out) |
| 57 | + res.Body = ioutil.NopCloser(newBody) |
| 58 | + } |
44 | 59 | } |
45 | 60 | return res, err |
46 | 61 | }} |
@@ -179,3 +194,9 @@ func handleHTTPError(resp *http.Response) error { |
179 | 194 |
|
180 | 195 | return fmt.Errorf("http error, '%s' failed (%d): '%s'", resp.Request.URL, resp.StatusCode, message) |
181 | 196 | } |
| 197 | + |
| 198 | +var jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`) |
| 199 | + |
| 200 | +func inspectableMIMEType(t string) bool { |
| 201 | + return strings.HasPrefix(t, "text/") || jsonTypeRE.MatchString(t) |
| 202 | +} |
0 commit comments