diff --git a/plugins/inputs/statsd/README.md b/plugins/inputs/statsd/README.md index e82da5a03c878..620b95b7e6007 100644 --- a/plugins/inputs/statsd/README.md +++ b/plugins/inputs/statsd/README.md @@ -75,6 +75,14 @@ ## Max duration (TTL) for each metric to stay cached/reported without being updated. # max_ttl = "10h" + + ## Sanitize name method + ## By default, telegraf will pass names directly as they are received. + ## However, upstream statsd now does sanitization of names which can be + ## enabled by using the "upstream" method option. This option will a) replace + ## white space with '_', replace '/' with '-', and remove charachters not + ## matching 'a-zA-Z_\-0-9\.;='. + #sanitize_name_method = "" ``` ## Description diff --git a/plugins/inputs/statsd/statsd.go b/plugins/inputs/statsd/statsd.go index 861d2561a85a8..257605982ed38 100644 --- a/plugins/inputs/statsd/statsd.go +++ b/plugins/inputs/statsd/statsd.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "net" + "regexp" "sort" "strconv" "strings" @@ -98,6 +99,8 @@ type Statsd struct { ReadBufferSize int `toml:"read_buffer_size"` + SanitizeNamesMethod string `toml:"sanitize_name_method"` + sync.Mutex // Lock for preventing a data race during resource cleanup cleanup sync.Mutex @@ -284,6 +287,14 @@ const sampleConfig = ` ## Max duration (TTL) for each metric to stay cached/reported without being updated. #max_ttl = "1000h" + + ## Sanitize name method + ## By default, telegraf will pass names directly as they are received. + ## However, upstream statsd now does sanitization of names which can be + ## enabled by using the "upstream" method option. This option will a) replace + ## white space with '_', replace '/' with '-', and remove charachters not + ## matching 'a-zA-Z_\-0-9\.;='. + #sanitize_name_method = "" ` func (s *Statsd) SampleConfig() string { @@ -763,6 +774,17 @@ func (s *Statsd) parseName(bucket string) (name string, field string, tags map[s } name = bucketparts[0] + switch s.SanitizeNamesMethod { + case "": + case "upstream": + whitespace := regexp.MustCompile(`\s+`) + name = whitespace.ReplaceAllString(name, "_") + name = strings.ReplaceAll(name, "/", "-") + allowedChars := regexp.MustCompile(`[^a-zA-Z_\-0-9\.;=]`) + name = allowedChars.ReplaceAllString(name, "") + default: + s.Log.Errorf("Unknown sanitizae name method: %s", s.SanitizeNamesMethod) + } p := s.graphiteParser var err error @@ -1089,6 +1111,7 @@ func init() { DeleteGauges: true, DeleteSets: true, DeleteTimings: true, + SanitizeNamesMethod: "", } }) } diff --git a/plugins/inputs/statsd/statsd_test.go b/plugins/inputs/statsd/statsd_test.go index 5121f06b6b8f7..22d6ee4e30901 100644 --- a/plugins/inputs/statsd/statsd_test.go +++ b/plugins/inputs/statsd/statsd_test.go @@ -1701,3 +1701,67 @@ func TestParse_KeyValue(t *testing.T) { } } } + +func TestParseSanitize(t *testing.T) { + s := NewTestStatsd() + s.SanitizeNamesMethod = "upstream" + + tests := []struct { + inName string + outName string + }{ + { + "regex.ARP flood stats", + "regex_ARP_flood_stats", + }, + { + "regex./dev/null", + "regex_-dev-null", + }, + { + "regex.wow!!!", + "regex_wow", + }, + { + "regex.all*things", + "regex_allthings", + }, + } + + for _, test := range tests { + name, _, _ := s.parseName(test.inName) + require.Equalf(t, name, test.outName, "Expected: %s, got %s", test.outName, name) + } +} + +func TestParseNoSanitize(t *testing.T) { + s := NewTestStatsd() + s.SanitizeNamesMethod = "" + + tests := []struct { + inName string + outName string + }{ + { + "regex.ARP flood stats", + "regex_ARP", + }, + { + "regex./dev/null", + "regex_/dev/null", + }, + { + "regex.wow!!!", + "regex_wow!!!", + }, + { + "regex.all*things", + "regex_all*things", + }, + } + + for _, test := range tests { + name, _, _ := s.parseName(test.inName) + require.Equalf(t, name, test.outName, "Expected: %s, got %s", test.outName, name) + } +}