package logging import ( "context" "errors" "io" "time" sr "tildegit.org/tjp/sliderule" ) func LogRequests(logger Logger) sr.Middleware { return func(inner sr.Handler) sr.Handler { return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { start := time.Now() response := inner.Handle(ctx, request) if response != nil { response.Body = loggingBody(logger, request, response, start) } else { end := time.Now() logger.Log( "msg", "request", "ts", end.UTC(), "dur", end.Sub(start), "url", request.URL, "status", "(not found)", ) } return response }) } } type loggedResponseBody struct { request *sr.Request response *sr.Response body io.Reader start time.Time written int logger Logger } func (lr *loggedResponseBody) log() { end := time.Now() _ = lr.logger.Log( "msg", "request", "ts", end.UTC(), "dur", end.Sub(lr.start), "url", lr.request.URL, "status", lr.response.Status, "bodylen", lr.written, ) } func (lr *loggedResponseBody) Read(b []byte) (int, error) { if lr.body == nil { lr.log() return 0, io.EOF } wr, err := lr.body.Read(b) lr.written += wr if errors.Is(err, io.EOF) { lr.log() } return wr, err } func (lr *loggedResponseBody) Close() error { if cl, ok := lr.body.(io.Closer); ok { return cl.Close() } return nil } type loggedWriteToResponseBody struct { *loggedResponseBody } func (lwtr loggedWriteToResponseBody) WriteTo(dst io.Writer) (int64, error) { n, err := lwtr.body.(io.WriterTo).WriteTo(dst) if err == nil { lwtr.written += int(n) lwtr.log() } return n, err } func loggingBody(logger Logger, request *sr.Request, response *sr.Response, start time.Time) io.Reader { body := &loggedResponseBody{ request: request, response: response, body: response.Body, start: start, written: 0, logger: logger, } if _, ok := response.Body.(io.WriterTo); ok { return loggedWriteToResponseBody{body} } return body }