package main import ( "context" "errors" "fmt" "net/http" "os" "os/signal" "runtime" "syscall" "time" "git.hongxiaowei.com/xiaowei/service/app/services/sales-api/handlers" "github.com/ardanlabs/conf" "go.uber.org/automaxprocs/maxprocs" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var build = "develop" func main() { log, err := initLog("SALES-API") if err != nil { fmt.Println(err) return } if err := run(log); err != nil { log.Errorw("err", err) } } func run(log *zap.SugaredLogger) error { if _, err := maxprocs.Set(); err != nil { return fmt.Errorf("maxprocs:%w", err) } log.Infow("startup", "GOMAXPROCS", runtime.GOMAXPROCS(0)) //configuration cfg := struct { conf.Version Web struct { APIHost string `conf:"default:0.0.0.0:3000"` DebugHost string `conf:"default:0.0.0.0:4000"` ReadTimeout time.Duration `conf:"default:5s"` WriteTimeout time.Duration `conf:"default:10s"` IdleTimeout time.Duration `conf:"default:10s"` ShutDownTimeout time.Duration `conf:"default:10s"` } }{ Version: conf.Version{ SVN: build, Desc: "copyright information here", }, } const prefix = "SALES" help, err := conf.ParseOSArgs(prefix, &cfg) if err != nil { if errors.Is(err, conf.ErrHelpWanted) { fmt.Println(help) return nil } return fmt.Errorf("parsing config:%w", err) } log.Infow("starting service", "version", build) defer log.Infow("shutdown complete") out, err := conf.String(&cfg) if err != nil { return err } log.Infow("startup", "config", out) // ========================================================= // Start Debug Service log.Infow("startup", "status", "debug router started", "host", cfg.Web.DebugHost) debugMux := handlers.DebugMux(build, log) go func() { if err := http.ListenAndServe(cfg.Web.DebugHost, debugMux); err != nil { log.Errorw("shutdown", "status", "debug router closed", "host", cfg.Web.DebugHost, "ERROR", err) } }() log.Infow("startup", "status", "initializing API support") // Make a channel to listen for an interrupt or terminate signal from ths OS. // Use a buffered channel because the signal package requires it shutdown := make(chan os.Signal, 1) signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM) // Construct the mux for the API calls apiMux := handlers.APIMux(handlers.APIMuxConfig{ Shutdown: nil, Log: nil, }) // Construct a server to service the requests against the mux api := http.Server{ Addr: cfg.Web.APIHost, Handler: apiMux, ReadTimeout: cfg.Web.ReadTimeout, WriteTimeout: cfg.Web.WriteTimeout, IdleTimeout: cfg.Web.IdleTimeout, ErrorLog: zap.NewStdLog(log.Desugar()), } //Make a channel to listen for errors coming from the listener.Use a // bufferd channel so the goroutine can exit if we don't collect this error serverErrors := make(chan error, 1) // Start the service listening for api requests go func() { log.Infow("startup", "status", "api router started", "host", api.Addr) serverErrors <- api.ListenAndServe() }() // ===================================================== // Shutdown // Blocking main and waiting for shutdown select { case err := <-serverErrors: return fmt.Errorf("server err:%w", err) case sig := <-shutdown: log.Infow("shutdown", "status", "shutdown started", "signal", sig) defer log.Infow("shutdown", "status", "shutdown complete", "signal", sig) // Give outstanding requests a deadline for completion ctx, cancel := context.WithTimeout(context.Background(), cfg.Web.ShutDownTimeout) defer cancel() if err := api.Shutdown(ctx); err != nil { api.Close() return err } } return nil } func initLog(service string) (*zap.SugaredLogger, error) { config := zap.NewProductionConfig() config.OutputPaths = []string{"stdout"} config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder config.DisableStacktrace = true config.InitialFields = map[string]interface{}{ "service": service, } log, err := config.Build() if err != nil { fmt.Println(err) } return log.Sugar(), err }