mirror of https://github.com/aptly-dev/aptly
117 lines
3.5 KiB
Go
117 lines
3.5 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// Only use base path as label value (e.g.: /api/repos) because of time series cardinality
|
|
// See https://prometheus.io/docs/practices/naming/#labels
|
|
func getBasePath(c *gin.Context) string {
|
|
segment0, err := getURLSegment(c.Request.URL.Path, 0)
|
|
if err != nil {
|
|
return "/"
|
|
}
|
|
segment1, err := getURLSegment(c.Request.URL.Path, 1)
|
|
if err != nil {
|
|
return *segment0
|
|
}
|
|
|
|
return *segment0 + *segment1
|
|
}
|
|
|
|
func getURLSegment(url string, idx int) (*string, error) {
|
|
urlSegments := strings.Split(url, "/")
|
|
// Remove segment at index 0 because it's an empty string
|
|
urlSegments = urlSegments[1:cap(urlSegments)]
|
|
|
|
if len(urlSegments) <= idx {
|
|
return nil, fmt.Errorf("index %d out of range, only has %d url segments", idx, len(urlSegments))
|
|
}
|
|
|
|
segmentAtIndex := urlSegments[idx]
|
|
s := fmt.Sprintf("/%s", segmentAtIndex)
|
|
return &s, nil
|
|
}
|
|
|
|
func instrumentHandlerInFlight(g *prometheus.GaugeVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
|
|
return func(c *gin.Context) {
|
|
g.WithLabelValues(c.Request.Method, pathFunc(c)).Inc()
|
|
defer g.WithLabelValues(c.Request.Method, pathFunc(c)).Dec()
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func instrumentHandlerCounter(counter *prometheus.CounterVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
|
|
return func(c *gin.Context) {
|
|
c.Next()
|
|
counter.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Inc()
|
|
}
|
|
}
|
|
|
|
func instrumentHandlerRequestSize(obs prometheus.ObserverVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
|
|
return func(c *gin.Context) {
|
|
c.Next()
|
|
obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(float64(c.Request.ContentLength))
|
|
}
|
|
}
|
|
|
|
func instrumentHandlerResponseSize(obs prometheus.ObserverVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
|
|
return func(c *gin.Context) {
|
|
c.Next()
|
|
var responseSize = math.Max(float64(c.Writer.Size()), 0)
|
|
obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(responseSize)
|
|
}
|
|
}
|
|
|
|
func instrumentHandlerDuration(obs prometheus.ObserverVec, pathFunc func(*gin.Context) string) func(*gin.Context) {
|
|
return func(c *gin.Context) {
|
|
now := time.Now()
|
|
c.Next()
|
|
obs.WithLabelValues(strconv.Itoa(c.Writer.Status()), c.Request.Method, pathFunc(c)).Observe(time.Since(now).Seconds())
|
|
}
|
|
}
|
|
|
|
// JSONLogger is a gin middleware that takes an instance of Logger and uses it for writing access
|
|
// logs that include error messages if there are any.
|
|
func JSONLogger() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Start timer
|
|
start := time.Now()
|
|
path := c.Request.URL.Path
|
|
raw := c.Request.URL.RawQuery
|
|
|
|
// Process request
|
|
c.Next()
|
|
|
|
ts := time.Now()
|
|
if raw != "" {
|
|
path = path + "?" + raw
|
|
}
|
|
|
|
errorMessage := strings.TrimSuffix(c.Errors.ByType(gin.ErrorTypePrivate).String(), "\n")
|
|
l := log.With().Str("remote", c.ClientIP()).Logger().
|
|
With().Str("method", c.Request.Method).Logger().
|
|
With().Str("path", path).Logger().
|
|
With().Str("protocol", c.Request.Proto).Logger().
|
|
With().Str("code", fmt.Sprint(c.Writer.Status())).Logger().
|
|
With().Str("latency", ts.Sub(start).String()).Logger().
|
|
With().Str("agent", c.Request.UserAgent()).Logger()
|
|
|
|
if c.Writer.Status() >= 400 && c.Writer.Status() < 500 {
|
|
l.Warn().Msg(errorMessage)
|
|
} else if c.Writer.Status() >= 500 {
|
|
l.Error().Msg(errorMessage)
|
|
} else {
|
|
l.Info().Msg(errorMessage)
|
|
}
|
|
}
|
|
}
|