Skip to content

Commit 3a05668

Browse files
docs: add a guide for Golang API Monitoring with Prometheus and Grafana (#22292)
<!--Delete sections as needed --> ## Description add a guide for Golang API Monitoring with Prometheus and Grafana ## Related issues or tickets #22291 ## Reviews <!-- Notes for reviewers here --> <!-- List applicable reviews (optionally @tag reviewers) --> - [ ] Technical review - [x] Editorial review - [ ] Product review --------- Co-authored-by: Craig Osterhout <103533812+craig-osterhout@users.noreply.github.com>
1 parent e0ea510 commit 3a05668

File tree

7 files changed

+643
-0
lines changed

7 files changed

+643
-0
lines changed

Diff for: content/guides/go-prometheus-monitoring/_index.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
description: Containerize a Golang application and monitor it with Prometheus and Grafana.
3+
keywords: golang, prometheus, grafana, monitoring, containerize
4+
title: Monitor a Golang application with Prometheus and Grafana
5+
summary: |
6+
Learn how to containerize a Golang application and monitor it with Prometheus and Grafana.
7+
linkTitle: Monitor with Prometheus and Grafana
8+
languages: [go]
9+
params:
10+
time: 45 minutes
11+
---
12+
13+
The guide teaches you how to containerize a Golang application and monitor it with Prometheus and Grafana.
14+
15+
> **Acknowledgment**
16+
>
17+
> Docker would like to thank [Pradumna Saraf](https://twitter.com/pradumna_saraf) for his contribution to this guide.
18+
19+
## Overview
20+
21+
To make sure your application is working as intended, monitoring is important. One of the most popular monitoring tools is Prometheus. Prometheus is an open-source monitoring and alerting toolkit that is designed for reliability and scalability. It collects metrics from monitored targets by scraping metrics HTTP endpoints on these targets. To visualize the metrics, you can use Grafana. Grafana is an open-source platform for monitoring and observability that allows you to query, visualize, alert on, and understand your metrics no matter where they are stored.
22+
23+
In this guide, you will be creating a Golang server with some endpoints to simulate a real-world application. Then you will expose metrics from the server using Prometheus. Finally, you will visualize the metrics using Grafana. You will containerize the Golang application, and using the Docker Compose file, you will connect all the services: Golang, Prometheus, and Grafana.
24+
25+
## What will you learn?
26+
27+
* Create a Golang application with custom Prometheus metrics.
28+
* Containerize a Golang application.
29+
* Use Docker Compose to run multiple services and connect them together to monitor a Golang application with Prometheus and Grafana.
30+
* Visualize the metrics using Grafana dashboards.
31+
32+
## Prerequisites
33+
34+
- A good understanding of Golang is assumed.
35+
- You must me familiar with Prometheus and creating dashboards in Grafana.
36+
- You must have familiarity with Docker concepts like containers, images, and Dockerfiles. If you are new to Docker, you can start with the [Docker basics](/get-started/docker-concepts/the-basics/what-is-a-container.md) guide.
37+
38+
## Next steps
39+
40+
You will create a Golang server and expose metrics using Prometheus.
+250
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
---
2+
title: Building the application
3+
linkTitle: Understand the application
4+
weight: 10 #
5+
keywords: go, golang, prometheus, grafana, containerize, monitor
6+
description: Learn how to create a Golang server to register metrics with Prometheus.
7+
---
8+
9+
## Prerequisites
10+
11+
* You have a [Git client](https://git-scm.com/downloads). The examples in this section use a command-line based Git client, but you can use any client.
12+
13+
You will be creating a Golang server with some endpoints to simulate a real-world application. Then you will expose metrics from the server using Prometheus.
14+
15+
## Getting the sample application
16+
17+
Clone the sample application to use with this guide. Open a terminal, change
18+
directory to a directory that you want to work in, and run the following
19+
command to clone the repository:
20+
21+
```console
22+
$ git clone https://github.com/dockersamples/go-prometheus-monitoring.git
23+
```
24+
25+
Once you cloned you will see the following content structure inside `go-prometheus-monitoring` directory,
26+
27+
```text
28+
go-prometheus-monitoring
29+
├── CONTRIBUTING.md
30+
├── Docker
31+
│ ├── grafana.yml
32+
│ └── prometheus.yml
33+
├── dashboard.json
34+
├── Dockerfile
35+
├── LICENSE
36+
├── README.md
37+
├── compose.yaml
38+
├── go.mod
39+
├── go.sum
40+
└── main.go
41+
```
42+
43+
- **main.go** - The entry point of the application.
44+
- **go.mod and go.sum** - Go module files.
45+
- **Dockerfile** - Dockerfile used to build the app.
46+
- **Docker/** - Contains the Docker Compose configuration files for Grafana and Prometheus.
47+
- **compose.yaml** - Compose file to launch everything (Golang app, Prometheus, and Grafana).
48+
- **dashboard.json** - Grafana dashboard configuration file.
49+
- **Dockerfile** - Dockerfile used to build the Golang app.
50+
- **compose.yaml** - Docker Compose file to launch everything (Golang app, Prometheus, and Grafana).
51+
- Other files are for licensing and documentation purposes.
52+
53+
## Understanding the application
54+
55+
The following is the complete logic of the application you will find in `main.go`.
56+
57+
```go
58+
package main
59+
60+
import (
61+
"strconv"
62+
63+
"github.com/gin-gonic/gin"
64+
"github.com/prometheus/client_golang/prometheus"
65+
"github.com/prometheus/client_golang/prometheus/promhttp"
66+
)
67+
68+
// Define metrics
69+
var (
70+
HttpRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
71+
Name: "api_http_request_total",
72+
Help: "Total number of requests processed by the API",
73+
}, []string{"path", "status"})
74+
75+
HttpRequestErrorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
76+
Name: "api_http_request_error_total",
77+
Help: "Total number of errors returned by the API",
78+
}, []string{"path", "status"})
79+
)
80+
81+
// Custom registry (without default Go metrics)
82+
var customRegistry = prometheus.NewRegistry()
83+
84+
// Register metrics with custom registry
85+
func init() {
86+
customRegistry.MustRegister(HttpRequestTotal, HttpRequestErrorTotal)
87+
}
88+
89+
func main() {
90+
router := gin.Default()
91+
92+
// Register /metrics before middleware
93+
router.GET("/metrics", PrometheusHandler())
94+
95+
router.Use(RequestMetricsMiddleware())
96+
router.GET("/health", func(c *gin.Context) {
97+
c.JSON(200, gin.H{
98+
"message": "Up and running!",
99+
})
100+
})
101+
router.GET("/v1/users", func(c *gin.Context) {
102+
c.JSON(200, gin.H{
103+
"message": "Hello from /v1/users",
104+
})
105+
})
106+
107+
router.Run(":8000")
108+
}
109+
110+
// Custom metrics handler with custom registry
111+
func PrometheusHandler() gin.HandlerFunc {
112+
h := promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})
113+
return func(c *gin.Context) {
114+
h.ServeHTTP(c.Writer, c.Request)
115+
}
116+
}
117+
118+
// Middleware to record incoming requests metrics
119+
func RequestMetricsMiddleware() gin.HandlerFunc {
120+
return func(c *gin.Context) {
121+
path := c.Request.URL.Path
122+
c.Next()
123+
status := c.Writer.Status()
124+
if status < 400 {
125+
HttpRequestTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
126+
} else {
127+
HttpRequestErrorTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
128+
}
129+
}
130+
}
131+
```
132+
133+
In this part of the code, you have imported the required packages `gin`, `prometheus`, and `promhttp`. Then you have defined a couple of variables, `HttpRequestTotal` and `HttpRequestErrorTotal` are Prometheus counter metrics, and `customRegistry` is a custom registry that will be used to register these metrics. The name of the metric is a string that you can use to identify the metric. The help string is a string that will be shown when you query the `/metrics` endpoint to understand the metric. The reason you are using the custom registry is so avoid the default Go metrics that are registered by default by the Prometheus client. Then using the `init` function you are registering the metrics with the custom registry.
134+
135+
```go
136+
import (
137+
"strconv"
138+
139+
"github.com/gin-gonic/gin"
140+
"github.com/prometheus/client_golang/prometheus"
141+
"github.com/prometheus/client_golang/prometheus/promhttp"
142+
)
143+
144+
// Define metrics
145+
var (
146+
HttpRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
147+
Name: "api_http_request_total",
148+
Help: "Total number of requests processed by the API",
149+
}, []string{"path", "status"})
150+
151+
HttpRequestErrorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
152+
Name: "api_http_request_error_total",
153+
Help: "Total number of errors returned by the API",
154+
}, []string{"path", "status"})
155+
)
156+
157+
// Custom registry (without default Go metrics)
158+
var customRegistry = prometheus.NewRegistry()
159+
160+
// Register metrics with custom registry
161+
func init() {
162+
customRegistry.MustRegister(HttpRequestTotal, HttpRequestErrorTotal)
163+
}
164+
```
165+
166+
In the `main` function, you have created a new instance of the `gin` framework and created three routes. You can see the health endpoint that is on path `/health` that will return a JSON with `{"message": "Up and running!"}` and the `/v1/users` endpoint that will return a JSON with `{"message": "Hello from /v1/users"}`. The third route is for the `/metrics` endpoint that will return the metrics in the Prometheus format. Then you have `RequestMetricsMiddleware` middleware, it will be called for every request made to the API. It will record the incoming requests metrics like status codes and paths. Finally, you are running the gin application on port 8000.
167+
168+
```golang
169+
func main() {
170+
router := gin.Default()
171+
172+
// Register /metrics before middleware
173+
router.GET("/metrics", PrometheusHandler())
174+
175+
router.Use(RequestMetricsMiddleware())
176+
router.GET("/health", func(c *gin.Context) {
177+
c.JSON(200, gin.H{
178+
"message": "Up and running!",
179+
})
180+
})
181+
router.GET("/v1/users", func(c *gin.Context) {
182+
c.JSON(200, gin.H{
183+
"message": "Hello from /v1/users",
184+
})
185+
})
186+
187+
router.Run(":8000")
188+
}
189+
```
190+
191+
Now comes the middleware function `RequestMetricsMiddleware`. This function is called for every request made to the API. It increments the `HttpRequestTotal` counter (different counter for different paths and status codes) if the status code is less than or equal to 400. If the status code is greater than 400, it increments the `HttpRequestErrorTotal` counter (different counter for different paths and status codes). The `PrometheusHandler` function is the custom handler that will be called for the `/metrics` endpoint. It will return the metrics in the Prometheus format.
192+
193+
```golang
194+
// Custom metrics handler with custom registry
195+
func PrometheusHandler() gin.HandlerFunc {
196+
h := promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})
197+
return func(c *gin.Context) {
198+
h.ServeHTTP(c.Writer, c.Request)
199+
}
200+
}
201+
202+
// Middleware to record incoming requests metrics
203+
func RequestMetricsMiddleware() gin.HandlerFunc {
204+
return func(c *gin.Context) {
205+
path := c.Request.URL.Path
206+
c.Next()
207+
status := c.Writer.Status()
208+
if status < 400 {
209+
HttpRequestTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
210+
} else {
211+
HttpRequestErrorTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
212+
}
213+
}
214+
}
215+
```
216+
217+
That's it, this was the complete gist of the application. Now it's time to run and test if the app is registering metrics correctly.
218+
219+
## Running the application
220+
221+
Make sure you are still inside `go-prometheus-monitoring` directory in the terminal, and run the following command. Install the dependencies by running `go mod tidy` and then build and run the application by running `go run main.go`. Then visit `http://localhost:8000/health` or `http://localhost:8000/v1/users`. You should see the output `{"message": "Up and running!"}` or `{"message": "Hello from /v1/users"}`. If you are able to see this then your app is successfully up and running.
222+
223+
224+
Now, check your application's metrics by accessing the `/metrics` endpoint.
225+
Open `http://localhost:8000/metrics` in your browser. You should see similar output to the following.
226+
227+
```sh
228+
# HELP api_http_request_error_total Total number of errors returned by the API
229+
# TYPE api_http_request_error_total counter
230+
api_http_request_error_total{path="/",status="404"} 1
231+
api_http_request_error_total{path="//v1/users",status="404"} 1
232+
api_http_request_error_total{path="/favicon.ico",status="404"} 1
233+
# HELP api_http_request_total Total number of requests processed by the API
234+
# TYPE api_http_request_total counter
235+
api_http_request_total{path="/health",status="200"} 2
236+
api_http_request_total{path="/v1/users",status="200"} 1
237+
```
238+
239+
In the terminal, press `ctrl` + `c` to stop the application.
240+
241+
> [!Note]
242+
> If you don't want to run the application locally, and want to run it in a Docker container, skip to next page where you create a Dockerfile and containerize the application.
243+
244+
## Summary
245+
246+
In this section, you learned how to create a Golang app to register metrics with Prometheus. By implementing middleware functions, you were able to increment the counters based on the request path and status codes.
247+
248+
## Next steps
249+
250+
In the next section, you'll learn how to containerize your application.

0 commit comments

Comments
 (0)