Skip to content

Commit f71c018

Browse files
rohanKanojiaanjannath
authored andcommitted
fix (daemon) : Add logging to provide additional information for non-200 status codes (#3766)
+ Add a custom log writer in order to intercept response body and status code for additional logging + Log response body when response code is not 200 Signed-off-by: Rohan Kumar <[email protected]>
1 parent f71d9ce commit f71c018

File tree

4 files changed

+167
-3
lines changed

4 files changed

+167
-3
lines changed

cmd/crc/cmd/custom_response_writer.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
)
7+
8+
// CustomResponseWriter wraps the standard http.ResponseWriter and captures the response body
9+
type CustomResponseWriter struct {
10+
http.ResponseWriter
11+
statusCode int
12+
body *bytes.Buffer
13+
}
14+
15+
// NewCustomResponseWriter creates a new CustomResponseWriter
16+
func NewCustomResponseWriter(w http.ResponseWriter) *CustomResponseWriter {
17+
return &CustomResponseWriter{
18+
ResponseWriter: w,
19+
statusCode: http.StatusOK,
20+
body: &bytes.Buffer{},
21+
}
22+
}
23+
24+
// WriteHeader allows capturing and modifying the status code
25+
func (rw *CustomResponseWriter) WriteHeader(statusCode int) {
26+
rw.statusCode = statusCode
27+
rw.ResponseWriter.WriteHeader(statusCode)
28+
}
29+
30+
// Write captures the response body and logs it
31+
func (rw *CustomResponseWriter) Write(p []byte) (int, error) {
32+
bufferLen, err := rw.body.Write(p)
33+
if err != nil {
34+
return bufferLen, err
35+
}
36+
37+
return rw.ResponseWriter.Write(p)
38+
}
39+
40+
// interceptResponseBodyMiddleware injects the custom bodyConsumer function (received as second argument) into
41+
// http.HandleFunc logic that allows users to intercept response body as per their requirements (e.g. logging)
42+
// and returns updated http.Handler
43+
func interceptResponseBodyMiddleware(next http.Handler, bodyConsumer func(statusCode int, buffer *bytes.Buffer, r *http.Request)) http.Handler {
44+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45+
responseWriter := NewCustomResponseWriter(w)
46+
next.ServeHTTP(responseWriter, r)
47+
bodyConsumer(responseWriter.statusCode, responseWriter.body, r)
48+
})
49+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
type TestHandler struct {
14+
}
15+
16+
func (t *TestHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
17+
_, err := fmt.Fprint(w, "Testing!")
18+
if err != nil {
19+
return
20+
}
21+
}
22+
23+
func TestLogResponseBodyMiddlewareCapturesResponseAsExpected(t *testing.T) {
24+
// Given
25+
interceptedResponseStatusCode := -1
26+
interceptedResponseBody := ""
27+
responseBodyConsumer := func(statusCode int, buffer *bytes.Buffer, _ *http.Request) {
28+
interceptedResponseStatusCode = statusCode
29+
interceptedResponseBody = buffer.String()
30+
}
31+
testHandler := &TestHandler{}
32+
server := httptest.NewServer(interceptResponseBodyMiddleware(http.StripPrefix("/", testHandler), responseBodyConsumer))
33+
defer server.Close()
34+
// When
35+
resp, err := http.Get(server.URL)
36+
if err != nil {
37+
t.Fatal(err)
38+
}
39+
40+
// Then
41+
responseBody := new(bytes.Buffer)
42+
bytesRead, err := responseBody.ReadFrom(resp.Body)
43+
if err != nil {
44+
t.Fatal(err)
45+
}
46+
assert.Equal(t, 200, resp.StatusCode)
47+
assert.Equal(t, 200, interceptedResponseStatusCode)
48+
assert.Equal(t, int64(8), bytesRead)
49+
assert.Equal(t, "Testing!", responseBody.String())
50+
assert.Equal(t, "Testing!", interceptedResponseBody)
51+
}

