package middleware import ( "bufio" "bytes" "errors" "github.com/gorilla/websocket" "io" "log" "net" "net/http" "time" ) // LoggingMiddleware логирует входящие HTTP-запросы func LoggingMiddleware(logger *log.Logger) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Полностью пропускаем WebSocket запросы if websocket.IsWebSocketUpgrade(r) { next.ServeHTTP(w, r) return } start := time.Now() // Логируем основные параметры запроса logData := map[string]interface{}{ "method": r.Method, "path": r.URL.Path, "query": r.URL.RawQuery, "ip": r.RemoteAddr, } // Чтение тела запроса (для логирования) var bodyBytes []byte if r.Body != nil { bodyBytes, _ = io.ReadAll(r.Body) r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) if len(bodyBytes) > 0 { logData["body_size"] = len(bodyBytes) if r.Header.Get("Content-Type") == "application/json" { logData["body"] = string(bodyBytes) } } } // Создаем responseWriter только для НЕ WebSocket запросов rw := &responseWriter{ResponseWriter: w, status: http.StatusOK} next.ServeHTTP(rw, r) // Логирование только для НЕ WebSocket запросов duration := time.Since(start) logger.Printf( "%s %s %d %s | IP: %s | Duration: %s | Body: %d bytes", r.Method, r.URL.Path, rw.status, http.StatusText(rw.status), r.RemoteAddr, duration, len(bodyBytes), ) }) } } type responseWriter struct { http.ResponseWriter status int size int } func (rw *responseWriter) WriteHeader(code int) { if rw.status == 0 { // Защита от двойного вызова WriteHeader rw.status = code rw.ResponseWriter.WriteHeader(code) } } func (rw *responseWriter) Write(b []byte) (int, error) { if rw.status == 0 { rw.WriteHeader(http.StatusOK) } size, err := rw.ResponseWriter.Write(b) rw.size += size return size, err } func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if hijacker, ok := rw.ResponseWriter.(http.Hijacker); ok { return hijacker.Hijack() } return nil, nil, errors.New("response writer does not support hijacking") }