Skip to content

Commit

Permalink
Print the response's body on demand for debugging purposes (#276)
Browse files Browse the repository at this point in the history
* Implement debugging functionality when reading a response's body

* Remove the silliness :)

December 28 has passed already

* Don't use log.Print for writing to Stdout

* Add important note

* Update flake.lock

* Add fmt.Fprint to the list of unhandled errors

* End comment with a period

* Remove the comment completely

* Document a method as having its documentation pending

* Remove TODO line

* Activate printing the response body from envvars

- Adds `BATON_DEBUG_PRINT_RESPONSE_BODY`

* Remove `logBody` unexported function
  • Loading branch information
shackra authored Jan 24, 2025
1 parent 2f67ce1 commit 03891ab
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 41 deletions.
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ linters-settings:
arguments:
- fmt.Printf
- fmt.Println
- fmt.Fprint
- fmt.Fprintf
- fmt.Fprintln
- os.Stderr.Sync
Expand Down
40 changes: 20 additions & 20 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions pkg/sync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ const maxDepth = 8

var dontFixCycles, _ = strconv.ParseBool(os.Getenv("BATON_DONT_FIX_CYCLES"))

var (
ErrSyncNotComplete = fmt.Errorf("sync exited without finishing")
)
var ErrSyncNotComplete = fmt.Errorf("sync exited without finishing")

type Syncer interface {
Sync(context.Context) error
Expand Down Expand Up @@ -973,8 +971,7 @@ func (s *syncer) SyncAssets(ctx context.Context) error {
return nil
}

// SyncGrantExpansion
// TODO(morgabra) Docs.
// SyncGrantExpansion documentation pending.
func (s *syncer) SyncGrantExpansion(ctx context.Context) error {
l := ctxzap.Extract(ctx)
entitlementGraph := s.state.EntitlementGraph(ctx)
Expand Down
40 changes: 40 additions & 0 deletions pkg/uhttp/body_print.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package uhttp

// Implements a debugging facility for request responses. This changes
// the behavior of `BaseHttpClient` with an unexported flag.
//
// IMPORTANT: This feature is intended for development and debugging purposes only.
// Do not enable in production as it may expose sensitive information in logs.
//
// Usage:
// client := uhttp.NewBaseHttpClient(
// httpClient,
// uhttp.WithPrintBody(true), // Enable response body printing
// )

import (
"errors"
"fmt"
"io"
"os"
)

type printReader struct {
reader io.Reader
}

func (pr *printReader) Read(p []byte) (int, error) {
n, err := pr.reader.Read(p)
if n > 0 {
_, merr := fmt.Fprint(os.Stdout, string(p[:n]))
if merr != nil {
return -1, errors.Join(err, merr)
}
}

return n, err
}

func wrapPrintBody(body io.Reader) io.Reader {
return &printReader{reader: body}
}
33 changes: 17 additions & 16 deletions pkg/uhttp/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"io"
"net/http"
"net/url"
"os"
"syscall"
"time"

Expand All @@ -33,8 +34,6 @@ const (
authorizationHeader = "Authorization"
)

const maxBodySize = 4096

type WrapperResponse struct {
Header http.Header
Body []byte
Expand Down Expand Up @@ -140,8 +139,8 @@ func WithJSONResponse(response interface{}) DoOption {

if !IsJSONContentType(contentHeader) {
if len(resp.Body) != 0 {
// we want to see the body regardless
return fmt.Errorf("unexpected content type for JSON response: %s. status code: %d. body: «%s»", contentHeader, resp.StatusCode, logBody(resp.Body, maxBodySize))
// to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead
return fmt.Errorf("unexpected content type for JSON response: %s. status code: %d", contentHeader, resp.StatusCode)
}
return fmt.Errorf("unexpected content type for JSON response: %s. status code: %d", contentHeader, resp.StatusCode)
}
Expand All @@ -150,7 +149,8 @@ func WithJSONResponse(response interface{}) DoOption {
}
err := json.Unmarshal(resp.Body, response)
if err != nil {
return fmt.Errorf("failed to unmarshal json response: %w. status code: %d. body %v", err, resp.StatusCode, logBody(resp.Body, maxBodySize))
// to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead
return fmt.Errorf("failed to unmarshal json response: %w. status code: %d", err, resp.StatusCode)
}
return nil
}
Expand All @@ -164,19 +164,13 @@ func WithAlwaysJSONResponse(response interface{}) DoOption {
}
err := json.Unmarshal(resp.Body, response)
if err != nil {
return fmt.Errorf("failed to unmarshal json response: %w. status code: %d. body %v", err, resp.StatusCode, logBody(resp.Body, maxBodySize))
// to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead
return fmt.Errorf("failed to unmarshal json response: %w. status code: %d", err, resp.StatusCode)
}
return nil
}
}

func logBody(body []byte, size int) string {
if len(body) > size {
return string(body[:size]) + " ..."
}
return string(body)
}

type ErrorResponse interface {
Message() string
}
Expand All @@ -190,12 +184,14 @@ func WithErrorResponse(resource ErrorResponse) DoOption {
contentHeader := resp.Header.Get(ContentType)

if !IsJSONContentType(contentHeader) {
return fmt.Errorf("unexpected content type for JSON error response: %s. status code: %d. body: «%s»", contentHeader, resp.StatusCode, logBody(resp.Body, maxBodySize))
// to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead
return fmt.Errorf("unexpected content type for JSON error response: %s. status code: %d", contentHeader, resp.StatusCode)
}

// Decode the JSON response body into the ErrorResponse
if err := json.Unmarshal(resp.Body, &resource); err != nil {
return fmt.Errorf("failed to unmarshal JSON error response: %w. status code: %d. body %v", err, resp.StatusCode, logBody(resp.Body, maxBodySize))
// to print the response, set the envvar BATON_DEBUG_PRINT_RESPONSE_BODY as non-empty, instead
return fmt.Errorf("failed to unmarshal JSON error response: %w. status code: %d", err, resp.StatusCode)
}

// Construct a more detailed error message
Expand Down Expand Up @@ -341,7 +337,12 @@ func (c *BaseHttpClient) Do(req *http.Request, options ...DoOption) (*http.Respo
}

// Replace resp.Body with a no-op closer so nobody has to worry about closing the reader.
resp.Body = io.NopCloser(bytes.NewBuffer(body))
shouldPrint := os.Getenv("BATON_DEBUG_PRINT_RESPONSE_BODY")
if shouldPrint != "" {
resp.Body = io.NopCloser(wrapPrintBody(bytes.NewBuffer(body)))
} else {
resp.Body = io.NopCloser(bytes.NewBuffer(body))
}

wresp := WrapperResponse{
Header: resp.Header,
Expand Down

0 comments on commit 03891ab

Please sign in to comment.