91 lines
2.2 KiB
Go
91 lines
2.2 KiB
Go
package elinps
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
)
|
|
|
|
// LoggerPadrao cria um logger simples (stdout) adequado para Docker.
|
|
//
|
|
// Regras do projeto:
|
|
// - logs relevantes (rotas, tempo de execução, erros)
|
|
// - NUNCA logar segredos (Authorization/cookies/senha/DSN)
|
|
func LoggerPadrao() *slog.Logger {
|
|
return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
|
}
|
|
|
|
type responseWriterComStatus struct {
|
|
http.ResponseWriter
|
|
status int
|
|
bytes int
|
|
}
|
|
|
|
func (w *responseWriterComStatus) WriteHeader(status int) {
|
|
w.status = status
|
|
w.ResponseWriter.WriteHeader(status)
|
|
}
|
|
|
|
func (w *responseWriterComStatus) Write(b []byte) (int, error) {
|
|
if w.status == 0 {
|
|
w.status = http.StatusOK
|
|
}
|
|
n, err := w.ResponseWriter.Write(b)
|
|
w.bytes += n
|
|
return n, err
|
|
}
|
|
|
|
// MiddlewareLogRequisicao registra uma linha por requisição com:
|
|
// - método, path, status, duração
|
|
// - request_id (se existir)
|
|
// - ip_real (após chi/middleware.RealIP)
|
|
func MiddlewareLogRequisicao(logger *slog.Logger) func(http.Handler) http.Handler {
|
|
if logger == nil {
|
|
logger = slog.Default()
|
|
}
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ini := time.Now()
|
|
ww := &responseWriterComStatus{ResponseWriter: w}
|
|
next.ServeHTTP(ww, r)
|
|
|
|
dur := time.Since(ini)
|
|
attrs := []any{
|
|
"metodo", r.Method,
|
|
"path", r.URL.Path,
|
|
"status", ww.status,
|
|
"dur_ms", dur.Milliseconds(),
|
|
"bytes", ww.bytes,
|
|
"ip_real", ipSomenteHost(r.RemoteAddr),
|
|
}
|
|
if reqID := requestIDFromContext(r.Context()); reqID != "" {
|
|
attrs = append(attrs, "request_id", reqID)
|
|
}
|
|
logger.Info("http_request", attrs...)
|
|
})
|
|
}
|
|
}
|
|
|
|
// ipSomenteHost retorna apenas o IP (sem porta). Se o valor não parecer um IP,
|
|
// devolve string vazia.
|
|
func ipSomenteHost(remoteAddr string) string {
|
|
ip := remoteAddr
|
|
if host, _, err := net.SplitHostPort(remoteAddr); err == nil {
|
|
ip = host
|
|
}
|
|
if net.ParseIP(ip) == nil {
|
|
return ""
|
|
}
|
|
return ip
|
|
}
|
|
|
|
func requestIDFromContext(ctx context.Context) string {
|
|
// O chi/middleware.RequestID coloca o ID no context.
|
|
return middleware.GetReqID(ctx)
|
|
}
|