Skip to content

Commit

Permalink
feat(outputs.loki): add option for metric name label
Browse files Browse the repository at this point in the history
This allows the user to specify the label name used to store the metric
name. This label was added in influxdata#10001, but a user may not want this value
in which case they could set it to an empty string or use some other
customer value.

fixes: influxdata#13146
  • Loading branch information
powersj committed May 3, 2023
1 parent a868add commit 4c5f485
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 27 deletions.
6 changes: 6 additions & 0 deletions plugins/outputs/loki/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ to use them.
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"

## Metric Name Label
## Label to use for the metric name to when sending metrics. If set to an
## empty string, this will not add the label. This is NOT suggested as there
## is no way to differentiate between multiple metrics.
# metric_name_label = "__name"
```
31 changes: 18 additions & 13 deletions plugins/outputs/loki/loki.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,18 @@ const (
)

type Loki struct {
Domain string `toml:"domain"`
Endpoint string `toml:"endpoint"`
Timeout config.Duration `toml:"timeout"`
Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"`
Headers map[string]string `toml:"http_headers"`
ClientID string `toml:"client_id"`
ClientSecret string `toml:"client_secret"`
TokenURL string `toml:"token_url"`
Scopes []string `toml:"scopes"`
GZipRequest bool `toml:"gzip_request"`
Domain string `toml:"domain"`
Endpoint string `toml:"endpoint"`
Timeout config.Duration `toml:"timeout"`
Username config.Secret `toml:"username"`
Password config.Secret `toml:"password"`
Headers map[string]string `toml:"http_headers"`
ClientID string `toml:"client_id"`
ClientSecret string `toml:"client_secret"`
TokenURL string `toml:"token_url"`
Scopes []string `toml:"scopes"`
GZipRequest bool `toml:"gzip_request"`
MetricNameLabel string `toml:"metric_name_label"`

url string
client *http.Client
Expand Down Expand Up @@ -119,7 +120,9 @@ func (l *Loki) Write(metrics []telegraf.Metric) error {
})

for _, m := range metrics {
m.AddTag("__name", m.Name())
if l.MetricNameLabel != "" {
m.AddTag(l.MetricNameLabel, m.Name())
}

tags := m.TagList()
var line string
Expand Down Expand Up @@ -197,6 +200,8 @@ func (l *Loki) writeMetrics(s Streams) error {

func init() {
outputs.Add("loki", func() telegraf.Output {
return &Loki{}
return &Loki{
MetricNameLabel: "__name",
}
})
}
72 changes: 58 additions & 14 deletions plugins/outputs/loki/loki_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ func TestStatusCode(t *testing.T) {
w.WriteHeader(tt.statusCode)
})

err = tt.plugin.Connect()
require.NoError(t, err)
require.NoError(t, tt.plugin.Connect())

err = tt.plugin.Write([]telegraf.Metric{getMetric()})
tt.errFunc(t, err)
Expand Down Expand Up @@ -167,8 +166,7 @@ func TestContentType(t *testing.T) {
w.WriteHeader(http.StatusOK)
})

err = tt.plugin.Connect()
require.NoError(t, err)
require.NoError(t, tt.plugin.Connect())

err = tt.plugin.Write([]telegraf.Metric{getMetric()})
require.NoError(t, err)
Expand Down Expand Up @@ -226,23 +224,72 @@ func TestContentEncodingGzip(t *testing.T) {
require.Len(t, s.Streams, 1)
require.Len(t, s.Streams[0].Logs, 1)
require.Len(t, s.Streams[0].Logs[0], 2)
require.Equal(t, map[string]string{"__name": "log", "key1": "value1"}, s.Streams[0].Labels)
require.Equal(t, map[string]string{"key1": "value1"}, s.Streams[0].Labels)
require.Equal(t, "123000000000", s.Streams[0].Logs[0][0])
require.Contains(t, s.Streams[0].Logs[0][1], "line=\"my log\"")
require.Contains(t, s.Streams[0].Logs[0][1], "field=\"3.14\"")

w.WriteHeader(http.StatusNoContent)
})

err = tt.plugin.Connect()
require.NoError(t, err)
require.NoError(t, tt.plugin.Connect())

err = tt.plugin.Write([]telegraf.Metric{getMetric()})
require.NoError(t, err)
})
}
}

func TestMetricNameLabel(t *testing.T) {
ts := httptest.NewServer(http.NotFoundHandler())
defer ts.Close()

u, err := url.Parse(fmt.Sprintf("http://%s", ts.Listener.Addr().String()))
require.NoError(t, err)

tests := []struct {
name string
metricNameLabel string
}{
{
name: "no label",
metricNameLabel: "",
},
{
name: "custom label",
metricNameLabel: "foobar",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body)
require.NoError(t, err)

var s Request
require.NoError(t, json.Unmarshal(payload, &s))

switch tt.metricNameLabel {
case "":
require.Equal(t, map[string]string{"key1": "value1"}, s.Streams[0].Labels)
case "foobar":
require.Equal(t, map[string]string{"foobar": "log", "key1": "value1"}, s.Streams[0].Labels)
}

w.WriteHeader(http.StatusNoContent)
})

l := Loki{
Domain: u.String(),
MetricNameLabel: tt.metricNameLabel,
}
require.NoError(t, l.Connect())
require.NoError(t, l.Write([]telegraf.Metric{getMetric()}))
})
}
}

func TestBasicAuth(t *testing.T) {
ts := httptest.NewServer(http.NotFoundHandler())
defer ts.Close()
Expand Down Expand Up @@ -349,8 +396,7 @@ func TestOAuthClientCredentialsGrant(t *testing.T) {
}
})

err = tt.plugin.Connect()
require.NoError(t, err)
require.NoError(t, tt.plugin.Connect())

err = tt.plugin.Write([]telegraf.Metric{getMetric()})
require.NoError(t, err)
Expand All @@ -375,8 +421,7 @@ func TestDefaultUserAgent(t *testing.T) {
Domain: u.String(),
}

err = client.Connect()
require.NoError(t, err)
require.NoError(t, client.Connect())

err = client.Write([]telegraf.Metric{getMetric()})
require.NoError(t, err)
Expand Down Expand Up @@ -404,7 +449,7 @@ func TestMetricSorting(t *testing.T) {
require.Len(t, s.Streams, 1)
require.Len(t, s.Streams[0].Logs, 2)
require.Len(t, s.Streams[0].Logs[0], 2)
require.Equal(t, map[string]string{"__name": "log", "key1": "value1"}, s.Streams[0].Labels)
require.Equal(t, map[string]string{"key1": "value1"}, s.Streams[0].Labels)
require.Equal(t, "456000000000", s.Streams[0].Logs[0][0])
require.Contains(t, s.Streams[0].Logs[0][1], "line=\"older log\"")
require.Contains(t, s.Streams[0].Logs[0][1], "field=\"3.14\"")
Expand All @@ -419,8 +464,7 @@ func TestMetricSorting(t *testing.T) {
Domain: u.String(),
}

err = client.Connect()
require.NoError(t, err)
require.NoError(t, client.Connect())

err = client.Write(getOutOfOrderMetrics())
require.NoError(t, err)
Expand Down
6 changes: 6 additions & 0 deletions plugins/outputs/loki/sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"

## Metric Name Label
## Label to use for the metric name to when sending metrics. If set to an
## empty string, this will not add the label. This is NOT suggested as there
## is no way to differentiate between multiple metrics.
# metric_name_label = "__name"

0 comments on commit 4c5f485

Please sign in to comment.