269 lines
5.9 KiB
Go
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)
|
|
}
|
|
}
|