From ab74695f4cd743a5a72ba05f38863fed1f97045b Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Thu, 1 Jan 2026 20:53:29 -0600 Subject: [PATCH] Have master start and manage the logger process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Master now: - Starts logger on startup with configurable port and capacity - Restarts logger automatically if it crashes - Stops logger gracefully on shutdown New flags: - --logger-port (default 8085) - --logger-capacity (default 1000000) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- master/main.go | 12 +++-- master/runlogger.go | 106 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 master/runlogger.go diff --git a/master/main.go b/master/main.go index dd629da..00d8273 100644 --- a/master/main.go +++ b/master/main.go @@ -13,16 +13,22 @@ func main() { workers := flag.Int("workers", 1, "number of worker processes") basePort := flag.Int("base-port", 3000, "base port for worker processes") listenPort := flag.Int("port", 8080, "port for the reverse proxy to listen on") + loggerPort := flag.Int("logger-port", 8085, "port for the logger service") + loggerCapacity := flag.Int("logger-capacity", 1000000, "max messages for logger to store") flag.Parse() - // Create worker pool - pool := NewWorkerPool() - // Setup signal handling sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + // Start and manage the logger process + stopLogger := startLogger(*loggerPort, *loggerCapacity) + defer stopLogger() + + // Create worker pool + pool := NewWorkerPool() + fileChanges := make(chan FileChange, 10) go watchFiles(*watchDir, fileChanges) diff --git a/master/runlogger.go b/master/runlogger.go new file mode 100644 index 0000000..51552dd --- /dev/null +++ b/master/runlogger.go @@ -0,0 +1,106 @@ +package main + +import ( + "log" + "os" + "os/exec" + "strconv" + "sync" + "syscall" + "time" +) + +// startLogger starts the logger process and returns a function to stop it. +// It automatically restarts the logger if it crashes. +func startLogger(port int, capacity int) func() { + var mu sync.Mutex + var cmd *exec.Cmd + var stopping bool + + portStr := strconv.Itoa(port) + capacityStr := strconv.Itoa(capacity) + + start := func() *exec.Cmd { + c := exec.Command("../logger/logger", "--port", portStr, "--capacity", capacityStr) + c.Stdout = os.Stdout + c.Stderr = os.Stderr + + if err := c.Start(); err != nil { + log.Printf("[logger] Failed to start: %v", err) + return nil + } + + log.Printf("[logger] Started (pid %d) on port %s", c.Process.Pid, portStr) + return c + } + + // Start initial logger + cmd = start() + + // Monitor and restart on crash + go func() { + for { + mu.Lock() + currentCmd := cmd + mu.Unlock() + + if currentCmd == nil { + time.Sleep(time.Second) + mu.Lock() + if !stopping { + cmd = start() + } + mu.Unlock() + continue + } + + err := currentCmd.Wait() + + mu.Lock() + if stopping { + mu.Unlock() + return + } + + if err != nil { + log.Printf("[logger] Process exited: %v, restarting...", err) + } else { + log.Printf("[logger] Process exited normally, restarting...") + } + + time.Sleep(time.Second) + cmd = start() + mu.Unlock() + } + }() + + // Return stop function + return func() { + mu.Lock() + defer mu.Unlock() + + stopping = true + + if cmd == nil || cmd.Process == nil { + return + } + + log.Printf("[logger] Stopping (pid %d)", cmd.Process.Pid) + cmd.Process.Signal(syscall.SIGTERM) + + // Wait briefly for graceful shutdown + done := make(chan struct{}) + go func() { + cmd.Wait() + close(done) + }() + + select { + case <-done: + log.Printf("[logger] Stopped gracefully") + case <-time.After(5 * time.Second): + log.Printf("[logger] Force killing") + cmd.Process.Kill() + } + } +}