We will be using golang, gin, prometheus go library to expose last request time metrics
Out of the box metrics
Spin up a gin go application which exposes the metrics which comes out of the box via the /metrics endpoint
package main
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log"
)
func main() {
r := gin.Default()
r.GET("/metrics", func(c *gin.Context) {
handler := promhttp.Handler()
handler.ServeHTTP(c.Writer, c.Request)
})
err := r.Run(":8080")
if err != nil {
log.Fatalln(err)
}
}
Run the application using command go run main.go
and
When you hit the /metrics
you will see the following
$ curl http://localhost:8080/metrics
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0
go_gc_duration_seconds_sum 0
go_gc_duration_seconds_count 0
# HELP go_threads Number of OS threads created.
# TYPE go_threads gauge
go_threads 7
# HELP promhttp_metric_handler_requests_in_flight Current number of scrapes being served.
# TYPE promhttp_metric_handler_requests_in_flight gauge
promhttp_metric_handler_requests_in_flight 1
(Truncated the above for brevity)
Custom last_request_received_time metric
Now let us expose our custom metric which is the last request time stamp
Going with gauge data type as for this use case we are "setting" the value as a timestamp
package main
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log"
)
func main() {
r := gin.Default()
// Gauge registration
lastRequestReceivedTime := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "last_request_received_time",
Help: "Time when the last request was processed",
})
err := prometheus.Register(lastRequestReceivedTime)
handleErr(err)
// Middleware to set lastRequestReceivedTime for all requests
r.Use(func(context *gin.Context) {
lastRequestReceivedTime.SetToCurrentTime()
})
// Metrics handler
r.GET("/metrics", func(c *gin.Context) {
handler := promhttp.Handler()
handler.ServeHTTP(c.Writer, c.Request)
})
err = r.Run(":8080")
handleErr(err)
}
func handleErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
So now when you hit the metrics endpoint you will observe the newly created last_request_received_time
metric.
$ curl http://localhost:8080/metrics
# HELP last_request_received_time Time when the last request was processed
# TYPE last_request_received_time gauge
last_request_received_time 1.63186694449664e+09
(removed the other metrics for brevity)
Custom last_request_received_time with dynamic labels
Now say we want to categorize this based on HTTP headers(Eg userId, Operation type). What this means is the label names are constant but the values are different.
For this case we have the GaugeVec type in the library
package main
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log"
"net/http"
)
var (
HeaderUserId = "user_id"
HeaderOperationType = "operation_type"
)
func main() {
r := gin.Default()
// Gauge registration
lastRequestReceivedTime := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "last_request_received_time",
Help: "Time when the last request was processed",
}, []string{HeaderUserId, HeaderOperationType})
err := prometheus.Register(lastRequestReceivedTime)
handleErr(err)
// Metrics handler
r.GET("/metrics", func(c *gin.Context) {
handler := promhttp.Handler()
handler.ServeHTTP(c.Writer, c.Request)
})
// Middleware to set lastRequestReceivedTime for all requests
middleware := func(context *gin.Context) {
lastRequestReceivedTime.With(prometheus.Labels{
HeaderUserId: context.GetHeader(HeaderUserId),
HeaderOperationType: context.GetHeader(HeaderOperationType),
}).SetToCurrentTime()
}
// Request handler
r.GET("/data", middleware, func(c *gin.Context) {
c.JSON(http.StatusOK, map[string]string{"status": "success"})
})
err = r.Run(":8080")
handleErr(err)
}
func handleErr(err error) {
if err != nil {
log.Fatalln(err)
}
}
$ curl -H "user_id: user1" -H "operation_type: fetch" http://localhost:8080/data
{"status":"success"}
$ curl -H "user_id: user1" -H "operation_type: view" http://localhost:8080/data
{"status":"success"}
$ curl -H "user_id: user1" -H "operation_type: upload" http://localhost:8080/data
{"status":"success"}
$ curl http://localhost:8080/metrics
# HELP last_request_received_time Time when the last request was processed
# TYPE last_request_received_time gauge
last_request_received_time{operation_type="fetch",user_id="user1"} 1.6318757060797539e+09
last_request_received_time{operation_type="upload",user_id="user1"} 1.631875691071805e+09
last_request_received_time{operation_type="view",user_id="user1"} 1.631875931726781e+09
As we see above for the same metric we have different valued labels
The code for this is available at
https://github.com/kishaningithub/lastrequesttimemetrics
Feedback is welcome :-)