gohttpserver/httpserver/main.go

194 lines
4.2 KiB
Go

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, 1)
signal.Ignore(syscall.SIGPIPE)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
// block and wait for signal
sig := <-c
s.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 range 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()
}
zerolog.SetGlobalLevel(zerolog.InfoLevel)
tty := false
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 {
tty = true
}
var writers []io.Writer
if tty {
// this does cool colorization for console/dev
consoleWriter := zerolog.NewConsoleWriter(
func(w *zerolog.ConsoleWriter) {
// Customize time format
w.TimeFormat = time.RFC3339Nano
},
)
writers = append(writers, consoleWriter)
} else {
// log json in prod for the machines
writers = append(writers, os.Stdout)
}
/*
// this is how you log to a file, if you do that
// sort of thing still
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()
s.log = &logger
//log.Logger = logger
if viper.GetBool("debug") {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
s.log.Debug().Bool("debug", true).Send()
}
s.identify()
}
func (s *server) identify() {
s.log.Info().
Str("appname", s.appname).
Str("version", s.version).
Str("buildarch", s.buildarch).
Msg("starting")
}