构建应用程序

前提条件

  • 你需要安装一个 Git 客户端。本节中的示例使用基于命令行的 Git 客户端,但你可以使用任何客户端。

你将创建一个带有多个端点的 Golang 服务器来模拟真实世界的应用程序。然后你将使用 Prometheus 暴露服务器的指标。

获取示例应用程序

克隆示例应用程序以与本指南一起使用。打开终端,切换到你想工作的目录,然后运行以下命令克隆仓库

$ git clone https://github.com/dockersamples/go-prometheus-monitoring.git 

克隆完成后,你会在 go-prometheus-monitoring 目录中看到以下内容结构:

go-prometheus-monitoring
├── CONTRIBUTING.md
├── Docker
│   ├── grafana.yml
│   └── prometheus.yml
├── dashboard.json
├── Dockerfile
├── LICENSE
├── README.md
├── compose.yaml
├── go.mod
├── go.sum
└── main.go
  • main.go - 应用程序的入口点。
  • go.mod 和 go.sum - Go 模块文件。
  • Dockerfile - 用于构建应用的 Dockerfile。
  • Docker/ - 包含 Grafana 和 Prometheus 的 Docker Compose 配置文件。
  • compose.yaml - 用于启动所有组件(Golang 应用、Prometheus 和 Grafana)的 Compose 文件。
  • dashboard.json - Grafana 面板配置文件。
  • Dockerfile - 用于构建 Golang 应用的 Dockerfile。
  • compose.yaml - 用于启动所有组件(Golang 应用、Prometheus 和 Grafana)的 Docker Compose 文件。
  • 其他文件用于许可和文档用途。

理解应用程序

以下是你在 main.go 中找到的应用程序的完整逻辑。

package main

import (
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Define metrics
var (
	HttpRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "api_http_request_total",
		Help: "Total number of requests processed by the API",
	}, []string{"path", "status"})

	HttpRequestErrorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "api_http_request_error_total",
		Help: "Total number of errors returned by the API",
	}, []string{"path", "status"})
)

// Custom registry (without default Go metrics)
var customRegistry = prometheus.NewRegistry()

// Register metrics with custom registry
func init() {
	customRegistry.MustRegister(HttpRequestTotal, HttpRequestErrorTotal)
}

func main() {
	router := gin.Default()

	// Register /metrics before middleware
	router.GET("/metrics", PrometheusHandler())
	
	router.Use(RequestMetricsMiddleware())
	router.GET("/health", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Up and running!",
		})
	})
	router.GET("/v1/users", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello from /v1/users",
		})
	})

	router.Run(":8000")
}

// Custom metrics handler with custom registry
func PrometheusHandler() gin.HandlerFunc {
	h := promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})
	return func(c *gin.Context) {
		h.ServeHTTP(c.Writer, c.Request)
	}
}

// Middleware to record incoming requests metrics
func RequestMetricsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		c.Next()
		status := c.Writer.Status()
		if status < 400 {
			HttpRequestTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
		} else {
			HttpRequestErrorTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
		}
	}
}

在这部分代码中,你导入了所需的包:ginprometheuspromhttp。然后你定义了几个变量,HttpRequestTotalHttpRequestErrorTotal 是 Prometheus 计数器指标,而 customRegistry 是一个用于注册这些指标的自定义注册表。指标的名称是一个字符串,你可以用它来标识指标。帮助字符串是当你查询 /metrics 端点时会显示的字符串,用于理解该指标。你使用自定义注册表的原因是为了避免 Prometheus 客户端默认注册的默认 Go 指标。然后使用 init 函数将指标注册到自定义注册表。

import (
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Define metrics
var (
	HttpRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "api_http_request_total",
		Help: "Total number of requests processed by the API",
	}, []string{"path", "status"})

	HttpRequestErrorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "api_http_request_error_total",
		Help: "Total number of errors returned by the API",
	}, []string{"path", "status"})
)

// Custom registry (without default Go metrics)
var customRegistry = prometheus.NewRegistry()

// Register metrics with custom registry
func init() {
	customRegistry.MustRegister(HttpRequestTotal, HttpRequestErrorTotal)
}

main 函数中,你创建了一个新的 gin 框架实例并创建了三个路由。你可以看到路径为 /health 的健康检查端点,它将返回一个 JSON,内容为 {"message": "Up and running!"};以及路径为 /v1/users 的端点,它将返回一个 JSON,内容为 {"message": "Hello from /v1/users"}。第三个路由是 /metrics 端点,它将以 Prometheus 格式返回指标。然后你有一个 RequestMetricsMiddleware 中间件,它将在对 API 发起的每个请求时被调用。它将记录传入请求的指标,例如状态码和路径。最后,你在端口 8000 上运行 gin 应用程序。

func main() {
	router := gin.Default()

	// Register /metrics before middleware
	router.GET("/metrics", PrometheusHandler())
	
	router.Use(RequestMetricsMiddleware())
	router.GET("/health", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Up and running!",
		})
	})
	router.GET("/v1/users", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello from /v1/users",
		})
	})

	router.Run(":8000")
}

现在来看中间件函数 RequestMetricsMiddleware。此函数会在对 API 发起的每个请求时被调用。如果状态码小于或等于 400,它会增加 HttpRequestTotal 计数器(不同路径和状态码有不同的计数器)。如果状态码大于 400,它会增加 HttpRequestErrorTotal 计数器(不同路径和状态码有不同的计数器)。PrometheusHandler 函数是用于 /metrics 端点的自定义处理程序。它将以 Prometheus 格式返回指标。

// Custom metrics handler with custom registry
func PrometheusHandler() gin.HandlerFunc {
	h := promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})
	return func(c *gin.Context) {
		h.ServeHTTP(c.Writer, c.Request)
	}
}

// Middleware to record incoming requests metrics
func RequestMetricsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		c.Next()
		status := c.Writer.Status()
		if status < 400 {
			HttpRequestTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
		} else {
			HttpRequestErrorTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
		}
	}
}

就是这样,这就是应用程序的完整要点。现在是时候运行并测试应用程序是否正确注册指标了。

运行应用程序

确保你仍在终端的 go-prometheus-monitoring 目录中,并运行以下命令。通过运行 go mod tidy 安装依赖项,然后通过运行 go run main.go 构建并运行应用程序。然后访问 http://localhost:8000/healthhttp://localhost:8000/v1/users。你应该会看到输出 {"message": "Up and running!"}{"message": "Hello from /v1/users"}。如果你能看到这些,说明你的应用程序已成功启动并运行。

现在,通过访问 /metrics 端点来检查应用程序的指标。在浏览器中打开 http://localhost:8000/metrics。你应该会看到与以下类似的输出。

# HELP api_http_request_error_total Total number of errors returned by the API
# TYPE api_http_request_error_total counter
api_http_request_error_total{path="/",status="404"} 1
api_http_request_error_total{path="//v1/users",status="404"} 1
api_http_request_error_total{path="/favicon.ico",status="404"} 1
# HELP api_http_request_total Total number of requests processed by the API
# TYPE api_http_request_total counter
api_http_request_total{path="/health",status="200"} 2
api_http_request_total{path="/v1/users",status="200"} 1

在终端中,按 ctrl + c 停止应用程序。

注意

如果你不想在本地运行应用程序,而想在 Docker 容器中运行它,请跳到下一页,在该页中你将创建一个 Dockerfile 并容器化该应用程序。

总结

在本节中,你学习了如何创建 Golang 应用程序以向 Prometheus 注册指标。通过实现中间件函数,你能够根据请求路径和状态码增加计数器。

下一步

在下一节中,你将学习如何容器化你的应用程序。

页面选项