DEV Community

Kishan B
Kishan B

Posted on • Edited on • Originally published at kishaningithub.github.io

3

Exposing custom prometheus metrics with dynamic labels in gin framework

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

(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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

(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)
    }
}

Enter fullscreen mode Exit fullscreen mode
$ 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
Enter fullscreen mode Exit fullscreen mode

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 :-)

References

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay