Browse Source

add healthcheck handling (#106)

Add a special path -- `/ok` that can be used as a healthcheck for e.g.
kubernetes or amazon ECS.

When not in debug mode, do not generate logs for the healthcheck path.

This requires implementing our own version of ginrus.Ginrus, as the
upstream one does not support the `notlogged` argument(s) that
gin.LoggerWithWriter has.
dependabot/npm_and_yarn/web/prismjs-1.21.0
memory 8 years ago
committed by Max Schmitt
parent
commit
fab091d75a
  1. 66
      handlers/handlers.go
  2. 10
      handlers/public.go

66
handlers/handlers.go

@ -6,7 +6,6 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/gin-gonic/contrib/ginrus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -27,6 +26,63 @@ type Handler struct {
// DoNotPrivateKeyChecking is used for testing // DoNotPrivateKeyChecking is used for testing
var DoNotPrivateKeyChecking = false var DoNotPrivateKeyChecking = false
type loggerEntryWithFields interface {
WithFields(fields logrus.Fields) *logrus.Entry
}
// Ginrus returns a gin.HandlerFunc (middleware) that logs requests using logrus.
//
// Requests with errors are logged using logrus.Error().
// Requests without errors are logged using logrus.Info().
//
// It receives:
// 1. A time package format string (e.g. time.RFC3339).
// 2. A boolean stating whether to use UTC time zone or local.
// 3. Optionally, a list of paths to skip logging for (this is why
// we are not using upstream github.com/gin-gonic/contrib/ginrus)
func Ginrus(logger loggerEntryWithFields, timeFormat string, utc bool, notlogged ...string) gin.HandlerFunc {
var skip map[string]struct{}
if length := len(notlogged); length > 0 {
skip = make(map[string]struct{}, length)
for _, path := range notlogged {
skip[path] = struct{}{}
}
}
return func(c *gin.Context) {
start := time.Now()
// some evil middlewares modify this values
path := c.Request.URL.Path
c.Next()
// log only when path is not being skipped
if _, ok := skip[path]; !ok {
end := time.Now()
latency := end.Sub(start)
if utc {
end = end.UTC()
}
entry := logger.WithFields(logrus.Fields{
"status": c.Writer.Status(),
"method": c.Request.Method,
"path": path,
"ip": c.ClientIP(),
"latency": latency,
"user-agent": c.Request.UserAgent(),
"time": end.Format(timeFormat),
})
if len(c.Errors) > 0 {
// Append error field if this is an erroneous request.
entry.Error(c.Errors.String())
} else {
entry.Info()
}
}
}
}
// New initializes the http handlers // New initializes the http handlers
func New(store stores.Store) (*Handler, error) { func New(store stores.Store) (*Handler, error) {
if !util.GetConfig().EnableDebugMode { if !util.GetConfig().EnableDebugMode {
@ -78,7 +134,12 @@ func (h *Handler) setHandlers() error {
if err := h.addTemplatesFromFS([]string{"token.html", "protected.html"}); err != nil { if err := h.addTemplatesFromFS([]string{"token.html", "protected.html"}); err != nil {
return errors.Wrap(err, "could not add templates from FS") return errors.Wrap(err, "could not add templates from FS")
} }
h.engine.Use(ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, false)) if !util.GetConfig().EnableDebugMode {
// if we are not in debug mode, do not log healthchecks
h.engine.Use(Ginrus(logrus.StandardLogger(), time.RFC3339, false, "/ok"))
} else {
h.engine.Use(Ginrus(logrus.StandardLogger(), time.RFC3339, false))
}
protected := h.engine.Group("/api/v1/protected") protected := h.engine.Group("/api/v1/protected")
if util.GetConfig().AuthBackend == "oauth" { if util.GetConfig().AuthBackend == "oauth" {
logrus.Info("Using OAuth auth backend") logrus.Info("Using OAuth auth backend")
@ -96,6 +157,7 @@ func (h *Handler) setHandlers() error {
h.engine.GET("/api/v1/info", h.handleInfo) h.engine.GET("/api/v1/info", h.handleInfo)
h.engine.GET("/d/:id/:hash", h.handleDelete) h.engine.GET("/d/:id/:hash", h.handleDelete)
h.engine.GET("/ok", h.handleHealthcheck)
// Handling the shorted URLs, if no one exists, it checks // Handling the shorted URLs, if no one exists, it checks
// in the filesystem and sets headers for caching // in the filesystem and sets headers for caching

10
handlers/public.go

@ -139,6 +139,16 @@ func (h *Handler) handleGetVisitors(c *gin.Context) {
c.JSON(http.StatusOK, dataSets) c.JSON(http.StatusOK, dataSets)
} }
// handleHealthcheck returns success for healthcheckers without polluting logs
func (h *Handler) handleHealthcheck(c *gin.Context) {
out := struct {
Status string `json:"status"`
}{
"OK",
}
c.JSON(http.StatusOK, out)
}
func (h *Handler) handleInfo(c *gin.Context) { func (h *Handler) handleInfo(c *gin.Context) {
out := struct { out := struct {
util.Info util.Info

Loading…
Cancel
Save