47 lines
1.6 KiB
Go
47 lines
1.6 KiB
Go
|
|
// Package middleware — общие gin-middleware: request-id, access-log
|
|||
|
|
// и восстановление после паники. Ставятся в cmd/api в таком порядке,
|
|||
|
|
// где request_id идёт первым — остальные пишут его в свои записи.
|
|||
|
|
package middleware
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"log/slog"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/gin-gonic/gin"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// AccessLog пишет по одной записи на запрос: метод, путь, статус,
|
|||
|
|
// длительность, адрес клиента и request_id.
|
|||
|
|
//
|
|||
|
|
// Уровень выбирается по коду ответа: 5xx — error, 4xx — warn, остальное —
|
|||
|
|
// info. Так обычный трафик не засоряет ошибки, а реальные падения видно
|
|||
|
|
// фильтром уровня.
|
|||
|
|
func AccessLog() gin.HandlerFunc {
|
|||
|
|
return func(c *gin.Context) {
|
|||
|
|
start := time.Now()
|
|||
|
|
c.Next()
|
|||
|
|
|
|||
|
|
attrs := []any{
|
|||
|
|
"method", c.Request.Method,
|
|||
|
|
"path", c.Request.URL.Path,
|
|||
|
|
"status", c.Writer.Status(),
|
|||
|
|
"duration_ms", time.Since(start).Milliseconds(),
|
|||
|
|
"client_ip", c.ClientIP(),
|
|||
|
|
"request_id", RequestIDFrom(c),
|
|||
|
|
}
|
|||
|
|
// Кто именно дёргал — важно для аудита, но не все запросы авторизованы.
|
|||
|
|
if userID := c.GetString("user_id"); userID != "" {
|
|||
|
|
attrs = append(attrs, "user_id", userID)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
switch status := c.Writer.Status(); {
|
|||
|
|
case status >= 500:
|
|||
|
|
slog.Error("request", attrs...)
|
|||
|
|
case status >= 400:
|
|||
|
|
slog.Warn("request", attrs...)
|
|||
|
|
default:
|
|||
|
|
slog.Info("request", attrs...)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|