更新:当前来源可用here.

我目前正在为一家定制Prometheus出口商工作,价格为changedetection.io英镑,以公布所有注册手表的刮擦和价格指标.

在完成了有效的概念验证后,我正在try 维护该项目并准备好发布到开源社区(例如添加测试文档并使其功能尽可能完整).

在编写这些测试时,我在try 测试在监控的changedetection.io实例中创建新手表时的动态注册时发现了一个问题.为了让出口商无需重新启动即可拾取它们,我在每次收集运行时判断API以获取新添加的手表.

以下是priceCollectorCollect功能:

func (c *priceCollector) Collect(ch chan<- prometheus.Metric) {
    // check for new watches before collecting metrics
    watches, err := c.ApiClient.getWatches()
    if err != nil {
        log.Errorf("error while fetching watches: %v", err)
    } else {
        for id, watch := range watches {
            if _, ok := c.priceMetrics[id]; !ok {
                // create new metric and register it on the DefaultRegisterer
                c.priceMetrics[id] = newPriceMetric(prometheus.Labels{"title": watch.Title}, c.ApiClient, id)
                prometheus.MustRegister(c.priceMetrics[id])

                log.Infof("Picked up new watch %s, registered as metric %s", watch.Title, id)
            }
        }
    }

    // collect all registered metrics
    for _, metric := range c.priceMetrics {
        metric.Collect(ch)
    }
}

newPriceMetric函数只是创建一个新的priceMetric对象,该对象由prometheus.DescApiClient(提供访问www.example.com API的类)和UUID组成changedetection.io

func newPriceMetric(labels prometheus.Labels, apiClient *ApiClient, uuid string) priceMetric {
    return priceMetric{
        desc: prometheus.NewDesc(
            prometheus.BuildFQName(namespace, "watch", "price"),
            "Current price of an offer type watch",
            nil, labels,
        ),
        apiClient: apiClient,
        UUID:      uuid,
    }
}

测试默认行为运行得非常好,并且通过了所有测试,但当我try 测试添加新手表的行为时(当出口器在不重新启动的情况下运行时),测试失败.

注:expectMetricsexpectMetricCount都是普罗米修斯自己的testutil.CollectAndComparetestutil.CollectAndCount的包装函数.助手CreateTestApiServer创建包装的httptest服务器,该服务器基于传递的map[string]*data.WatchItem struct 返回SON有效负载.

func TestAutoregisterPriceCollector(t *testing.T) {
    watchDb := createCollectorTestDb()
    server := testutil.CreateTestApiServer(t, watchDb)
    defer server.Close()

    c, err := NewPriceCollector(server.URL(), "foo-bar-key")
    if err != nil {
        t.Fatal(err)
    }
    expectMetricCount(t, c, 2, "changedetectionio_watch_price")

    // now add a new watch and expect the collector to pick it up
    uuid, newItem := testutil.NewTestItem("Item 3", 300, "USD")
    watchDb[uuid] = newItem

    expectMetrics(t, c, "price_metrics_autoregister.prom", "changedetectionio_watch_price")
    expectMetricCount(t, c, 3, "changedetectionio_watch_price")
}

运行该测试时,运行失败并出现以下错误:

