为go应用添加prometheus监控指标

创建应用 我们首先从一个最简单的 Go 应用程序开始,在端口 8080 的 /metrics 端点上暴露客户端库的默认注册表,暂时还没有跟踪任何其他自定义的监控指标。
先创建一个名为 instrument-demo 的目录,在该目录下面初始化项目:

$ mkdir instrument-demo && cd instrument-demo $ go mod init github.com/cnych/instrument-demo

上面的命令会在 instrument-demo 目录下面生成一个 go.mod 文件,在同目录下面新建一个 main.go 的入口文件,内容如下所示:
package mainimport ( "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" )func main() { // Serve the default Prometheus metrics registry over HTTP on /metrics. http.Handle("/metrics", promhttp.Handler()) http.ListenAndServe(":8080", nil) }

然后执行下面的命令下载 Prometheus 客户端库依赖:
$ export GOPROXY="https://goproxy.cn" $ go mod tidy go: finding module for package github.com/prometheus/client_golang/prometheus/promhttp go: found github.com/prometheus/client_golang/prometheus/promhttp in github.com/prometheus/client_golang v1.11.0 go: downloading google.golang.org/protobuf v1.26.0-rc.1

然后直接执行 go run 命令启动服务:
$ go run main.go

然后我们可以在浏览器中访问 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_goroutines Number of goroutines that currently exist. # TYPE go_goroutines gauge go_goroutines 6 ...... # HELP go_threads Number of OS threads created. # TYPE go_threads gauge go_threads 8 # 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 # HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code. # TYPE promhttp_metric_handler_requests_total counter promhttp_metric_handler_requests_total{code="200"} 1 promhttp_metric_handler_requests_total{code="500"} 0 promhttp_metric_handler_requests_total{code="503"} 0

我们并没有在代码中添加什么业务逻辑,但是可以看到依然有一些指标数据输出,这是因为 Go 客户端库默认在我们暴露的全局默认指标注册表中注册了一些关于 promhttp 处理器和运行时间相关的默认指标,根据不同指标名称的前缀可以看出:
【为go应用添加prometheus监控指标】go_*:以 go_ 为前缀的指标是关于 Go 运行时相关的指标,比如垃圾回收时间、goroutine 数量等,这些都是 Go 客户端库特有的,其他语言的客户端库可能会暴露各自语言的其他运行时指标。
promhttp_*:来自 promhttp 工具包的相关指标,用于跟踪对指标请求的处理。
这些默认的指标是非常有用,但是更多的时候我们需要自己控制,来暴露一些自定义指标。这就需要我们去实现自定义的指标了。
添加自定义指标 接下来我们来自定义一个的 gauge 指标来暴露当前的温度。创建一个新的文件 custom-metric/main.go,内容如下所示:
package mainimport ( "net/http" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" )func main() { // 创建一个没有任何 label 标签的 gauge 指标 temp := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "home_temperature_celsius", Help: "The current temperature in degrees Celsius.", }) // 在默认的注册表中注册该指标 prometheus.MustRegister(temp) // 设置 gauge 的值为 39 temp.Set(39) // 暴露指标 http.Handle("/metrics", promhttp.Handler()) http.ListenAndServe(":8080", nil) }

上面文件中和最初的文件就有一些变化了:
  • 我们使用 prometheus.NewGauge() 函数创建了一个自定义的 gauge 指标对象,指标名称为 home_temperature_celsius,并添加了一个注释信息。
  • 然后使用 prometheus.MustRegister() 函数在默认的注册表中注册了这个 gauge 指标对象。
  • 通过调用 Set() 方法将 gauge 指标的值设置为 39。
  • 然后像之前一样通过 HTTP 暴露默认的注册表。
    需要注意的是除了 prometheus.MustRegister() 函数之外还有一个 prometheus.Register() 函数,一般在 golang 中我们会将 Mustxxx 开头的函数定义为必须满足条件的函数,如果不满足会返回一个 panic 而不是一个 error 操作,所以如果这里不能正常注册的话会抛出一个 panic。
现在我们来运行这个程序:
$ go run ./custom-metric

启动后重新访问指标接口 http://localhost:8080/metrics,仔细对比我们会发现多了一个名为 home_temperature_celsius 的指标:
... # HELP home_temperature_celsius The current temperature in degrees Celsius. # TYPE home_temperature_celsius gauge home_temperature_celsius 42 ...

这样我们就实现了添加一个自定义的指标的操作,整体比较简单,当然在实际的项目中需要结合业务来确定添加哪些自定义指标。
自定义注册表 前面我们是使用 prometheus.MustRegister() 函数来将指标注册到全局默认注册中,此外我们还可以使用 prometheus.NewRegistry() 函数来创建和使用自己的非全局的注册表。
既然有全局的默认注册表,为什么我们还需要自定义注册表呢?这主要是因为:
  • 全局变量通常不利于维护和测试,创建一个非全局的注册表,并明确地将其传递给程序中需要注册指标的地方,这也一种更加推荐的做法。
  • 全局默认注册表包括一组默认的指标,我们有时候可能希望除了自定义的指标之外,不希望暴露其他的指标。
    下面的示例程序演示了如何创建、使用和暴露一个非全局注册表对象,创建一个文件 custom-registry/main.go,内容如下所示:
    package mainimport ( "net/http" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" )func main() { // 创建一个自定义的注册表 registry := prometheus.NewRegistry() // 可选: 添加 process 和 Go 运行时指标到我们自定义的注册表中 registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) registry.MustRegister(prometheus.NewGoCollector()) // 创建一个简单呃 gauge 指标。 temp := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "home_temperature_celsius", Help: "The current temperature in degrees Celsius.", }) // 使用我们自定义的注册表注册 gauge registry.MustRegister(temp) // 设置 gague 的值为 39 temp.Set(39) // 暴露自定义指标 http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{Registry: registry})) http.ListenAndServe(":8080", nil) }

    上面我们没有使用全局默认的注册表了,而是创建的一个自定义的注册表:
  • 首先使用 prometheus.NewRegistry() 函数创建我们自己的注册表对象。
  • 然后使用自定义注册表对象上面的 MustRegister() 哈是来注册 guage 指标,而不是调用 prometheus.MustRegister() 函数(这会使用全局默认的注册表)。
  • 如果我们希望在自定义注册表中也有进程和 Go 运行时相关的指标,我们可以通过实例化 Collector 收集器来添加他们。
  • 最后在暴露指标的时候必须通过调用 promhttp.HandleFor() 函数来创建一个专门针对我们自定义注册表的 HTTP 处理器,为了同时暴露前面示例中的 promhttp_* 相关的指标,我们还需要在 promhttp.HandlerOpts 配置对象的 Registry 字段中传递我们的注册表对象。
    同样我们重新运行上面的自定义注册表程序:
    $ go run ./custom-metric

    启动后再次访问指标接口 http://localhost:8080/metrics,可以发现和上面示例中的指标数据是相同的。
指标定制
  • Gauges(仪表盘):Gauge类型代表一种样本数据可以任意变化的指标,即可增可减。
  • Counters(计数器):counter类型代表一种样本数据单调递增的指标,即只增不减,除非监控系统发生了重置。
  • Histograms(直方图):创建直方图指标比 counter 和 gauge 都要复杂,因为需要配置把观测值归入的 bucket 的数量,以及每个 bucket 的上边界。Prometheus 中的直方图是累积的,所以每一个后续的 bucket 都包含前一个 bucket 的观察计数,所有 bucket 的下限都从 0 开始的,所以我们不需要明确配置每个 bucket 的下限,只需要配置上限即可。
  • Summaries(摘要):与Histogram类似类型,用于表示一段时间内的数据采样结果(通常是请求持续时间或响应大小等),但它直接存储了分位数(通过客户端计算,然后展示出来),而不是通过区间计算
Gauges 前面的示例我们已经了解了如何添加 gauge 类型的指标,创建了一个没有任何标签的指标,直接使用 prometheus.NewGauge() 函数即可实例化一个 gauge 类型的指标对象,通过 prometheus.GaugeOpts 对象可以指定指标的名称和注释信息:
queueLength := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "queue_length", Help: "The number of items in the queue.", })

我们知道 gauge 类型的指标值是可以上升或下降的,所以我们可以为 gauge 指标设置一个指定的值,所以 gauge 指标对象暴露了 Set()、Inc()、Dec()、Add() 和 Sub() 这些函数来更改指标值:
// 使用 Set() 设置指定的值 queueLength.Set(0)// 增加或减少 queueLength.Inc()// +1:gauge增加1. queueLength.Dec()// -1:gauge减少1. queueLength.Add(23) // 增加23个增量. queueLength.Sub(42) // 减少42个.

Counters 要创建一个 counter 类型的指标和 gauge 比较类似,只是用 prometheus.NewCounter() 函数来初始化指标对象:
totalRequests := prometheus.NewCounter(prometheus.CounterOpts{ Name: "http_requests_total", Help: "The total number of handled HTTP requests.", })

我们知道 counter 指标只能随着时间的推移而不断增加,所以我们不能为其设置一个指定的值或者减少指标值,所以该对象下面只有 Inc() 和 Add() 两个函数:
totalRequests.Inc()// +1:计数器增加1. totalRequests.Add(23) // +n:计数器增加23.

当服务进程重新启动的时候,counter 指标值会被重置为 0,不过不用担心数据错乱,我们一般会使用的 rate() 函数会自动处理。
最终 counter 指标会被渲染成如下所示的数据:
# HELP http_requests_total The total number of handled HTTP requests. # TYPE http_requests_total counter http_requests_total 7734

    推荐阅读