diff --git a/plugins/outputs/prometheus_client/README.md b/plugins/outputs/prometheus_client/README.md index d42eecfd4..6cb0cc59e 100644 --- a/plugins/outputs/prometheus_client/README.md +++ b/plugins/outputs/prometheus_client/README.md @@ -23,4 +23,8 @@ This plugin starts a [Prometheus](https://prometheus.io/) Client, it exposes all # Expiration interval for each metric. 0 == no expiration expiration_interval = "60s" + + # Send string metrics as Prometheus labels. + # Unless set to false all string metrics will be sent as labels. + string_as_label = true ``` diff --git a/plugins/outputs/prometheus_client/prometheus_client.go b/plugins/outputs/prometheus_client/prometheus_client.go index bec4f413a..5b416b72e 100644 --- a/plugins/outputs/prometheus_client/prometheus_client.go +++ b/plugins/outputs/prometheus_client/prometheus_client.go @@ -61,6 +61,7 @@ type PrometheusClient struct { ExpirationInterval internal.Duration `toml:"expiration_interval"` Path string `toml:"path"` CollectorsExclude []string `toml:"collectors_exclude"` + StringAsLabel bool `toml:"string_as_label"` server *http.Server @@ -89,6 +90,10 @@ var sampleConfig = ` ## Collectors to enable, valid entries are "gocollector" and "process". ## If unset, both are enabled. collectors_exclude = ["gocollector", "process"] + + # Send string metrics as Prometheus labels. + # Unless set to false all string metrics will be sent as labels. + string_as_label = true ` func (p *PrometheusClient) basicAuth(h http.Handler) http.Handler { @@ -326,11 +331,13 @@ func (p *PrometheusClient) Write(metrics []telegraf.Metric) error { } // Prometheus doesn't have a string value type, so convert string - // fields to labels. - for fn, fv := range point.Fields() { - switch fv := fv.(type) { - case string: - labels[sanitize(fn)] = fv + // fields to labels if enabled. + if p.StringAsLabel { + for fn, fv := range point.Fields() { + switch fv := fv.(type) { + case string: + labels[sanitize(fn)] = fv + } } } @@ -465,6 +472,7 @@ func init() { outputs.Add("prometheus_client", func() telegraf.Output { return &PrometheusClient{ ExpirationInterval: internal.Duration{Duration: time.Second * 60}, + StringAsLabel: true, fam: make(map[string]*MetricFamily), now: time.Now, } diff --git a/plugins/outputs/prometheus_client/prometheus_client_test.go b/plugins/outputs/prometheus_client/prometheus_client_test.go index 276fde009..05bd5264a 100644 --- a/plugins/outputs/prometheus_client/prometheus_client_test.go +++ b/plugins/outputs/prometheus_client/prometheus_client_test.go @@ -22,6 +22,7 @@ func setUnixTime(client *PrometheusClient, sec int64) { func NewClient() *PrometheusClient { return &PrometheusClient{ ExpirationInterval: internal.Duration{Duration: time.Second * 60}, + StringAsLabel: true, fam: make(map[string]*MetricFamily), now: time.Now, } @@ -450,6 +451,40 @@ func TestWrite_StringFields(t *testing.T) { require.False(t, ok) } +func TestDoNotWrite_StringFields(t *testing.T) { + now := time.Now() + p1, err := metric.New( + "foo", + make(map[string]string), + map[string]interface{}{"value": 1.0, "status": "good"}, + now, + telegraf.Counter) + p2, err := metric.New( + "bar", + make(map[string]string), + map[string]interface{}{"status": "needs numeric field"}, + now, + telegraf.Gauge) + var metrics = []telegraf.Metric{p1, p2} + + client := &PrometheusClient{ + ExpirationInterval: internal.Duration{Duration: time.Second * 60}, + StringAsLabel: false, + fam: make(map[string]*MetricFamily), + now: time.Now, + } + + err = client.Write(metrics) + require.NoError(t, err) + + fam, ok := client.fam["foo"] + require.True(t, ok) + require.Equal(t, 0, fam.LabelSet["status"]) + + fam, ok = client.fam["bar"] + require.False(t, ok) +} + func TestExpire(t *testing.T) { client := NewClient()