collector_test.go:23:返回了意外的指标:收集指标失败:收集的指标changedetectionio_watch_price标签:{名称:"title"值:"Project 3"}仪表:{值:300},带有未注册的描述符Desc{fqName:"changedetectionio_watch_price",帮助:"报价类型手表的当前价格",constLabels:{title="Project 3"},VariableLabels:{}

我目前认为这个错误与testutil.CollectAnd*的内部工作方式有关.根据函数注释,他们在newly created pedantic Registry上注册收集器,这可能会导致它无法拾取懒惰注册的描述符.

有什么 idea 吗?

推荐答案

我不确定这是否回答了您的问题,但这里有一个例子

package main

import (
    "flag"
    "fmt"
    "log/slog"
    "net/http"
    "sync"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/collectors"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "github.com/prometheus/client_golang/prometheus/testutil"
)

var (
    endpoint = flag.String(
        "endpoint",
        "0.0.0.0:8080",
        "The endpoint of the HTTP server",
    )
)

type TestCollector struct {
    sync.RWMutex

    values []string
    foo    *prometheus.Desc
}

func NewTestCollector() *TestCollector {
    return &TestCollector{
        foo: prometheus.NewDesc(
            "foo",
            "foo",
            []string{
                "labels",
            },
            nil,
        ),
    }
}
func (c *TestCollector) Collect(ch chan<- prometheus.Metric) {
    c.RLock()
    defer c.RUnlock()

    for _, value := range c.values {
        ch <- prometheus.MustNewConstMetric(
            c.foo,
            prometheus.CounterValue,
            1,
            value,
        )
    }
}
func (c *TestCollector) Describe(ch chan<- *prometheus.Desc) {
    ch <- c.foo
}

func main() {
    flag.Parse()

    c := NewTestCollector()

    registry := prometheus.NewRegistry()
    registry.MustRegister(c)

    go func() {
        for i := range 20 {
            value := fmt.Sprintf("value-%02d", i)
            slog.Info("Adding value", "value", value)
            c.Lock()
            c.values = append(c.values, value)
            c.Unlock()
            slog.Info("testutil",
                "count", testutil.CollectAndCount(c, "foo"))

            time.Sleep(15 * time.Second)
        }
    }()

    http.Handle(
        "/metrics",
        promhttp.HandlerFor(
            registry, promhttp.HandlerOpts{}))
    slog.Error("unable to listen",
        "err", http.ListenAndServe(*endpoint, nil))
}

指标foo的集合(每15秒)增长(0.. 19)标签(value-xx)

CollectAndCount随着每次迭代而增加:

日志(log):

2024/04/12 10:43:37 INFO Adding value value=value-00
2024/04/12 10:43:37 INFO testutil count=1
2024/04/12 10:43:52 INFO Adding value value=value-01
2024/04/12 10:43:52 INFO testutil count=2
2024/04/12 10:44:07 INFO Adding value value=value-02
2024/04/12 10:44:07 INFO testutil count=3
2024/04/12 10:44:22 INFO Adding value value=value-03
2024/04/12 10:44:22 INFO testutil count=4
2024/04/12 10:44:37 INFO Adding value value=value-04
2024/04/12 10:44:37 INFO testutil count=5
2024/04/12 10:44:52 INFO Adding value value=value-05

以及:

curl --silent --get http://localhost:8080/metrics
# HELP foo foo
# TYPE foo counter
foo{labels="value-00"} 1
foo{labels="value-01"} 1
foo{labels="value-02"} 1
foo{labels="value-03"} 1
foo{labels="value-04"} 1
foo{labels="value-05"} 1

Go相关问答推荐

区分Terminal和Hook Zerolog Go中的错误级别日志(log)输出

我不能让GIO画一个按钮

+在具有html/模板Golang的Base64中

错误&对象已被Golang在K8s操作符上修改

如何使用 go 读取 RDF xml 文件中的 XML 命名空间属性

如何使用 html/template 在 golang 中运行一个范围内的范围

Go Build删除信息

使用goqu无法获取响应

无法从主域访问子域:无Access-Control-Allow-Origin

无法将 graphql-ws 连接到 gqlgen

golang 中的可变参数函数

从动态输入中提取字符串,其中部分字符串可能不存在

未定义 protoc protoc-gen-go 时间戳

vs 代码调试 go 测试不通过标志

当 git clone 工作时,Go mod tidy 在私有存储库上失败

从另一个没有重复的确定性 int

如何在 docker 文件中安装 golang 包?

Go/Golang:如何从 big.Float 中提取最低有效数字?

AWS EKS 上的 Golang REST API 部署因 CrashLoopBackOff 而失败

如何动态解析 Go Fiber 中的请求正文?