Add healthCheck server

This commit is contained in:
Agustin Godnic 2023-06-13 17:41:59 -03:00
parent f2df392552
commit a28fc75610
4 changed files with 152 additions and 0 deletions

119
common/health/http.go Normal file
View File

@ -0,0 +1,119 @@
package health
import (
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/pprof"
"go.uber.org/zap"
)
type Server struct {
app *fiber.App
port string
logger *zap.Logger
}
func NewServer(logger *zap.Logger, port string, pprofEnabled bool, checks ...Check) *Server {
app := fiber.New(fiber.Config{DisableStartupMessage: true})
// config use of middlware.
if pprofEnabled {
app.Use(pprof.New())
}
ctrl := NewController(checks, logger)
api := app.Group("/api")
api.Get("/health", ctrl.HealthCheck)
api.Get("/ready", ctrl.ReadinessCheck)
return &Server{
app: app,
port: port,
logger: logger,
}
}
// Start initiates the serving of HTTP requests.
func (s *Server) Start() {
addr := ":" + s.port
s.logger.Info("Monitoring server starting", zap.String("bindAddress", addr))
go func() {
err := s.app.Listen(addr)
if err != nil {
s.logger.Error("Failed to start monitoring server", zap.Error(err), zap.String("bindAddress", addr))
}
}()
}
// Stop gracefully shuts down the server.
//
// Blocks until all active connections are closed.
func (s *Server) Stop() {
_ = s.app.Shutdown()
}
type controller struct {
checks []Check
logger *zap.Logger
}
// NewController creates a Controller instance.
func NewController(checks []Check, logger *zap.Logger) *controller {
return &controller{checks: checks, logger: logger}
}
// HealthCheck is the HTTP handler for the route `GET /health`.
func (c *controller) HealthCheck(ctx *fiber.Ctx) error {
response := ctx.JSON(struct {
Status string `json:"status"`
}{
Status: "OK",
})
return response
}
// ReadinessCheck is the HTTP handler for the route `GET /ready`.
func (c *controller) ReadinessCheck(ctx *fiber.Ctx) error {
requestCtx := ctx.Context()
requestID := fmt.Sprintf("%v", requestCtx.Value("requestid"))
// For every callback, check whether it is passing
for _, check := range c.checks {
if err := check(requestCtx); err != nil {
c.logger.Error(
"Readiness check failed",
zap.Error(err),
zap.String("requestID", requestID),
)
// Return error information to the caller
response := ctx.
Status(fiber.StatusInternalServerError).
JSON(struct {
Ready string `json:"ready"`
Error string `json:"error"`
}{
Ready: "NO",
Error: err.Error(),
})
return response
}
}
// All checks passed
response := ctx.Status(fiber.StatusOK).
JSON(struct {
Ready string `json:"ready"`
}{
Ready: "OK",
})
return response
}

View File

@ -18,6 +18,13 @@ type Logger struct {
LogLevel string `split_words:"true" default:"INFO"`
}
// Monitoring contains configuration settings for the monitoring endpoints.
type Monitoring struct {
// MonitoringPort defines the TCP port for the monitoring endpoints.
MonitoringPort string `split_words:"true" default:"8000"`
PprofEnabled bool `split_words:"true" default:"false"`
}
// LoadFromEnv loads the configuration settings from environment variables.
//
// If there is a .env file in the current directory, it will be used to

View File

@ -3,7 +3,11 @@ package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"github.com/wormhole-foundation/wormhole-explorer/common/health"
"github.com/wormhole-foundation/wormhole-explorer/common/logger"
"github.com/wormhole-foundation/wormhole-explorer/common/mongohelpers"
"github.com/wormhole-foundation/wormhole-explorer/common/settings"
@ -32,6 +36,27 @@ func main() {
rootLogger.Fatal("Error connecting to MongoDB", zap.Error(err))
}
// Start serving the monitoring endpoints.
plugins := []health.Check{health.Mongo(db.Database)}
server := health.NewServer(
rootLogger,
cfg.MonitoringPort,
cfg.PprofEnabled,
plugins...,
)
server.Start()
// Block until we get a termination signal or the context is cancelled
rootLogger.Info("waiting for termination signal or context cancellation...")
sigterm := make(chan os.Signal, 1)
signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)
select {
case <-rootCtx.Done():
rootLogger.Warn("terminating (root context cancelled)")
case signal := <-sigterm:
rootLogger.Info("terminating (signal received)", zap.String("signal", signal.String()))
}
// Shut down gracefully
rootLogger.Info("disconnecting from MongoDB...")
db.Disconnect(rootCtx)

View File

@ -8,4 +8,5 @@ import (
type ServiceSettings struct {
settings.Logger
settings.MongoDB
settings.Monitoring
}