refatoração de segurança e logs
This commit is contained in:
parent
6873b87a85
commit
663a8d5bf2
12 changed files with 362 additions and 37 deletions
91
internal/elinps/logging.go
Normal file
91
internal/elinps/logging.go
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue