Skip to content

Commit

Permalink
Http response recording/logging
Browse files Browse the repository at this point in the history
  • Loading branch information
ldemailly committed Nov 20, 2023
1 parent 71b9af3 commit 7ca90ee
Showing 1 changed file with 68 additions and 1 deletion.
69 changes: 68 additions & 1 deletion http_logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"log"
"net/http"
"strings"
"time"
)

// TLSInfo returns ' https <cipher suite> "<peer CN>"' if the request is using TLS
Expand Down Expand Up @@ -51,7 +52,7 @@ func AppendTLSInfoAttrs(attrs []KeyVal, r *http.Request) []KeyVal {
// including headers when loglevel is verbose.
// additional key:value pairs can be passed as extraAttributes.
//
//nolint:revive
//nolint:revive // name is fine.
func LogRequest(r *http.Request, msg string, extraAttributes ...KeyVal) {
if !Log(Info) {
return
Expand All @@ -74,6 +75,72 @@ func LogRequest(r *http.Request, msg string, extraAttributes ...KeyVal) {
S(Info, msg, attr...)
}

// LogResponse logs the response code, byte size and duration of the request.
// additional key:value pairs can be passed as extraAttributes.
//
//nolint:revive // name is fine.
func LogResponse[T *ResponseRecorder | *http.Response](r T, msg string, extraAttributes ...KeyVal) {
if !Log(Info) {
return
}
var attr []KeyVal
switch v := any(r).(type) {
case *ResponseRecorder:
attr = []KeyVal{
Int("status", v.StatusCode),
Int64("size", v.ContentLength),
}
case *http.Response:
attr = []KeyVal{
Int("status", v.StatusCode),
Int64("size", v.ContentLength),
}

Check warning on line 97 in http_logging.go

View check run for this annotation

Codecov / codecov/patch

http_logging.go#L82-L97

Added lines #L82 - L97 were not covered by tests
}
attr = append(attr, extraAttributes...)
S(Info, msg, attr...)

Check warning on line 100 in http_logging.go

View check run for this annotation

Codecov / codecov/patch

http_logging.go#L99-L100

Added lines #L99 - L100 were not covered by tests
}

// Can be used (and is used by LogAndCall()) to wrap a http.ResponseWriter to record status code and size.
type ResponseRecorder struct {
w http.ResponseWriter
startTime time.Time
StatusCode int
ContentLength int64
}

func (rr *ResponseRecorder) Header() http.Header {
return rr.w.Header()

Check warning on line 112 in http_logging.go

View check run for this annotation

Codecov / codecov/patch

http_logging.go#L111-L112

Added lines #L111 - L112 were not covered by tests
}

func (rr *ResponseRecorder) Write(p []byte) (int, error) {
size, err := rr.w.Write(p)
rr.ContentLength += int64(size)
if err != nil {
rr.StatusCode = http.StatusInternalServerError
} else if rr.StatusCode == 0 {
rr.StatusCode = http.StatusOK
}
return size, err

Check warning on line 123 in http_logging.go

View check run for this annotation

Codecov / codecov/patch

http_logging.go#L115-L123

Added lines #L115 - L123 were not covered by tests
}

func (rr *ResponseRecorder) WriteHeader(code int) {
rr.w.WriteHeader(code)
rr.StatusCode = code

Check warning on line 128 in http_logging.go

View check run for this annotation

Codecov / codecov/patch

http_logging.go#L126-L128

Added lines #L126 - L128 were not covered by tests
}

// LogResponse logs the response code, byte size and duration of the request.
// additional key:value pairs can be passed as extraAttributes.
//
//nolint:revive // name is fine.
func LogAndCall(msg string, hf http.HandlerFunc, extraAttributes ...KeyVal) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
LogRequest(r, msg, extraAttributes...)
rr := &ResponseRecorder{w: w, startTime: time.Now()}
hf(rr, r)
LogResponse(rr, msg, Int64("microsec", time.Since(rr.startTime).Microseconds()))
})

Check warning on line 141 in http_logging.go

View check run for this annotation

Codecov / codecov/patch

http_logging.go#L135-L141

Added lines #L135 - L141 were not covered by tests
}

type logWriter struct {
source string
level Level
Expand Down

0 comments on commit 7ca90ee

Please sign in to comment.