cmd/crc/cmd/daemon.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
67
"io"
@@ -142,10 +143,10 @@ func run(configuration *types.Configuration) error {
142143
return
143144
}
144145
mux := http.NewServeMux()
145-
mux.Handle("/network/", http.StripPrefix("/network", vn.Mux()))
146+
mux.Handle("/network/", interceptResponseBodyMiddleware(http.StripPrefix("/network", vn.Mux()), logResponseBodyConditionally))
146147
machineClient := newMachine()
147-
mux.Handle("/api/", http.StripPrefix("/api", api.NewMux(config, machineClient, logging.Memory, segmentClient)))
148-
mux.Handle("/events", http.StripPrefix("/events", events.NewEventServer(machineClient)))
148+
mux.Handle("/api/", interceptResponseBodyMiddleware(http.StripPrefix("/api", api.NewMux(config, machineClient, logging.Memory, segmentClient)), logResponseBodyConditionally))
149+
mux.Handle("/events", interceptResponseBodyMiddleware(http.StripPrefix("/events", events.NewEventServer(machineClient)), logResponseBodyConditionally))
149150
s := &http.Server{
150151
Handler: handlers.LoggingHandler(os.Stderr, mux),
151152
ReadHeaderTimeout: 10 * time.Second,
@@ -271,3 +272,11 @@ func acceptJSONStringArray(w http.ResponseWriter, r *http.Request, fun func(host
271272
}
272273
w.WriteHeader(http.StatusOK)
273274
}
275+
276+
func logResponseBodyConditionally(statusCode int, buffer *bytes.Buffer, r *http.Request) {
277+
responseBody := buffer.String()
278+
if statusCode != http.StatusOK && responseBody != "" {
279+
log.Errorf("[%s] \"%s %s\" Response Body: %s\n", time.Now().Format("02/Jan/2006:15:04:05 -0700"),
280+
r.Method, r.URL.Path, buffer.String())
281+
}
282+
}

cmd/crc/cmd/daemon_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
"net/url"
7+
"os"
8+
"testing"
9+
10+
"github.com/sirupsen/logrus"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestLogResponseBodyLogsResponseBodyForFailedResponseCodes(t *testing.T) {
15+
// Given
16+
var logBuffer bytes.Buffer
17+
var responseBuffer bytes.Buffer
18+
responseBuffer.WriteString("{\"status\": \"FAILURE\"}")
19+
logrus.SetOutput(&logBuffer)
20+
defer logrus.SetOutput(os.Stdout)
21+
requestURL, err := url.Parse("http://127.0.0.1/log")
22+
assert.NoError(t, err)
23+
httpRequest := &http.Request{
24+
Method: "GET",
25+
URL: requestURL,
26+
}
27+
28+
// When
29+
logResponseBodyConditionally(500, &responseBuffer, httpRequest)
30+
31+
// Then
32+
assert.Greater(t, logBuffer.Len(), 0)
33+
assert.Contains(t, logBuffer.String(), ("\\\"GET /log\\\" Response Body: {\\\"status\\\": \\\"FAILURE\\\"}"))
34+
}
35+
36+
func TestLogResponseBodyLogsNothingWhenResponseSuccessful(t *testing.T) {
37+
// Given
38+
var logBuffer bytes.Buffer
39+
var responseBuffer bytes.Buffer
40+
responseBuffer.WriteString("{\"status\": \"SUCCESS\"}")
41+
logrus.SetOutput(&logBuffer)
42+
defer logrus.SetOutput(os.Stdout)
43+
requestURL, err := url.Parse("http://127.0.0.1/log")
44+
assert.NoError(t, err)
45+
httpRequest := &http.Request{
46+
Method: "GET",
47+
URL: requestURL,
48+
}
49+
50+
// When
51+
logResponseBodyConditionally(200, &responseBuffer, httpRequest)
52+
53+
// Then
54+
assert.Equal(t, logBuffer.Len(), 0)
55+
}

0 commit comments

Comments
 (0)