package httpserver import ( "context" "fmt" "io" "net/http" "os" "os/signal" "syscall" "time" "github.com/gorilla/mux" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/viper" ) type server struct { appname string version string buildarch string databaseURL string startupTime time.Time port int exitCode int log *zerolog.Logger ctx context.Context cancelFunc context.CancelFunc httpServer *http.Server router *mux.Router } func NewServer(options ...func(s *server)) *server { n := new(server) n.startupTime = time.Now() n.version = "unknown" for _, opt := range options { opt(n) } return n } // this is where we come in from package main. func Run(appname, version, buildarch string) int { s := NewServer(func(i *server) { i.appname = appname if version != "" { i.version = version } i.buildarch = buildarch }) s.configure() s.setupLogging() s.databaseURL = viper.GetString("DBURL") s.port = viper.GetInt("PORT") return s.serve() } func (s *server) serve() int { s.ctx, s.cancelFunc = context.WithCancel(context.Background()) // signal watcher go func() { c := make(chan os.Signal) signal.Ignore(syscall.SIGPIPE) signal.Notify(c, os.Interrupt, syscall.SIGTERM) // block and wait for signal sig := <-c log.Info().Msgf("signal received: %+v", sig) if s.cancelFunc != nil { // cancelling the main context will trigger a clean // shutdown. s.cancelFunc() } }() go s.serveUntilShutdown() for { select { case <-s.ctx.Done(): //aforementioned clean shutdown upon main context //cancellation s.cleanShutdown() return s.exitCode } } } func (s *server) uptime() time.Duration { return time.Since(s.startupTime) } func (s *server) configure() { viper.SetConfigName(s.appname) viper.SetConfigType("yaml") viper.AddConfigPath(fmt.Sprintf("/etc/%s", s.appname)) // path to look for the config file in viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", s.appname)) // call multiple times to add many search paths //viper.SetEnvPrefix(strings.ToUpper(s.appname)) viper.AutomaticEnv() viper.SetDefault("DEBUG", "false") viper.SetDefault("PORT", "8080") viper.SetDefault("DBURL", "") if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // Config file not found; ignore error if desired } else { // Config file was found but another error was produced log.Panic(). Err(err). Msg("config file malformed") } } // if viper.GetBool("debug") { // pp.Print(viper.AllSettings()) // } } func (s *server) setupLogging() { // always log in UTC zerolog.TimestampFunc = func() time.Time { return time.Now().UTC() } log.Logger = log.With().Caller().Logger() zerolog.SetGlobalLevel(zerolog.InfoLevel) tty := false if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 { tty = true } log.Info().Bool("tty", tty).Msg("tty detection") var writers []io.Writer if tty { consoleWriter := zerolog.NewConsoleWriter( func(w *zerolog.ConsoleWriter) { // Customize time format w.TimeFormat = time.RFC3339Nano }, ) writers = append(writers, consoleWriter) } else { writers = append(writers, os.Stdout) } /* logfile := viper.GetString("Logfile") if logfile != "" { logfileDir := filepath.Dir(logfile) err := goutil.Mkdirp(logfileDir) if err != nil { log.Error().Err(err).Msg("unable to create log dir") } hp.logfh, err = os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { panic("unable to open logfile: " + err.Error()) } writers = append(writers, hp.logfh) } */ multi := zerolog.MultiLevelWriter(writers...) logger := zerolog.New(multi).With().Timestamp().Logger().With().Caller().Logger() log.Logger = logger if viper.GetBool("debug") { zerolog.SetGlobalLevel(zerolog.DebugLevel) log.Debug().Bool("debug", true).Send() } s.identify() } func (s *server) identify() { log.Info(). Str("appname", s.appname). Str("version", s.version). Str("buildarch", s.buildarch). Msg("starting") }