Expand the documentation comment for CLIEntry to provide more context about what the function does, including its use of the fx dependency injection framework, signal handling, and blocking behavior.
103 lines
2.6 KiB
Go
103 lines
2.6 KiB
Go
package routewatch
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
const (
|
|
// shutdownTimeout is the maximum time allowed for graceful shutdown
|
|
shutdownTimeout = 60 * time.Second
|
|
// debugInterval is how often to log debug stats
|
|
debugInterval = 60 * time.Second
|
|
// bytesPerMB is bytes per megabyte
|
|
bytesPerMB = 1024 * 1024
|
|
)
|
|
|
|
// logDebugStats logs goroutine count and memory usage
|
|
func logDebugStats(logger *logger.Logger) {
|
|
// Only run if DEBUG env var contains "routewatch"
|
|
debugEnv := os.Getenv("DEBUG")
|
|
if !strings.Contains(debugEnv, "routewatch") {
|
|
return
|
|
}
|
|
|
|
ticker := time.NewTicker(debugInterval)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
var m runtime.MemStats
|
|
runtime.ReadMemStats(&m)
|
|
|
|
logger.Debug("System stats",
|
|
"goroutines", runtime.NumGoroutine(),
|
|
"alloc_mb", m.Alloc/bytesPerMB,
|
|
"total_alloc_mb", m.TotalAlloc/bytesPerMB,
|
|
"sys_mb", m.Sys/bytesPerMB,
|
|
"num_gc", m.NumGC,
|
|
"heap_alloc_mb", m.HeapAlloc/bytesPerMB,
|
|
"heap_sys_mb", m.HeapSys/bytesPerMB,
|
|
"heap_idle_mb", m.HeapIdle/bytesPerMB,
|
|
"heap_inuse_mb", m.HeapInuse/bytesPerMB,
|
|
"heap_released_mb", m.HeapReleased/bytesPerMB,
|
|
"stack_inuse_mb", m.StackInuse/bytesPerMB,
|
|
)
|
|
}
|
|
}
|
|
|
|
// CLIEntry is the main entry point for the routewatch command-line interface.
|
|
// It initializes the application using the fx dependency injection framework,
|
|
// sets up signal handling for graceful shutdown, and starts the RouteWatch service.
|
|
// This function blocks until the application receives a shutdown signal or encounters
|
|
// a fatal error.
|
|
func CLIEntry() {
|
|
app := fx.New(
|
|
getModule(),
|
|
fx.StopTimeout(shutdownTimeout), // Allow 60 seconds for graceful shutdown
|
|
fx.Invoke(func(lc fx.Lifecycle, rw *RouteWatch, logger *logger.Logger, shutdowner fx.Shutdowner) {
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(ctx context.Context) error {
|
|
// Start debug stats logging
|
|
go logDebugStats(logger)
|
|
|
|
// Handle shutdown signals
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
<-sigCh
|
|
logger.Info("Received shutdown signal")
|
|
if err := shutdowner.Shutdown(); err != nil {
|
|
logger.Error("Failed to shutdown gracefully", "error", err)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
if err := rw.Run(ctx); err != nil {
|
|
logger.Error("RouteWatch error", "error", err)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
},
|
|
OnStop: func(_ context.Context) error {
|
|
logger.Info("Shutting down RouteWatch")
|
|
rw.Shutdown()
|
|
|
|
return nil
|
|
},
|
|
})
|
|
}),
|
|
)
|
|
|
|
app.Run()
|
|
}
|