1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,134 @@
# Template Processor Plugin
The `template` processor applies a Go template to metrics to generate a new
tag. The primary use case of this plugin is to create a tag that can be used
for dynamic routing to multiple output plugins or using an output specific
routing option.
The template has access to each metric's measurement name, tags, fields, and
timestamp using the [interface in `/template_metric.go`](template_metric.go).
[Sprig](http://masterminds.github.io/sprig/) helper functions are available.
Read the full [Go Template Documentation][].
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Uses a Go template to create a new tag
[[processors.template]]
## Go template used to create the tag name of the output. In order to
## ease TOML escaping requirements, you should use single quotes around
## the template string.
tag = "topic"
## Go template used to create the tag value of the output. In order to
## ease TOML escaping requirements, you should use single quotes around
## the template string.
template = '{{ .Tag "hostname" }}.{{ .Tag "level" }}'
```
## Examples
### Combine multiple tags to create a single tag
```toml
[[processors.template]]
tag = "topic"
template = '{{ .Tag "hostname" }}.{{ .Tag "level" }}'
```
```diff
- cpu,level=debug,hostname=localhost time_idle=42
+ cpu,level=debug,hostname=localhost,topic=localhost.debug time_idle=42
```
### Use a field value as tag name
```toml
[[processors.template]]
tag = '{{ .Field "type" }}'
template = '{{ .Name }}'
```
```diff
- cpu,level=debug,hostname=localhost time_idle=42,type=sensor
+ cpu,level=debug,hostname=localhost,sensor=cpu time_idle=42,type=sensor
```
### Add measurement name as a tag
```toml
[[processors.template]]
tag = "measurement"
template = '{{ .Name }}'
```
```diff
- cpu,hostname=localhost time_idle=42
+ cpu,hostname=localhost,measurement=cpu time_idle=42
```
### Add the year as a tag, similar to the date processor
```toml
[[processors.template]]
tag = "year"
template = '{{.Time.UTC.Year}}'
```
### Add all fields as a tag
Sometimes it is useful to pass all fields with their values into a single
message for sending it to a monitoring system (e.g. Syslog, GroundWork), then
you can use `.Fields` or `.Tags`:
```toml
[[processors.template]]
tag = "message"
template = 'Message about {{.Name}} fields: {{.Fields}}'
```
```diff
- cpu,hostname=localhost time_idle=42
+ cpu,hostname=localhost,message=Message\ about\ cpu\ fields:\ map[time_idle:42] time_idle=42
```
More advanced example, which might make more sense:
```toml
[[processors.template]]
tag = "message"
template = '''Message about {{.Name}} fields:
{{ range $field, $value := .Fields -}}
{{$field}}:{{$value}}
{{ end }}'''
```
```diff
- cpu,hostname=localhost time_idle=42
+ cpu,hostname=localhost,message=Message\ about\ cpu\ fields:\ntime_idle:42\n time_idle=42
```
### Just add the current metric as a tag
```toml
[[processors.template]]
tag = "metric"
template = '{{.}}'
```
```diff
- cpu,hostname=localhost time_idle=42
+ cpu,hostname=localhost,metric=cpu\ map[hostname:localhost]\ map[time_idle:42]\ 1257894000000000000 time_idle=42
```
[Go Template Documentation]: https://golang.org/pkg/text/template/

View file

@ -0,0 +1,11 @@
# Uses a Go template to create a new tag
[[processors.template]]
## Go template used to create the tag name of the output. In order to
## ease TOML escaping requirements, you should use single quotes around
## the template string.
tag = "topic"
## Go template used to create the tag value of the output. In order to
## ease TOML escaping requirements, you should use single quotes around
## the template string.
template = '{{ .Tag "hostname" }}.{{ .Tag "level" }}'

View file

@ -0,0 +1,85 @@
//go:generate ../../../tools/readme_config_includer/generator
package template
import (
_ "embed"
"fmt"
"strings"
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/processors"
)
//go:embed sample.conf
var sampleConfig string
type TemplateProcessor struct {
Tag string `toml:"tag"`
Template string `toml:"template"`
Log telegraf.Logger `toml:"-"`
tmplTag *template.Template
tmplValue *template.Template
}
func (*TemplateProcessor) SampleConfig() string {
return sampleConfig
}
func (r *TemplateProcessor) Apply(in ...telegraf.Metric) []telegraf.Metric {
// for each metric in "in" array
for _, raw := range in {
m := raw
if wm, ok := raw.(telegraf.UnwrappableMetric); ok {
m = wm.Unwrap()
}
tm, ok := m.(telegraf.TemplateMetric)
if !ok {
r.Log.Errorf("metric of type %T is not a template metric", raw)
continue
}
newM := TemplateMetric{tm}
var b strings.Builder
if err := r.tmplTag.Execute(&b, &newM); err != nil {
r.Log.Errorf("failed to execute tag name template: %v", err)
continue
}
tag := b.String()
b.Reset()
if err := r.tmplValue.Execute(&b, &newM); err != nil {
r.Log.Errorf("failed to execute value template: %v", err)
continue
}
value := b.String()
raw.AddTag(tag, value)
}
return in
}
func (r *TemplateProcessor) Init() error {
var err error
r.tmplTag, err = template.New("tag template").Funcs(sprig.TxtFuncMap()).Parse(r.Tag)
if err != nil {
return fmt.Errorf("creating tag name template failed: %w", err)
}
r.tmplValue, err = template.New("value template").Funcs(sprig.TxtFuncMap()).Parse(r.Template)
if err != nil {
return fmt.Errorf("creating value template failed: %w", err)
}
return nil
}
func init() {
processors.Add("template", func() telegraf.Processor {
return &TemplateProcessor{}
})
}

View file

@ -0,0 +1,74 @@
package template
import (
"sync"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
)
var (
onceTagList sync.Once
onceFieldList sync.Once
)
type TemplateMetric struct {
metric telegraf.TemplateMetric
}
func (m *TemplateMetric) Name() string {
return m.metric.Name()
}
func (m *TemplateMetric) Tag(key string) string {
return m.metric.Tag(key)
}
func (m *TemplateMetric) Field(key string) interface{} {
return m.metric.Field(key)
}
func (m *TemplateMetric) Time() time.Time {
return m.metric.Time()
}
func (m *TemplateMetric) Tags() map[string]string {
return m.metric.Tags()
}
func (m *TemplateMetric) Fields() map[string]interface{} {
return m.metric.Fields()
}
func (m *TemplateMetric) String() string {
return m.metric.String()
}
func (m *TemplateMetric) TagList() map[string]string {
onceTagList.Do(func() {
config.PrintOptionValueDeprecationNotice(
"processors.template", "template", "{{.TagList}}",
telegraf.DeprecationInfo{
Since: "1.28.0",
RemovalIn: "1.34.0",
Notice: "use '{{.Tags}}' instead",
},
)
})
return m.metric.Tags()
}
func (m *TemplateMetric) FieldList() map[string]interface{} {
onceFieldList.Do(func() {
config.PrintOptionValueDeprecationNotice(
"processors.template", "template", "{{.FieldList}}",
telegraf.DeprecationInfo{
Since: "1.28.0",
RemovalIn: "1.34.0",
Notice: "use '{{.Fields}}' instead",
},
)
})
return m.metric.Fields()
}

View file

@ -0,0 +1,344 @@
package template
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
func TestName(t *testing.T) {
plugin := TemplateProcessor{
Tag: "measurement",
Template: "{{ .Name }}",
}
err := plugin.Init()
require.NoError(t, err)
input := []telegraf.Metric{
testutil.MustMetric(
"cpu",
map[string]string{},
map[string]interface{}{
"time_idle": 42,
},
time.Unix(0, 0),
),
}
actual := plugin.Apply(input...)
expected := []telegraf.Metric{
testutil.MustMetric(
"cpu",
map[string]string{
"measurement": "cpu",
},
map[string]interface{}{
"time_idle": 42,
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, actual)
}
func TestNameTemplate(t *testing.T) {
plugin := TemplateProcessor{
Tag: `{{ .Tag "foo" }}`,
Template: `{{ .Name }}`,
}
err := plugin.Init()
require.NoError(t, err)
input := []telegraf.Metric{
testutil.MustMetric(
"cpu",
map[string]string{"foo": "measurement"},
map[string]interface{}{
"time_idle": 42,
},
time.Unix(0, 0),
),
}
actual := plugin.Apply(input...)
expected := []telegraf.Metric{
testutil.MustMetric(
"cpu",
map[string]string{
"foo": "measurement",
"measurement": "cpu",
},
map[string]interface{}{
"time_idle": 42,
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, actual)
}
func TestTagTemplateConcatenate(t *testing.T) {
now := time.Now()
// Create Template processor
tmp := TemplateProcessor{Tag: "topic", Template: `{{.Tag "hostname"}}.{{ .Tag "level" }}`}
// manually init
err := tmp.Init()
if err != nil {
panic(err)
}
// create metric for testing
input := []telegraf.Metric{testutil.MustMetric("Tags", map[string]string{"hostname": "localhost", "level": "debug"}, nil, now)}
// act
actual := tmp.Apply(input[0])
// assert
expected := []telegraf.Metric{
testutil.MustMetric("Tags", map[string]string{"hostname": "localhost", "level": "debug", "topic": "localhost.debug"}, nil, now),
}
testutil.RequireMetricsEqual(t, expected, actual)
}
func TestMetricMissingTagsIsNotLost(t *testing.T) {
now := time.Now()
// Create Template processor
tmp := TemplateProcessor{Tag: "topic", Template: `{{.Tag "hostname"}}.{{ .Tag "level" }}`}
// manually init
err := tmp.Init()
if err != nil {
panic(err)
}
// create metrics for testing
m1 := testutil.MustMetric("Works", map[string]string{"hostname": "localhost", "level": "debug"}, nil, now)
m2 := testutil.MustMetric("Fails", map[string]string{"hostname": "localhost"}, nil, now)
// act
actual := tmp.Apply(m1, m2)
// assert
// make sure no metrics are lost when a template process fails
require.Len(t, actual, 2, "Number of metrics input should equal number of metrics output")
}
func TestTagAndFieldConcatenate(t *testing.T) {
now := time.Now()
// Create Template processor
tmp := TemplateProcessor{Tag: "LocalTemp", Template: `{{.Tag "location"}} is {{ .Field "temperature" }}`}
// manually init
err := tmp.Init()
if err != nil {
panic(err)
}
// create metric for testing
m1 := testutil.MustMetric("weather", map[string]string{"location": "us-midwest"}, map[string]interface{}{"temperature": "too warm"}, now)
// act
actual := tmp.Apply(m1)
// assert
expected := []telegraf.Metric{
testutil.MustMetric(
"weather",
map[string]string{"location": "us-midwest", "LocalTemp": "us-midwest is too warm"},
map[string]interface{}{"temperature": "too warm"},
now,
),
}
testutil.RequireMetricsEqual(t, expected, actual)
}
func TestFieldList(t *testing.T) {
// Prepare
plugin := TemplateProcessor{Tag: "fields", Template: "{{.FieldList}}"}
require.NoError(t, plugin.Init())
// Run
m := testutil.TestMetric(1.23)
actual := plugin.Apply(m)
// Verify
expected := m.Copy()
expected.AddTag("fields", "map[value:1.23]")
testutil.RequireMetricsEqual(t, []telegraf.Metric{expected}, actual)
}
func TestTagList(t *testing.T) {
// Prepare
plugin := TemplateProcessor{Tag: "tags", Template: "{{.TagList}}"}
require.NoError(t, plugin.Init())
// Run
m := testutil.TestMetric(1.23)
actual := plugin.Apply(m)
// Verify
expected := m.Copy()
expected.AddTag("tags", "map[tag1:value1]")
testutil.RequireMetricsEqual(t, []telegraf.Metric{expected}, actual)
}
func TestFields(t *testing.T) {
// Prepare
plugin := TemplateProcessor{
Tag: "fields",
Template: "{{.Fields}}",
Log: testutil.Logger{},
}
require.NoError(t, plugin.Init())
// Run
m := testutil.TestMetric(1.23)
actual := plugin.Apply(m)
// Verify
expected := m.Copy()
expected.AddTag("fields", "map[value:1.23]")
testutil.RequireMetricsEqual(t, []telegraf.Metric{expected}, actual)
}
func TestTags(t *testing.T) {
// Prepare
plugin := TemplateProcessor{
Tag: "tags",
Template: "{{.Tags}}",
Log: testutil.Logger{},
}
require.NoError(t, plugin.Init())
// Run
m := testutil.TestMetric(1.23)
actual := plugin.Apply(m)
// Verify
expected := m.Copy()
expected.AddTag("tags", "map[tag1:value1]")
testutil.RequireMetricsEqual(t, []telegraf.Metric{expected}, actual)
}
func TestString(t *testing.T) {
// Prepare
plugin := TemplateProcessor{
Tag: "tags",
Template: "{{.}}",
Log: testutil.Logger{},
}
require.NoError(t, plugin.Init())
// Run
m := testutil.TestMetric(1.23)
actual := plugin.Apply(m)
// Verify
expected := m.Copy()
expected.AddTag("tags", "test1 map[tag1:value1] map[value:1.23] 1257894000000000000")
testutil.RequireMetricsEqual(t, []telegraf.Metric{expected}, actual)
}
func TestDot(t *testing.T) {
// Prepare
plugin := TemplateProcessor{Tag: "metric", Template: "{{.}}"}
require.NoError(t, plugin.Init())
// Run
m := testutil.TestMetric(1.23)
actual := plugin.Apply(m)
// Verify
expected := m.Copy()
expected.AddTag("metric", "test1 map[tag1:value1] map[value:1.23] 1257894000000000000")
testutil.RequireMetricsEqual(t, []telegraf.Metric{expected}, actual)
}
func TestTracking(t *testing.T) {
// Create a tracking metric and tap the delivery information
var mu sync.Mutex
delivered := make([]telegraf.DeliveryInfo, 0, 1)
notify := func(di telegraf.DeliveryInfo) {
mu.Lock()
defer mu.Unlock()
delivered = append(delivered, di)
}
m := testutil.TestMetric(1.23)
input, _ := metric.WithTracking(m, notify)
// Create an expectation
e := m.Copy()
e.AddTag("metric", "test1 map[tag1:value1] map[value:1.23] 1257894000000000000")
expected := []telegraf.Metric{e}
// Configure the plugin
plugin := TemplateProcessor{Tag: "metric", Template: "{{.}}"}
require.NoError(t, plugin.Init())
// Process expected metrics and compare with resulting metrics
actual := plugin.Apply(input)
testutil.RequireMetricsEqual(t, expected, actual)
// Simulate output acknowledging delivery
for _, m := range actual {
m.Accept()
}
// Check delivery
// Check delivery
require.Eventuallyf(t, func() bool {
mu.Lock()
defer mu.Unlock()
return len(delivered) == 1
}, time.Second, 100*time.Millisecond, "%d delivered but 1 expected", len(delivered))
}
func TestSprig(t *testing.T) {
plugin := TemplateProcessor{
Tag: `{{ .Tag "foo" | lower }}`,
Template: `{{ .Name | upper }}`,
}
err := plugin.Init()
require.NoError(t, err)
input := []telegraf.Metric{
testutil.MustMetric(
"cpu",
map[string]string{"foo": "MEASUREMENT"},
map[string]interface{}{
"time_idle": 42,
},
time.Unix(0, 0),
),
}
actual := plugin.Apply(input...)
expected := []telegraf.Metric{
testutil.MustMetric(
"cpu",
map[string]string{
"foo": "MEASUREMENT",
"measurement": "CPU",
},
map[string]interface{}{
"time_idle": 42,
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, actual)
}