feat(processors.defaults): Add support for specifying default tags (#16717)

Co-authored-by: Pieter Slabbert <pieter.slabbert@ilovezoona.com>
This commit is contained in:
Pieter Slabbert 2025-04-04 20:54:53 +02:00 committed by GitHub
parent aa68d6175a
commit 9ed15a6194
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 151 additions and 12 deletions

View File

@ -1,14 +1,14 @@
# Defaults Processor Plugin
The _Defaults_ processor allows you to ensure certain fields will always exist
with a specified default value on your metric(s).
The _Defaults_ processor allows you to ensure certain fields and tags will
always exist with a specified default value on your metric(s).
There are three cases where this processor will insert a configured default
field.
field or tag.
1. The field is nil on the incoming metric
1. The field is not nil, but its value is an empty string.
1. The field is not nil, but its value is a string of one or more empty spaces.
1. The field/tag is nil on the incoming metric
1. The field/tag is not nil, but its value is an empty string.
1. The field/tag is not nil, but its value is a string of one or more empty spaces.
Telegraf minimum version: Telegraf 1.15.0
@ -24,11 +24,11 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## Configuration
```toml @sample.conf
## Set default fields on your metric(s) when they are nil or empty
## Set default fields and tags on your metric(s) when they are nil or empty
[[processors.defaults]]
## Ensures a set of fields always exists on your metric(s) with their
## Ensures a set of fields or tags always exists on your metric(s) with their
## respective default value.
## For any given field pair (key = default), if it's not set, a field
## For any given field/tag pair (key = default), if it's not set, a field/tag
## is set on the metric with the specified default.
##
## A field is considered not set if it is nil on the incoming metric;
@ -39,6 +39,12 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
field_1 = "bar"
time_idle = 0
is_error = true
## A tag is considered not set if it is nil on the incoming metric;
## or it is not nil but it is empty string or a string of one or
## more spaces.
## <target-tag> = <value>
[processors.defaults.tags]
tag_1 = "foo"
```
## Example

View File

@ -16,6 +16,7 @@ var sampleConfig string
// on your Metrics with at least a default value.
type Defaults struct {
DefaultFieldsSets map[string]interface{} `toml:"fields"`
DefaultTagsSets map[string]string `toml:"tags"`
}
func (*Defaults) SampleConfig() string {
@ -37,6 +38,14 @@ func (def *Defaults) Apply(inputMetrics ...telegraf.Metric) []telegraf.Metric {
metric.AddField(defField, defValue)
}
}
for defTag, defValue := range def.DefaultTagsSets {
if maybeCurrent, isSet := metric.GetTag(defTag); !isSet {
metric.AddTag(defTag, defValue)
} else if trimmed := strings.TrimSpace(maybeCurrent); trimmed == "" {
metric.RemoveTag(defTag)
metric.AddTag(defTag, defValue)
}
}
}
return inputMetrics
}

View File

@ -133,6 +133,124 @@ func TestDefaults(t *testing.T) {
}
}
func TestTagDefaults(t *testing.T) {
scenarios := []struct {
name string
defaults *Defaults
input telegraf.Metric
expected []telegraf.Metric
}{
{
name: "Test that no values are changed since they are not nil or empty",
defaults: &Defaults{
DefaultTagsSets: map[string]string{
"wind_feel": "very chill",
},
},
input: testutil.MustMetric(
"CPU metrics",
map[string]string{
"wind_feel": "a dragon's breath",
},
map[string]interface{}{
"usage": 45,
"is_dead": false,
},
time.Unix(0, 0),
),
expected: []telegraf.Metric{
testutil.MustMetric(
"CPU metrics",
map[string]string{
"wind_feel": "a dragon's breath",
},
map[string]interface{}{
"usage": 45,
"is_dead": false,
},
time.Unix(0, 0),
),
},
},
{
name: "Tests that the missing tags are set on the metric",
defaults: &Defaults{
DefaultTagsSets: map[string]string{
"wind_feel": "Unknown",
},
},
input: testutil.MustMetric(
"CPU metrics",
map[string]string{},
map[string]interface{}{
"usage": 45,
"temperature": 64,
},
time.Unix(0, 0),
),
expected: []telegraf.Metric{
testutil.MustMetric(
"CPU metrics",
map[string]string{
"wind_feel": "Unknown",
},
map[string]interface{}{
"usage": 45,
"temperature": 64,
},
time.Unix(0, 0),
),
},
},
{
name: "Tests that set but empty tags are replaced by specified defaults",
defaults: &Defaults{
DefaultTagsSets: map[string]string{
"wind_feel": "Unknown",
"fan_loudness": "Inaudible",
"boost_enabled": "false",
},
},
input: testutil.MustMetric(
"CPU metrics",
map[string]string{
"wind_feel": " ",
"fan_loudness": " ",
"boost_enabled": "",
},
map[string]interface{}{
"max_clock_gz": 0,
},
time.Unix(0, 0),
),
expected: []telegraf.Metric{
testutil.MustMetric(
"CPU metrics",
map[string]string{
"wind_feel": "Unknown",
"fan_loudness": "Inaudible",
"boost_enabled": "false",
},
map[string]interface{}{
"max_clock_gz": 0,
},
time.Unix(0, 0),
),
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
defaults := scenario.defaults
resultMetrics := defaults.Apply(scenario.input)
require.Len(t, resultMetrics, 1)
testutil.RequireMetricsEqual(t, scenario.expected, resultMetrics)
})
}
}
func TestTracking(t *testing.T) {
inputRaw := []telegraf.Metric{
metric.New("foo", map[string]string{}, map[string]interface{}{"value": 42, "topic": "telegraf"}, time.Unix(0, 0)),

View File

@ -1,8 +1,8 @@
## Set default fields on your metric(s) when they are nil or empty
## Set default fields and tags on your metric(s) when they are nil or empty
[[processors.defaults]]
## Ensures a set of fields always exists on your metric(s) with their
## Ensures a set of fields or tags always exists on your metric(s) with their
## respective default value.
## For any given field pair (key = default), if it's not set, a field
## For any given field/tag pair (key = default), if it's not set, a field/tag
## is set on the metric with the specified default.
##
## A field is considered not set if it is nil on the incoming metric;
@ -13,3 +13,9 @@
field_1 = "bar"
time_idle = 0
is_error = true
## A tag is considered not set if it is nil on the incoming metric;
## or it is not nil but it is empty string or a string of one or
## more spaces.
## <target-tag> = <value>
[processors.defaults.tags]
tag_1 = "foo"