diff --git a/docs/docs/operations/logging.md b/docs/docs/operations/logging.md new file mode 100644 index 00000000000..e099e7b5da3 --- /dev/null +++ b/docs/docs/operations/logging.md @@ -0,0 +1,61 @@ +--- +layout: default +title: Logging the request body pattern for a request +nav_order: 5 +parent: Operations +--- + +# Logging the request body pattern for a request + +If you want to log the request body of incoming requests, you will need to buffer the body before it reaches the gateway. To log the request body, you can use a middleware `http.Handler` to buffer the request body before it's consumed. + +1. Create a `http.Handler` middleware. The `logRequestBody` example middleware logs the request body when the response status code is not 200. + +```go +type logResponseWriter struct { + http.ResponseWriter + statusCode int +} + +func (rsp *logResponseWriter) WriteHeader(code int) { + rsp.statusCode = code + rsp.ResponseWriter.WriteHeader(code) +} + +func newLogResponseWriter(w http.ResponseWriter) *logResponseWriter { + return &logResponseWriter{w, http.StatusOK} +} + +// logRequestBody logs the request body when the response status code is not 200. +func logRequestBody(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + lw := newLogResponseWriter(w) + + // Note that buffering the entire request body could consume a lot of memory. + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, fmt.Sprintf("failed to read body: %v", err), http.StatusBadRequest) + return + } + clonedR := r.Clone(r.Context()) + clonedR.Body = io.NopCloser(bytes.NewReader(body)) + + h.ServeHTTP(lw, clonedR) + + if lw.statusCode != http.StatusOK { + grpclog.Errorf("http error %+v request body %+v", lw.statusCode, string(body)) + } + }) +} +``` + +2. Wrap the gateway serve mux with the `logRequestBody` middleware: + +```go + mux := runtime.NewServeMux() + // Register generated gateway handlers + + s := &http.Server{ + Handler: logRequestBody(mux), + } +``` \ No newline at end of file diff --git a/examples/internal/gateway/handlers.go b/examples/internal/gateway/handlers.go index 6fdb3cec102..a4cbb2d4cb9 100644 --- a/examples/internal/gateway/handlers.go +++ b/examples/internal/gateway/handlers.go @@ -1,7 +1,9 @@ package gateway import ( + "bytes" "fmt" + "io" "net/http" "path" "strings" @@ -27,7 +29,7 @@ func openAPIServer(dir string) http.HandlerFunc { } } -// allowCORS allows Cross Origin Resoruce Sharing from any origin. +// allowCORS allows Cross Origin Resource Sharing from any origin. // Don't do this without consideration in production systems. func allowCORS(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -64,3 +66,38 @@ func healthzServer(conn *grpc.ClientConn) http.HandlerFunc { fmt.Fprintln(w, "ok") } } + +type logResponseWriter struct { + http.ResponseWriter + statusCode int +} + +func (rsp *logResponseWriter) WriteHeader(code int) { + rsp.statusCode = code + rsp.ResponseWriter.WriteHeader(code) +} + +func newLogResponseWriter(w http.ResponseWriter) *logResponseWriter { + return &logResponseWriter{w, http.StatusOK} +} + +// logRequestBody logs the request body when the response status code is not 200. +// This addresses the issue of being unable to retrieve the request body in the customErrorHandler middleware. +func logRequestBody(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + lw := newLogResponseWriter(w) + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, fmt.Sprintf("grpc server read request body err %+v", err), http.StatusBadRequest) + return + } + clonedR := r.Clone(r.Context()) + clonedR.Body = io.NopCloser(bytes.NewReader(body)) + + h.ServeHTTP(lw, clonedR) + + if lw.statusCode != http.StatusOK { + grpclog.Errorf("http error %+v request body %+v", lw.statusCode, string(body)) + } + }) +} diff --git a/examples/internal/gateway/main.go b/examples/internal/gateway/main.go index c7035b40fc4..25f9f820a83 100644 --- a/examples/internal/gateway/main.go +++ b/examples/internal/gateway/main.go @@ -58,7 +58,7 @@ func Run(ctx context.Context, opts Options) error { s := &http.Server{ Addr: opts.Addr, - Handler: allowCORS(mux), + Handler: logRequestBody(allowCORS(mux)), } go func() { <-ctx.Done()