package logging import ( "context" "crypto/sha256" "encoding/hex" "errors" "io" "net" "time" "tildegit.org/tjp/sliderule/internal/types" ) func LogRequests(logger Logger) types.Middleware { return func(inner types.Handler) types.Handler { return types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response { start := time.Now() var clientip string if request.RemoteAddr != nil { clientip, _, _ = net.SplitHostPort(request.RemoteAddr.String()) } response := inner.Handle(ctx, request) if response != nil { response.Body = loggingBody(logger, request, response, start, clientip) } else { end := time.Now() params := []any{ "msg", "request", "ts", end.UTC(), "dur", end.Sub(start), "url", request.URL, "status", "(not found)", "clientip", clientip, } if fingerprint, ok := clientFingerprint(request); ok { params = append(params, "client_ident", fingerprint) } _ = logger.Log(params...) } return response }) } } func clientFingerprint(request *types.Request) (string, bool) { if request.TLSState == nil || len(request.TLSState.PeerCertificates) == 0 { return "", false } digest := sha256.Sum256(request.TLSState.PeerCertificates[0].Raw) return hex.EncodeToString(digest[:]), true } type loggedResponseBody struct { request *types.Request response *types.Response body io.Reader start time.Time clientip string written int logger Logger } func (lr *loggedResponseBody) log() { end := time.Now() params := []any{ "msg", "request", "ts", end.UTC(), "dur", end.Sub(lr.start), "url", lr.request.URL, "status", lr.response.Status, "clientip", lr.clientip, "bodylen", lr.written, } if fingerprint, ok := clientFingerprint(lr.request); ok { params = append(params, "client_ident", fingerprint) } _ = lr.logger.Log(params...) } 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 *types.Request, response *types.Response, start time.Time, clientip string) io.Reader { body := &loggedResponseBody{ request: request, response: response, body: response.Body, start: start, written: 0, logger: logger, clientip: clientip, } if _, ok := response.Body.(io.WriterTo); ok { return loggedWriteToResponseBody{body} } return body }