refatoração de segurança e logs

This commit is contained in:
Luiz Silva 2026-01-01 19:13:24 -03:00
parent 6873b87a85
commit 663a8d5bf2
12 changed files with 362 additions and 37 deletions

View file

@ -0,0 +1,91 @@
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)
}