diff --git a/cmd/server/main.go b/cmd/server/main.go index dc83fe0..b3054e8 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -4,6 +4,7 @@ import ( "context" "log/slog" "net/http" + _ "net/http/pprof" // registers /debug/pprof on http.DefaultServeMux (served only on PprofAddr) "os" "github.com/golang-migrate/migrate/v4" @@ -48,6 +49,18 @@ func main() { go scheduler.New(st, orch).Start(context.Background()) + // Optional pprof endpoint on a separate, non-published listener so we can + // pull live goroutine dumps (docker compose exec app wget -qO- + // localhost:6060/debug/pprof/goroutine?debug=2) without killing the process. + if cfg.PprofAddr != "" { + go func() { + slog.Info("pprof listening", "addr", cfg.PprofAddr) + if err := http.ListenAndServe(cfg.PprofAddr, nil); err != nil { + slog.Error("pprof serve", "err", err) + } + }() + } + slog.Info("listening", "addr", cfg.HTTPAddr) if err := http.ListenAndServe(cfg.HTTPAddr, srv.Router()); err != nil { slog.Error("serve", "err", err) diff --git a/docker-compose.yml b/docker-compose.yml index 43dfbb0..314b0f3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: ENC_KEY: ${ENC_KEY} SESSION_SECRET: ${SESSION_SECRET} WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-4} + PPROF_ADDR: ${PPROF_ADDR:-:6060} depends_on: postgres: condition: service_healthy diff --git a/internal/config/config.go b/internal/config/config.go index cb91069..a0cbe9f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -15,6 +15,7 @@ type Config struct { EncKey []byte SessionSecret []byte WorkerConcurrency int + PprofAddr string // if non-empty, serve net/http/pprof here (e.g. ":6060") } func Load() (Config, error) { @@ -25,6 +26,7 @@ func Load() (Config, error) { AuthPass: os.Getenv("AUTH_PASS"), SessionSecret: []byte(os.Getenv("SESSION_SECRET")), WorkerConcurrency: 4, + PprofAddr: os.Getenv("PPROF_ADDR"), } if v := os.Getenv("WORKER_CONCURRENCY"); v != "" { n, err := strconv.Atoi(v)