diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index acbe865..25261ce 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -5,6 +5,9 @@ import ( "encoding/json" "errors" "log" + "net" + "net/http" + _ "net/http/pprof" // registers /debug/pprof/* on http.DefaultServeMux "time" "github.com/gofiber/fiber/v2" @@ -426,6 +429,22 @@ func main() { log.Printf("Static file serving disabled (backend=%s)", resolvedBackend) } + // pprof server (separate from the API). Bind to 127.0.0.1 by default; + // in containers set PPROF_BIND=0.0.0.0 and gate access at nginx. + if cfg.PprofEnabled { + addr := net.JoinHostPort(cfg.PprofBind, cfg.PprofPort) + go func() { + pprofSrv := &http.Server{ + Addr: addr, + ReadHeaderTimeout: 5 * time.Second, + } + log.Printf("pprof listening on %s (/debug/pprof/)", addr) + if err := pprofSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Printf("pprof server error: %v", err) + } + }() + } + log.Printf("Server starting on :%s", cfg.Port) if err := app.Listen(":" + cfg.Port); err != nil { log.Printf("Server error: %v", err) diff --git a/backend/pkg/config/config.go b/backend/pkg/config/config.go index 95cd314..5555200 100644 --- a/backend/pkg/config/config.go +++ b/backend/pkg/config/config.go @@ -66,6 +66,11 @@ type Config struct { // Security MaxRequestBodyMB int // max request body in MB (default 10) BcryptCost int // bcrypt cost for password hashing (default 12) + + // pprof — profiling endpoints (net/http/pprof) + PprofEnabled bool // enable a separate pprof HTTP server (default false) + PprofBind string // bind address (default "127.0.0.1"; use "0.0.0.0" in containers) + PprofPort string // pprof port (default "6060") } func Load() *Config { @@ -120,6 +125,10 @@ func Load() *Config { EmailVerificationRequired: getEnv("EMAIL_VERIFICATION_REQUIRED", "false") == "true", MaxRequestBodyMB: getEnvInt("MAX_REQUEST_BODY_MB", 10), BcryptCost: getBcryptCost(), + + PprofEnabled: getEnvBool("PPROF_ENABLED", false), + PprofBind: getEnv("PPROF_BIND", "127.0.0.1"), + PprofPort: getEnv("PPROF_PORT", "6060"), } } diff --git a/docker-compose.yml b/docker-compose.yml index 9caa50a..a55068c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,6 +54,9 @@ services: PGWEB_URL: /pgweb/ GRAFANA_URL: /grafana/ PROMETHEUS_URL: /prometheus/ + PPROF_ENABLED: "true" + PPROF_BIND: 0.0.0.0 + PPROF_PORT: "6060" depends_on: postgres: condition: service_healthy diff --git a/nginx/default.conf b/nginx/default.conf index 348efd1..0fc4f72 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -34,6 +34,27 @@ server { proxy_pass http://backend:8080; } + # pprof (Go runtime profiles) — backend:6060 + # NOTE: expose only in dev / behind auth in prod (leaks runtime info, + # CPU/trace profiles consume resources). + location = /debug/pprof { + return 301 /debug/pprof/; + } + + location /debug/pprof/ { + allow 127.0.0.1; + allow 10.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + deny all; + + proxy_pass http://backend:6060/debug/pprof/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_buffering off; + proxy_read_timeout 120s; + } + # Static uploads location /static/ { proxy_pass http://backend:8080;