gitlab.com/ignitionrobotics/web/ign-go@v1.0.0-rc4/monitoring/prometheus/prometheus.go (about) 1 package prometheus 2 3 import ( 4 "github.com/gorilla/mux" 5 "github.com/prometheus/client_golang/prometheus" 6 "github.com/prometheus/client_golang/prometheus/promhttp" 7 "github.com/urfave/negroni" 8 "gitlab.com/ignitionrobotics/web/ign-go/monitoring" 9 "net/http" 10 "regexp" 11 "strconv" 12 "strings" 13 "time" 14 ) 15 16 const ( 17 // defaultMetricsRoute is the default route used for the metrics handler Prometheus calls. 18 defaultMetricsRoute = "/metrics" 19 ) 20 21 var ( 22 // RequestDurationSeconds tracks the seconds spent serving HTTP requests. 23 // Used to monitor service status and raise alerts (using the RED method) 24 RequestDurationSeconds *prometheus.HistogramVec 25 // TotalRequests tracks the total number of HTTP requests. 26 // Used for auto-scaling 27 TotalRequests *prometheus.CounterVec 28 29 // invalidCharsRE is a regex used to convert a route to a compatible Prometheus label value 30 invalidCharsRE = regexp.MustCompile(`[^a-zA-Z0-9]+`) 31 ) 32 33 // provider is an implementation of a monitoring provider that generates Prometheus metrics. 34 type provider struct { 35 // route is the route that the metrics handle will be served from. 36 route string 37 // requestDurationSeconds contains the RequestDurationSeconds metric 38 requestDurationSeconds *prometheus.HistogramVec 39 // totalRequests contains the TotalRequests metric 40 totalRequests *prometheus.CounterVec 41 } 42 43 // init initializes Prometheus metrics. 44 func init() { 45 // RequestDurationSeconds 46 RequestDurationSeconds = prometheus.NewHistogramVec( 47 prometheus.HistogramOpts{ 48 Subsystem: "http", 49 Name: "request_duration_seconds", 50 Help: "Seconds spent serving HTTP requests.", 51 Buckets: prometheus.DefBuckets, 52 }, 53 []string{"method", "path", "status"}, 54 ) 55 prometheus.MustRegister(RequestDurationSeconds) 56 57 // TotalRequests 58 TotalRequests = prometheus.NewCounterVec( 59 prometheus.CounterOpts{ 60 Subsystem: "http", 61 Name: "requests_total", 62 Help: "The total number of HTTP requests.", 63 }, 64 []string{"status"}, 65 ) 66 prometheus.MustRegister(TotalRequests) 67 } 68 69 // NewPrometheusProvider creates a new Prometheus metrics provider. 70 // route is the route the Prometheus server will contact to get metric information. 71 // If route is an empty string, it will default to "/metrics". 72 // TODO Provide a mechanism to define additional application-specific metrics. 73 func NewPrometheusProvider(route string) monitoring.Provider { 74 // Default route 75 if route == "" { 76 route = defaultMetricsRoute 77 } 78 79 return &provider{ 80 route: route, 81 requestDurationSeconds: RequestDurationSeconds, 82 totalRequests: TotalRequests, 83 } 84 } 85 86 // MetricsRoute returns the route to the metrics endpoint. 87 func (p *provider) MetricsRoute() string { 88 return p.route 89 } 90 91 // MetricsHandler returns an HTTP handler to use with the metrics endpoint. 92 // It uses the default Prometheus handler. 93 func (p *provider) MetricsHandler() http.Handler { 94 return promhttp.Handler() 95 } 96 97 // Middleware returns a middleware that is used to gather metrics from incoming requests. 98 func (p *provider) Middleware(args ...interface{}) negroni.Handler { 99 return negroni.HandlerFunc( 100 func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 101 begin := time.Now() 102 103 // An interceptor is used to access HTTP response properties 104 interceptor := negroni.NewResponseWriter(w) 105 106 // Process the request 107 next.ServeHTTP(interceptor, r) 108 109 // Process request and response values 110 path := p.getRouteName(r) 111 status := strconv.Itoa(interceptor.Status()) 112 took := time.Since(begin).Seconds() 113 114 // Register metric data 115 p.requestDurationSeconds.WithLabelValues(r.Method, path, status).Observe(took) 116 p.totalRequests.WithLabelValues(status).Inc() 117 }, 118 ) 119 } 120 121 // getRouteName converts routes from a path to a string compatible with a Prometheus label value. 122 // Example: '/api/delay/{example}' to 'api_delay_example' 123 func (p *provider) getRouteName(r *http.Request) string { 124 if mux.CurrentRoute(r) != nil { 125 if name := mux.CurrentRoute(r).GetName(); len(name) > 0 { 126 return p.urlToLabel(name) 127 } else if path, err := mux.CurrentRoute(r).GetPathTemplate(); err == nil && len(path) > 0 { 128 return p.urlToLabel(path) 129 } 130 } 131 return p.urlToLabel(r.URL.Path) 132 } 133 134 // urlToLabel converts a URL path to a string compatible with a Prometheus label value. 135 func (p *provider) urlToLabel(path string) string { 136 result := invalidCharsRE.ReplaceAllString(path, "_") 137 result = strings.ToLower(strings.Trim(result, "_")) 138 if result == "" { 139 result = "root" 140 } 141 return result 142 }