package main import ( "context" "errors" // "flag" "fmt" "io" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "git.runcible.io/learning/pulley/internal/config" "git.runcible.io/learning/pulley/internal/logging" "github.com/jackc/pgx/v5/pgxpool" ) const Version = "1.0.0" //type config struct { // port int // env string // logLevel string //} type application struct { config config.ServiceConfig logger *slog.Logger } func run(ctx context.Context, w io.Writer, args []string) error { // var cfg config // flagSet := flag.NewFlagSet(args[0], flag.ExitOnError) // flagSet.IntVar(&cfg.port, "port", 5002, "API server port") // flagSet.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)") // flagSet.StringVar(&cfg.logLevel, "log-level", "INFO", "Logging Level (INFO|DEBUG|WARN|ERROR)") // if err := flagSet.Parse(args[1:]); err != nil { // return err // } cfg := config.GetServiceConfig() logger := logging.InitLogging(cfg.LogLevel, w, true) signalCtx, stop := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT) defer func() { slog.Info("Calling signal watcher stop.") stop() }() // appCtx is a context that can be programatically called to initiate the shutdown process // it inherits from signalCtx which will stop on receipt of an OS signal. You can think of // appCtx as responding to internal signals in the app, and signalCtx as responding to // external signals sent to the app. appCtx, cancel := context.WithCancel(signalCtx) defer cancel() pool, err := pgxpool.New(appCtx, cfg.DatabaseUri) if err != nil { slog.Error("Error encountered in creating pgx postgres connection pool", "error", err.Error()) return err } defer func() { slog.Info("Closing database connection pool") pool.Close() }() if err := pool.Ping(appCtx); err != nil { slog.Error("Error in attempting first postgres database connection") return err } app := application{config: cfg, logger: logger} srv := &http.Server{ Addr: fmt.Sprintf("%s:%d", "0.0.0.0", app.config.Port), ErrorLog: slog.NewLogLogger(app.logger.Handler(), slog.LevelError), IdleTimeout: time.Minute, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 524288, // 0.5 mb Handler: app.routes(), } slog.Info(fmt.Sprintf("Listening on http://%s", srv.Addr)) defer func() { slog.Debug("Must have hit a graceful shutdown") }() go func() { if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { slog.Error("Error encounterd by webserver in listen loop", "error", err.Error()) cancel() } }() // Handle graceful shutdown // This was an older way to do it before 1.16. Now we can use signal.NotifyContext // sigChan := make(chan os.Signal, 1) // signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // Block until signal is received //<-sigChan slog.Info("Listening for shutdown...") <-appCtx.Done() slog.Info("Shutdown intitiated") // We don't want to inherit from appCtx because it has already been canceled at this point shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownRelease() if err := srv.Shutdown(shutdownCtx); err != nil { slog.Error("Failed to close within context timeout. Forcing server close.") srv.Close() return err } return nil } func main() { ctx := context.Background() if err := run(ctx, os.Stdout, os.Args); err != nil { slog.Error(err.Error()) os.Exit(1) } }