diff --git a/cmd/httpd/main.go b/cmd/httpd/main.go index f9ed795..c105754 100644 --- a/cmd/httpd/main.go +++ b/cmd/httpd/main.go @@ -5,7 +5,9 @@ import ( "git.eeqj.de/sneak/gohttpserver/internal/database" "git.eeqj.de/sneak/gohttpserver/internal/globals" "git.eeqj.de/sneak/gohttpserver/internal/handlers" + "git.eeqj.de/sneak/gohttpserver/internal/healthcheck" "git.eeqj.de/sneak/gohttpserver/internal/logger" + "git.eeqj.de/sneak/gohttpserver/internal/middleware" "git.eeqj.de/sneak/gohttpserver/internal/server" "go.uber.org/fx" ) @@ -29,6 +31,8 @@ func main() { handlers.New, logger.New, server.New, + middleware.New, + healthcheck.New, ), fx.Invoke(func(*server.Server) {}), ).Run() diff --git a/internal/config/config.go b/internal/config/config.go index be6096c..76481ac 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,8 +20,9 @@ import ( ) type ConfigParams struct { - Globals globals.Globals - Logger logger.Logger + fx.In + Globals *globals.Globals + Logger *logger.Logger } type Config struct { @@ -32,7 +33,7 @@ type Config struct { MetricsUsername string Port int SentryDSN string - params ConfigParams + params *ConfigParams log *zerolog.Logger } @@ -77,6 +78,7 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { MetricsUsername: viper.GetString("METRICS_USERNAME"), MetricsPassword: viper.GetString("METRICS_PASSWORD"), log: log, + params: ¶ms, } if s.Debug { diff --git a/internal/database/database.go b/internal/database/database.go index 3450e6e..a996c3a 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -20,19 +20,19 @@ import ( type DatabaseParams struct { fx.In - Logger logger.Logger - Config config.Config + Logger *logger.Logger + Config *config.Config } type Database struct { URL string log *zerolog.Logger - params DatabaseParams + params *DatabaseParams } func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) { s := new(Database) - s.params = params + s.params = ¶ms s.log = params.Logger.Get() s.log.Info().Msg("Database instantiated") diff --git a/internal/handlers/handlehealthcheck.go b/internal/handlers/handlehealthcheck.go index 9067d0a..34fed69 100644 --- a/internal/handlers/handlehealthcheck.go +++ b/internal/handlers/handlehealthcheck.go @@ -2,29 +2,11 @@ package handlers import ( "net/http" - "time" ) -func (s *Handlers) handleHealthCheck() http.HandlerFunc { - type response struct { - Status string `json:"status"` - Now string `json:"now"` - UptimeSeconds int64 `json:"uptime_seconds"` - UptimeHuman string `json:"uptime_human"` - Version string `json:"version"` - Appname string `json:"appname"` - Maintenance bool `json:"maintenance_mode"` - } +func (s *Handlers) HandleHealthCheck() http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { - resp := &response{ - Status: "ok", - Now: time.Now().UTC().Format(time.RFC3339Nano), - UptimeSeconds: int64(s.uptime().Seconds()), - UptimeHuman: s.params.Server.uptime().String(), - Maintenance: s.params.Server.MaintenanceMode(), - Appname: s.params.Globals.Appname, - Version: s.params.Globals.Version, - } + resp := s.hc.Healthcheck() s.respondJSON(w, req, resp, 200) } } diff --git a/internal/handlers/handleindex.go b/internal/handlers/handleindex.go index 0c79b53..e91fedc 100644 --- a/internal/handlers/handleindex.go +++ b/internal/handlers/handleindex.go @@ -13,7 +13,7 @@ func (s *Handlers) HandleIndex() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { err := indexTemplate.ExecuteTemplate(w, "index", nil) if err != nil { - s.log.Println(err.Error()) + s.log.Error().Err(err).Msg("") http.Error(w, http.StatusText(500), 500) } } diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 5dd0990..e9d652f 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -7,6 +7,7 @@ import ( "git.eeqj.de/sneak/gohttpserver/internal/database" "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/healthcheck" "git.eeqj.de/sneak/gohttpserver/internal/logger" "github.com/rs/zerolog" "go.uber.org/fx" @@ -14,20 +15,23 @@ import ( type HandlersParams struct { fx.In - Logger logger.Logger - Globals globals.Globals - Database database.Database + Logger *logger.Logger + Globals *globals.Globals + Database *database.Database + Healthcheck *healthcheck.Healthcheck } type Handlers struct { - params HandlersParams + params *HandlersParams log *zerolog.Logger + hc *healthcheck.Healthcheck } func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) { s := new(Handlers) - s.params = params + s.params = ¶ms s.log = params.Logger.Get() + s.hc = params.Healthcheck lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { // FIXME compile some templates here or something diff --git a/internal/healthcheck/healthcheck.go b/internal/healthcheck/healthcheck.go new file mode 100644 index 0000000..c7ff3a9 --- /dev/null +++ b/internal/healthcheck/healthcheck.go @@ -0,0 +1,71 @@ +package healthcheck + +import ( + "context" + "time" + + "git.eeqj.de/sneak/gohttpserver/internal/config" + "git.eeqj.de/sneak/gohttpserver/internal/database" + "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/logger" + "github.com/rs/zerolog" + "go.uber.org/fx" +) + +type HealthcheckParams struct { + fx.In + Globals *globals.Globals + Config *config.Config + Logger *logger.Logger + Database *database.Database +} + +type Healthcheck struct { + StartupTime time.Time + log *zerolog.Logger + params *HealthcheckParams +} + +func New(lc fx.Lifecycle, params HealthcheckParams) (*Healthcheck, error) { + s := new(Healthcheck) + s.params = ¶ms + s.log = params.Logger.Get() + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + s.StartupTime = time.Now() + return nil + }, + OnStop: func(ctx context.Context) error { + // FIXME do server shutdown here + return nil + }, + }) + return s, nil +} + +type HealthcheckResponse struct { + Status string `json:"status"` + Now string `json:"now"` + UptimeSeconds int64 `json:"uptime_seconds"` + UptimeHuman string `json:"uptime_human"` + Version string `json:"version"` + Appname string `json:"appname"` + Maintenance bool `json:"maintenance_mode"` +} + +func (s *Healthcheck) uptime() time.Duration { + return time.Since(s.StartupTime) +} + +func (s *Healthcheck) Healthcheck() *HealthcheckResponse { + resp := &HealthcheckResponse{ + Status: "ok", + Now: time.Now().UTC().Format(time.RFC3339Nano), + UptimeSeconds: int64(s.uptime().Seconds()), + UptimeHuman: s.uptime().String(), + Appname: s.params.Globals.Appname, + Version: s.params.Globals.Version, + } + return resp +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 4f9ca53..c127ba2 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -12,7 +12,7 @@ import ( type LoggerParams struct { fx.In - Globals globals.Globals + Globals *globals.Globals } type Logger struct { diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index ba189ff..2686665 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -21,19 +21,19 @@ import ( type MiddlewareParams struct { fx.In - Logger logger.Logger - Globals globals.Globals - Config config.Config + Logger *logger.Logger + Globals *globals.Globals + Config *config.Config } type Middleware struct { log *zerolog.Logger - params MiddlewareParams + params *MiddlewareParams } func New(lc fx.Lifecycle, params MiddlewareParams) (*Middleware, error) { s := new(Middleware) - s.params = params + s.params = ¶ms s.log = params.Logger.Get() return s, nil } diff --git a/internal/server/routes.go b/internal/server/routes.go index 1fb69d1..b283a5a 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -57,33 +57,33 @@ func (s *Server) SetupRoutes() { // complete docs: https://github.com/go-chi/chi //////////////////////////////////////////////////////////////////////// - s.router.Get("/", s.h.handleIndex()) + s.router.Get("/", s.h.HandleIndex()) s.router.Mount("/s", http.StripPrefix("/s", http.FileServer(http.FS(static.Static)))) s.router.Route("/api/v1", func(r chi.Router) { - r.Get("/now", s.h.handleNow()) + r.Get("/now", s.h.HandleNow()) }) // if you want to use a general purpose middleware (http.Handler // wrapper) on a specific HandleFunc route, you need to take the // .ServeHTTP of the http.Handler to get its HandleFunc, viz: - authMiddleware := s.AuthMiddleware() + authMiddleware := s.mw.AuthMiddleware() s.router.Get( "/login", - authMiddleware(s.h.handleLogin()).ServeHTTP, + authMiddleware(s.h.HandleLogin()).ServeHTTP, ) // route that panics for testing // CHANGEME remove this s.router.Get( "/panic", - s.h.handlePanic(), + s.h.HandlePanic(), ) s.router.Get( "/.well-known/healthcheck.json", - s.h.handleHealthCheck(), + s.h.HandleHealthCheck(), ) // set up authenticated /metrics route: diff --git a/internal/server/server.go b/internal/server/server.go index 8bf7350..e85801b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,7 +3,6 @@ package server import ( "context" "fmt" - "log" "net/http" "os" "os/signal" @@ -16,7 +15,6 @@ import ( "git.eeqj.de/sneak/gohttpserver/internal/logger" "git.eeqj.de/sneak/gohttpserver/internal/middleware" "github.com/rs/zerolog" - "github.com/spf13/viper" "go.uber.org/fx" "github.com/getsentry/sentry-go" @@ -34,17 +32,14 @@ import ( type ServerParams struct { fx.In - Logger logger.Logger - Globals globals.Globals - Config config.Config - Middleware middleware.Middleware - Handlers handlers.Handlers + Logger *logger.Logger + Globals *globals.Globals + Config *config.Config + Middleware *middleware.Middleware + Handlers *handlers.Handlers } type Server struct { - appname string - version string - buildarch string startupTime time.Time port int exitCode int @@ -55,8 +50,8 @@ type Server struct { httpServer *http.Server router *chi.Mux params ServerParams - mw middleware.Middleware - h handlers.Handlers + mw *middleware.Middleware + h *handlers.Handlers } func New(lc fx.Lifecycle, params ServerParams) (*Server, error) { @@ -69,11 +64,12 @@ func New(lc fx.Lifecycle, params ServerParams) (*Server, error) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { s.startupTime = time.Now() - s.version = params.Globals.Version - s.Run() + s.Run() // background FIXME + return nil }, OnStop: func(ctx context.Context) error { // FIXME do server shutdown here + return nil }, }) return s, nil @@ -93,22 +89,22 @@ func (s *Server) Run() { // logging before sentry, because sentry logs s.enableSentry() - return s.serve() + s.serve() // FIXME deal with return value } func (s *Server) enableSentry() { s.sentryEnabled = false - if s.Config.SentryDSN == "" { + if s.params.Config.SentryDSN == "" { return } err := sentry.Init(sentry.ClientOptions{ - Dsn: viper.GetString("SENTRY_DSN"), + Dsn: s.params.Config.SentryDSN, Release: fmt.Sprintf("%s-%s", s.params.Globals.Appname, s.params.Globals.Version), }) if err != nil { - log.Fatal().Err(err).Msg("sentry init failure") + s.log.Fatal().Err(err).Msg("sentry init failure") return } s.log.Info().Msg("sentry error reporting activated") @@ -170,11 +166,7 @@ func (s *Server) cleanShutdown() { } } -func (s *Server) uptime() time.Duration { - return time.Since(s.startupTime) -} - -func (s *Server) maintenance() bool { +func (s *Server) MaintenanceMode() bool { return s.params.Config.MaintenanceMode }