1
0
Fork 0
telegraf/plugins/processors/regex/converter.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

269 lines
5.9 KiB
Go

package regex
import (
"errors"
"fmt"
"regexp"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
)
type converterType int
const (
convertTags = iota
convertFields
convertTagRename
convertFieldRename
convertMetricRename
)
func (ct converterType) String() string {
switch ct {
case convertTags:
return "tags"
case convertFields:
return "fields"
case convertTagRename:
return "tag_rename"
case convertFieldRename:
return "field_rename"
case convertMetricRename:
return "metric_rename"
}
return fmt.Sprintf("unknown %d", int(ct))
}
type converter struct {
Key string `toml:"key"`
Pattern string `toml:"pattern"`
Replacement string `toml:"replacement"`
ResultKey string `toml:"result_key"`
Append bool `toml:"append"`
filter filter.Filter
re *regexp.Regexp
groups []string
apply func(m telegraf.Metric)
}
func (c *converter) setup(ct converterType, log telegraf.Logger) error {
// Compile the pattern
re, err := regexp.Compile(c.Pattern)
if err != nil {
return err
}
c.re = re
switch ct {
case convertTags, convertFields:
if c.Key == "" {
return errors.New("key required")
}
f, err := filter.Compile([]string{c.Key})
if err != nil {
return err
}
c.filter = f
// Check for named groups
if c.ResultKey == "" && c.Replacement == "" {
groups := c.re.SubexpNames()
allNamed := len(groups) > 1
for _, g := range groups[1:] {
if g == "" {
allNamed = false
break
}
}
if allNamed {
log.Debugf("%s: Using named-group mode...", ct)
c.groups = groups[1:]
} else {
msg := "Neither 'result_key' nor 'replacement' given with unnamed or mixed groups;"
msg += " using explicit, empty replacement!"
log.Warnf("%s: %s", ct, msg)
}
} else {
log.Debugf("%s: Using explicit mode...", ct)
}
case convertTagRename, convertFieldRename:
switch c.ResultKey {
case "":
c.ResultKey = "keep"
case "overwrite", "keep":
// Do nothing as those are valid choices
default:
return fmt.Errorf("invalid metrics result_key %q", c.ResultKey)
}
}
// Select the application function
switch ct {
case convertTags:
c.apply = c.applyTags
case convertFields:
c.apply = c.applyFields
case convertTagRename:
c.apply = c.applyTagRename
case convertFieldRename:
c.apply = c.applyFieldRename
case convertMetricRename:
c.apply = c.applyMetricRename
}
return nil
}
func (c *converter) applyTags(m telegraf.Metric) {
for _, tag := range m.TagList() {
if !c.filter.Match(tag.Key) || !c.re.MatchString(tag.Value) {
continue
}
// Handle named groups
if len(c.groups) > 0 {
matches := c.re.FindStringSubmatch(tag.Value)
for i, match := range matches[1:] {
if match == "" {
continue
}
name := c.groups[i]
if c.Append {
if v, ok := m.GetTag(name); ok {
match = v + match
}
}
m.AddTag(name, match)
}
continue
}
// Handle explicit replacements
newKey := tag.Key
if c.ResultKey != "" {
newKey = c.ResultKey
}
newValue := c.re.ReplaceAllString(tag.Value, c.Replacement)
if c.Append {
if v, ok := m.GetTag(newKey); ok {
newValue = v + newValue
}
}
m.AddTag(newKey, newValue)
}
}
func (c *converter) applyFields(m telegraf.Metric) {
for _, field := range m.FieldList() {
if !c.filter.Match(field.Key) {
continue
}
value, ok := field.Value.(string)
if !ok || !c.re.MatchString(value) {
continue
}
// Handle named groups
if len(c.groups) > 0 {
matches := c.re.FindStringSubmatch(value)
for i, match := range matches[1:] {
if match == "" {
continue
}
name := c.groups[i]
if c.Append {
if v, ok := m.GetTag(name); ok {
match = v + match
}
}
m.AddField(name, match)
}
continue
}
// Handle explicit replacements
newKey := field.Key
if c.ResultKey != "" {
newKey = c.ResultKey
}
newValue := c.re.ReplaceAllString(value, c.Replacement)
m.AddField(newKey, newValue)
}
}
func (c *converter) applyTagRename(m telegraf.Metric) {
replacements := make(map[string]string)
for _, tag := range m.TagList() {
name := tag.Key
if c.re.MatchString(name) {
newName := c.re.ReplaceAllString(name, c.Replacement)
if !m.HasTag(newName) {
// There is no colliding tag, we can just change the name.
tag.Key = newName
continue
}
if c.ResultKey == "overwrite" {
// We got a colliding tag, remember the replacement and do it later
replacements[name] = newName
}
}
}
// We needed to postpone the replacement as we cannot modify the tag-list
// while iterating it as this will result in invalid memory dereference panic.
for oldName, newName := range replacements {
value, ok := m.GetTag(oldName)
if !ok {
// Just in case the tag got removed in the meantime
continue
}
m.AddTag(newName, value)
m.RemoveTag(oldName)
}
}
func (c *converter) applyFieldRename(m telegraf.Metric) {
replacements := make(map[string]string)
for _, field := range m.FieldList() {
name := field.Key
if c.re.MatchString(name) {
newName := c.re.ReplaceAllString(name, c.Replacement)
if !m.HasField(newName) {
// There is no colliding field, we can just change the name.
field.Key = newName
continue
}
if c.ResultKey == "overwrite" {
// We got a colliding field, remember the replacement and do it later
replacements[name] = newName
}
}
}
// We needed to postpone the replacement as we cannot modify the field-list
// while iterating it as this will result in invalid memory dereference panic.
for oldName, newName := range replacements {
value, ok := m.GetField(oldName)
if !ok {
// Just in case the field got removed in the meantime
continue
}
m.AddField(newName, value)
m.RemoveField(oldName)
}
}
func (c *converter) applyMetricRename(m telegraf.Metric) {
value := m.Name()
if c.re.MatchString(value) {
newValue := c.re.ReplaceAllString(value, c.Replacement)
m.SetName(newValue)
}
}