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 # Defaults Processor Plugin
The _Defaults_ processor allows you to ensure certain fields will always exist The _Defaults_ processor allows you to ensure certain fields and tags will
with a specified default value on your metric(s). always exist with a specified default value on your metric(s).
There are three cases where this processor will insert a configured default 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/tag is nil on the incoming metric
1. The field is not nil, but its value is an empty string. 1. The field/tag 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 not nil, but its value is a string of one or more empty spaces.
Telegraf minimum version: Telegraf 1.15.0 Telegraf minimum version: Telegraf 1.15.0
@ -24,11 +24,11 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## Configuration ## Configuration
```toml @sample.conf ```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]] [[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. ## 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. ## is set on the metric with the specified default.
## ##
## A field is considered not set if it is nil on the incoming metric; ## 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" field_1 = "bar"
time_idle = 0 time_idle = 0
is_error = true 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 ## Example

View File

@ -16,6 +16,7 @@ var sampleConfig string
// on your Metrics with at least a default value. // on your Metrics with at least a default value.
type Defaults struct { type Defaults struct {
DefaultFieldsSets map[string]interface{} `toml:"fields"` DefaultFieldsSets map[string]interface{} `toml:"fields"`
DefaultTagsSets map[string]string `toml:"tags"`
} }
func (*Defaults) SampleConfig() string { func (*Defaults) SampleConfig() string {
@ -37,6 +38,14 @@ func (def *Defaults) Apply(inputMetrics ...telegraf.Metric) []telegraf.Metric {
metric.AddField(defField, defValue) 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 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) { func TestTracking(t *testing.T) {
inputRaw := []telegraf.Metric{ inputRaw := []telegraf.Metric{
metric.New("foo", map[string]string{}, map[string]interface{}{"value": 42, "topic": "telegraf"}, time.Unix(0, 0)), 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]] [[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. ## 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. ## is set on the metric with the specified default.
## ##
## A field is considered not set if it is nil on the incoming metric; ## A field is considered not set if it is nil on the incoming metric;
@ -13,3 +13,9 @@
field_1 = "bar" field_1 = "bar"
time_idle = 0 time_idle = 0
is_error = true 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"