2025-02-22 23:06:16 +08:00

96 lines
2.1 KiB
Go

package httplog
import (
"bufio"
"context"
"errors"
"log/slog"
"net"
"net/http"
"time"
"git.jeffthecoder.xyz/public/lazyhandler/magic"
)
type ctxKey int
const (
loggerKey ctxKey = iota
)
var (
ErrNotHijackable = errors.New("not a hijacker")
)
type Log struct {
LogStart bool
LogStartLevel slog.Level
LogFinish bool
LogFinishLevel slog.Level
}
type responseRecorder struct {
http.ResponseWriter
StatusCode int
}
func (recorder *responseRecorder) WriteHeader(statusCode int) {
recorder.StatusCode = statusCode
recorder.ResponseWriter.WriteHeader(statusCode)
}
func (recorder *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacker, ok := recorder.ResponseWriter.(http.Hijacker); ok {
recorder.ResponseWriter = nil
return hijacker.Hijack()
}
return nil, nil, ErrNotHijackable
}
var (
_ http.Hijacker = &responseRecorder{}
)
func (log Log) WrapHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
args := []any{
"remote_addr", r.RemoteAddr,
"host", r.Host,
"path", r.URL.Path,
}
startTime := time.Now()
if log.LogStart {
slog.With(append(args, "time", startTime)...).Log(r.Context(), log.LogStartLevel, "request")
}
recorder := &responseRecorder{ResponseWriter: w, StatusCode: 200}
next.ServeHTTP(recorder, r.WithContext(context.WithValue(r.Context(), loggerKey, slog.With(args...))))
if log.LogFinish && recorder.ResponseWriter != nil {
finishTime := time.Now()
slog.With(append(args, "time", finishTime, "duration", finishTime.Sub(startTime), "status", recorder.StatusCode)...).Log(r.Context(), log.LogFinishLevel, "response")
}
})
}
func Logger(r *http.Request) *slog.Logger {
if logger, ok := r.Context().Value(loggerKey).(*slog.Logger); ok {
return logger.With("time", time.Now())
}
return slog.With(
"remote_addr", r.RemoteAddr,
"host", r.Host,
"path", r.URL.Path,
"time", time.Now(),
)
}
func RegisterExtractor() {
magic.RegisterExtractorGeneric[*slog.Logger](func(r *http.Request) (any, error) {
return Logger(r), nil
})
}