Skip to content

Commit d1bef20

Browse files
feat: add Prometheus support (Custom Metrics) for Instrumenting the API (#18)
- feat: Add Prometheus (Custom Metrics) for Instrumenting the API - chore: Update Docker Compose with Grafana and Prometheus Services - feat: Separate Redis logic (rate limit) to its dir - fix: Update dependencies
1 parent 6046370 commit d1bef20

File tree

9 files changed

+150
-11
lines changed

9 files changed

+150
-11
lines changed

compose.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ services:
2424
condition: service_healthy
2525
redis:
2626
condition: service_healthy
27+
develop:
28+
watch:
29+
- path: .
30+
action: rebuild
2731

2832
mongo:
2933
container_name: go-mongodb
@@ -44,6 +48,8 @@ services:
4448
image: redis:7.4
4549
volumes:
4650
- redisdata:/data
51+
ports:
52+
- 6379:6379
4753
networks:
4854
- go-network
4955
healthcheck:
@@ -52,9 +58,35 @@ services:
5258
timeout: 10s
5359
retries: 5
5460
start_period: 40s
61+
62+
prometheus:
63+
container_name: prometheus
64+
image: prom/prometheus:v2.55.0
65+
volumes:
66+
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
67+
ports:
68+
- 9090:9090
69+
networks:
70+
- go-network
71+
72+
grafana:
73+
container_name: grafana
74+
image: grafana/grafana:11.3.0
75+
volumes:
76+
- ./grafana/datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yaml
77+
- grafana-data:/var/lib/grafana
78+
ports:
79+
- 3000:3000
80+
networks:
81+
- go-network
82+
environment:
83+
- GF_SECURITY_ADMIN_USER=admin
84+
- GF_SECURITY_ADMIN_PASSWORD=password
85+
5586
volumes:
5687
mongodbdata:
5788
redisdata:
89+
grafana-data:
5890

5991
networks:
6092
go-network:

go.mod

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ require (
88
github.com/go-redis/redis_rate/v10 v10.0.1
99
github.com/joho/godotenv v1.5.1
1010
github.com/magiconair/properties v1.8.7
11+
github.com/prometheus/client_golang v1.20.5
1112
github.com/redis/go-redis/v9 v9.0.2
1213
github.com/vektah/gqlparser/v2 v2.5.16
1314
go.mongodb.org/mongo-driver v1.16.1
1415
)
1516

1617
require (
1718
github.com/agnivade/levenshtein v1.1.1 // indirect
19+
github.com/beorn7/perks v1.0.1 // indirect
1820
github.com/bytedance/sonic v1.12.2 // indirect
1921
github.com/bytedance/sonic/loader v0.2.0 // indirect
20-
github.com/cespare/xxhash/v2 v2.2.0 // indirect
22+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2123
github.com/cloudwego/base64x v0.1.4 // indirect
2224
github.com/cloudwego/iasm v0.2.0 // indirect
2325
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@@ -34,13 +36,18 @@ require (
3436
github.com/json-iterator/go v1.1.12 // indirect
3537
github.com/klauspost/compress v1.17.9 // indirect
3638
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
39+
github.com/kr/text v0.2.0 // indirect
3740
github.com/leodido/go-urn v1.4.0 // indirect
3841
github.com/mattn/go-isatty v0.0.20 // indirect
3942
github.com/mitchellh/mapstructure v1.5.0 // indirect
4043
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4144
github.com/modern-go/reflect2 v1.0.2 // indirect
4245
github.com/montanaflynn/stats v0.7.1 // indirect
46+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
4347
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
48+
github.com/prometheus/client_model v0.6.1 // indirect
49+
github.com/prometheus/common v0.55.0 // indirect
50+
github.com/prometheus/procfs v0.15.1 // indirect
4451
github.com/sosodev/duration v1.3.1 // indirect
4552
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
4653
github.com/ugorji/go/codec v1.2.12 // indirect

go.sum

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsVi
1010
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
1111
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
1212
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
13+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
14+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
1315
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
1416
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
1517
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
@@ -19,12 +21,13 @@ github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKz
1921
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
2022
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
2123
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
22-
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
23-
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
24+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
25+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
2426
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
2527
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
2628
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
2729
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
30+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2831
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2932
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3033
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -71,6 +74,12 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
7174
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
7275
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
7376
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
77+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
78+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
79+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
80+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
81+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
82+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
7483
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
7584
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
7685
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
@@ -86,12 +95,24 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
8695
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
8796
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
8897
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
98+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
99+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
89100
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
90101
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
91102
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
92103
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
104+
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
105+
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
106+
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
107+
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
108+
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
109+
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
110+
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
111+
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
93112
github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE=
94113
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
114+
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
115+
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
95116
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
96117
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
97118
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
@@ -162,8 +183,9 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
162183
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
163184
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
164185
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
165-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
166186
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
187+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
188+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
167189
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
168190
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
169191
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

grafana/datasource.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: 1
2+
datasources:
3+
- name: Prometheus (Main)
4+
type: prometheus
5+
url: http://prometheus:9090
6+
isDefault: true

handler/handler.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,24 @@ import (
66
"github.com/99designs/gqlgen/graphql/handler"
77
"github.com/99designs/gqlgen/graphql/playground"
88
"github.com/Pradumnasaraf/Contributors/graph"
9+
prom "github.com/Pradumnasaraf/Contributors/prometheus"
10+
"github.com/Pradumnasaraf/Contributors/redis"
911
"github.com/gin-gonic/gin"
10-
11-
"github.com/Pradumnasaraf/Contributors/middleware"
12+
"github.com/prometheus/client_golang/prometheus"
13+
"github.com/prometheus/client_golang/prometheus/promhttp"
1214
)
1315

1416
// Graphql handler
1517
func GraphqlHandler() gin.HandlerFunc {
16-
// NewExecutableSchema and Config are in the generated.go file
17-
// Resolver is in the resolver.go file
1818
h := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))
1919
return func(c *gin.Context) {
20-
err := middleware.RateLimiter(c.ClientIP())
20+
21+
prom.HttpRequestTotal()
22+
metrics := prom.HttpRequestDuration()
23+
timer := prometheus.NewTimer(metrics.HttpRequestDuration.WithLabelValues("/query"))
24+
defer timer.ObserveDuration()
25+
26+
err := redis.RateLimiter(c.ClientIP())
2127
if err != nil {
2228
c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too Many Requests"})
2329
return
@@ -34,10 +40,20 @@ func PlaygroundHandler() gin.HandlerFunc {
3440
}
3541
}
3642

43+
// Health handler
3744
func HealthCheckHandler() gin.HandlerFunc {
3845
return func(c *gin.Context) {
3946
c.JSON(200, gin.H{
4047
"message": "healthy",
4148
})
4249
}
4350
}
51+
52+
// Prometheus handler
53+
func PrometheusHandler() gin.HandlerFunc {
54+
registry := prom.PrometheusRegistry
55+
h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
56+
return func(c *gin.Context) {
57+
h.ServeHTTP(c.Writer, c.Request)
58+
}
59+
}

prometheus/prometheus.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package prometheus
2+
3+
import (
4+
"github.com/prometheus/client_golang/prometheus"
5+
)
6+
7+
type metrics struct {
8+
HttpRequestTotal *prometheus.CounterVec
9+
HttpRequestDuration *prometheus.HistogramVec
10+
}
11+
12+
var PrometheusRegistry = prometheus.NewRegistry()
13+
var prometheusMetrics = initializeMetrics(PrometheusRegistry)
14+
15+
func initializeMetrics(reg prometheus.Registerer) *metrics {
16+
requestMetrics := &metrics{
17+
HttpRequestTotal: prometheus.NewCounterVec(prometheus.CounterOpts{
18+
Name: "http_request_total",
19+
Help: "Total number of HTTP requests to the API.",
20+
},
21+
[]string{"path"}),
22+
HttpRequestDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
23+
Name: "http_request_duration_seconds",
24+
Help: "Duration of HTTP requests",
25+
Buckets: []float64{0.01, 0.015, 0.02, 0.025, 0.03},
26+
},
27+
[]string{"path"}),
28+
}
29+
30+
reg.MustRegister(requestMetrics.HttpRequestDuration, requestMetrics.HttpRequestTotal)
31+
return requestMetrics
32+
}
33+
34+
func HttpRequestTotal() {
35+
36+
prometheusMetrics.HttpRequestTotal.WithLabelValues("/query").Inc()
37+
}
38+
39+
func HttpRequestDuration() *metrics {
40+
return prometheusMetrics
41+
}

prometheus/prometheus.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
global:
2+
scrape_interval: 5s
3+
evaluation_interval: 5s
4+
5+
scrape_configs:
6+
- job_name: myapp
7+
static_configs:
8+
- targets: ["api:8080"]
9+

middleware/rateLimit.go renamed to redis/rateLimit.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
package middleware
1+
package redis
22

33
import (
44
"context"
55
"errors"
6+
"log"
67
"os"
78
"strconv"
89

@@ -20,7 +21,7 @@ func RateLimiter(clientIP string) error {
2021
limitInt, _ := strconv.Atoi(os.Getenv("REDIS_RATE_LIMIT"))
2122
res, err := limiter.Allow(ctx, clientIP, redis_rate.PerHour(limitInt))
2223
if err != nil {
23-
panic(err)
24+
log.Fatal("unable to connect to redis instance or check the limit of the incoming request.")
2425
}
2526

2627
if res.Remaining == 0 {

server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"log"
5+
"os"
56

67
"github.com/Pradumnasaraf/Contributors/handler"
78
"github.com/Pradumnasaraf/Contributors/middleware"
@@ -11,12 +12,16 @@ import (
1112
func main() {
1213

1314
router := gin.Default()
15+
1416
// Will bypass the middleware (Auth) for health check
1517
router.GET("/health", handler.HealthCheckHandler())
18+
router.GET("/metrics", handler.PrometheusHandler())
1619

1720
router.Use(middleware.BasicAuth())
21+
1822
router.GET("/", handler.PlaygroundHandler())
1923
router.POST("/query", handler.GraphqlHandler())
2024

25+
log.Printf("Server is running on http://localhost:%s", os.Getenv("PORT"))
2126
log.Fatal(router.Run())
2227
}

0 commit comments

Comments
 (0)