Skip to content

Commit d6467e9

Browse files
ada-fossdanielnelson
authored andcommitted
Add strings processor (#4476)
1 parent 12ff8bb commit d6467e9

File tree

4 files changed

+766
-0
lines changed

4 files changed

+766
-0
lines changed

plugins/processors/all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
_ "github.com/influxdata/telegraf/plugins/processors/parser"
88
_ "github.com/influxdata/telegraf/plugins/processors/printer"
99
_ "github.com/influxdata/telegraf/plugins/processors/regex"
10+
_ "github.com/influxdata/telegraf/plugins/processors/strings"
1011
_ "github.com/influxdata/telegraf/plugins/processors/rename"
1112
_ "github.com/influxdata/telegraf/plugins/processors/topk"
1213
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Strings Processor Plugin
2+
3+
The `strings` plugin maps certain go string functions onto measurement, tag, and field values. Values can be modified in place or stored in another key.
4+
5+
Implemented functions are:
6+
- lowercase
7+
- uppercase
8+
- trim
9+
- trim_left
10+
- trim_right
11+
- trim_prefix
12+
- trim_suffix
13+
14+
Please note that in this implementation these are processed in the order that they appear above.
15+
16+
Specify the `measurement`, `tag` or `field` that you want processed in each section and optionally a `dest` if you want the result stored in a new tag or field. You can specify lots of transformations on data with a single strings processor.
17+
18+
### Configuration:
19+
20+
```toml
21+
[[processors.strings]]
22+
# [[processors.strings.uppercase]]
23+
# tag = "method"
24+
25+
# [[processors.strings.lowercase]]
26+
# field = "uri_stem"
27+
# dest = "uri_stem_normalised"
28+
29+
## Convert a tag value to lowercase
30+
# [[processors.strings.trim]]
31+
# field = "message"
32+
33+
# [[processors.strings.trim_left]]
34+
# field = "message"
35+
# cutset = "\t"
36+
37+
# [[processors.strings.trim_right]]
38+
# field = "message"
39+
# cutset = "\r\n"
40+
41+
# [[processors.strings.trim_prefix]]
42+
# field = "my_value"
43+
# prefix = "my_"
44+
45+
# [[processors.strings.trim_suffix]]
46+
# field = "read_count"
47+
# suffix = "_count"
48+
```
49+
50+
#### Trim, TrimLeft, TrimRight
51+
52+
The `trim`, `trim_left`, and `trim_right` functions take an optional parameter: `cutset`. This value is a string containing the characters to remove from the value.
53+
54+
#### TrimPrefix, TrimSuffix
55+
56+
The `trim_prefix` and `trim_suffix` functions remote the given `prefix` or `suffix`
57+
respectively from the string.
58+
59+
### Example
60+
**Config**
61+
```toml
62+
[[processors.strings]]
63+
[[processors.strings.lowercase]]
64+
field = "uri-stem"
65+
66+
[[processors.strings.trim_prefix]]
67+
field = "uri_stem"
68+
prefix = "/api/"
69+
70+
[[processors.strings.uppercase]]
71+
field = "cs-host"
72+
dest = "cs-host_normalised"
73+
```
74+
75+
**Input**
76+
```
77+
iis_log,method=get,uri_stem=/API/HealthCheck cs-host="MIXEDCASE_host",referrer="-",ident="-",http_version=1.1,agent="UserAgent",resp_bytes=270i 1519652321000000000
78+
```
79+
80+
**Output**
81+
```
82+
iis_log,method=get,uri_stem=healthcheck cs-host="MIXEDCASE_host",cs-host_normalised="MIXEDCASE_HOST",referrer="-",ident="-",http_version=1.1,agent="UserAgent",resp_bytes=270i 1519652321000000000
83+
```
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package strings
2+
3+
import (
4+
"strings"
5+
"unicode"
6+
7+
"github.com/influxdata/telegraf"
8+
"github.com/influxdata/telegraf/plugins/processors"
9+
)
10+
11+
type Strings struct {
12+
Lowercase []converter `toml:"lowercase"`
13+
Uppercase []converter `toml:"uppercase"`
14+
Trim []converter `toml:"trim"`
15+
TrimLeft []converter `toml:"trim_left"`
16+
TrimRight []converter `toml:"trim_right"`
17+
TrimPrefix []converter `toml:"trim_prefix"`
18+
TrimSuffix []converter `toml:"trim_suffix"`
19+
20+
converters []converter
21+
init bool
22+
}
23+
24+
type ConvertFunc func(s string) string
25+
26+
type converter struct {
27+
Field string
28+
Tag string
29+
Measurement string
30+
Dest string
31+
Cutset string
32+
Suffix string
33+
Prefix string
34+
35+
fn ConvertFunc
36+
}
37+
38+
const sampleConfig = `
39+
## Convert a tag value to uppercase
40+
# [[processors.strings.uppercase]]
41+
# tag = "method"
42+
43+
## Convert a field value to lowercase and store in a new field
44+
# [[processors.strings.lowercase]]
45+
# field = "uri_stem"
46+
# dest = "uri_stem_normalised"
47+
48+
## Trim leading and trailing whitespace using the default cutset
49+
# [[processors.strings.trim]]
50+
# field = "message"
51+
52+
## Trim leading characters in cutset
53+
# [[processors.strings.trim_left]]
54+
# field = "message"
55+
# cutset = "\t"
56+
57+
## Trim trailing characters in cutset
58+
# [[processors.strings.trim_right]]
59+
# field = "message"
60+
# cutset = "\r\n"
61+
62+
## Trim the given prefix from the field
63+
# [[processors.strings.trim_prefix]]
64+
# field = "my_value"
65+
# prefix = "my_"
66+
67+
## Trim the given suffix from the field
68+
# [[processors.strings.trim_suffix]]
69+
# field = "read_count"
70+
# suffix = "_count"
71+
`
72+
73+
func (s *Strings) SampleConfig() string {
74+
return sampleConfig
75+
}
76+
77+
func (s *Strings) Description() string {
78+
return "Perform string processing on tags, fields, and measurements"
79+
}
80+
81+
func (c *converter) convertTag(metric telegraf.Metric) {
82+
tv, ok := metric.GetTag(c.Tag)
83+
if !ok {
84+
return
85+
}
86+
87+
dest := c.Tag
88+
if c.Dest != "" {
89+
dest = c.Dest
90+
}
91+
92+
metric.AddTag(dest, c.fn(tv))
93+
}
94+
95+
func (c *converter) convertField(metric telegraf.Metric) {
96+
fv, ok := metric.GetField(c.Field)
97+
if !ok {
98+
return
99+
}
100+
101+
dest := c.Field
102+
if c.Dest != "" {
103+
dest = c.Dest
104+
}
105+
106+
if fv, ok := fv.(string); ok {
107+
metric.AddField(dest, c.fn(fv))
108+
}
109+
}
110+
111+
func (c *converter) convertMeasurement(metric telegraf.Metric) {
112+
if metric.Name() != c.Measurement {
113+
return
114+
}
115+
116+
metric.SetName(c.fn(metric.Name()))
117+
}
118+
119+
func (c *converter) convert(metric telegraf.Metric) {
120+
if c.Field != "" {
121+
c.convertField(metric)
122+
}
123+
124+
if c.Tag != "" {
125+
c.convertTag(metric)
126+
}
127+
128+
if c.Measurement != "" {
129+
c.convertMeasurement(metric)
130+
}
131+
}
132+
133+
func (s *Strings) initOnce() {
134+
if s.init {
135+
return
136+
}
137+
138+
s.converters = make([]converter, 0)
139+
for _, c := range s.Lowercase {
140+
c.fn = strings.ToLower
141+
s.converters = append(s.converters, c)
142+
}
143+
for _, c := range s.Uppercase {
144+
c.fn = strings.ToUpper
145+
s.converters = append(s.converters, c)
146+
}
147+
for _, c := range s.Trim {
148+
if c.Cutset != "" {
149+
c.fn = func(s string) string { return strings.Trim(s, c.Cutset) }
150+
} else {
151+
c.fn = func(s string) string { return strings.TrimFunc(s, unicode.IsSpace) }
152+
}
153+
s.converters = append(s.converters, c)
154+
}
155+
for _, c := range s.TrimLeft {
156+
if c.Cutset != "" {
157+
c.fn = func(s string) string { return strings.TrimLeft(s, c.Cutset) }
158+
} else {
159+
c.fn = func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) }
160+
}
161+
s.converters = append(s.converters, c)
162+
}
163+
for _, c := range s.TrimRight {
164+
if c.Cutset != "" {
165+
c.fn = func(s string) string { return strings.TrimRight(s, c.Cutset) }
166+
} else {
167+
c.fn = func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) }
168+
}
169+
s.converters = append(s.converters, c)
170+
}
171+
for _, c := range s.TrimPrefix {
172+
c.fn = func(s string) string { return strings.TrimPrefix(s, c.Prefix) }
173+
s.converters = append(s.converters, c)
174+
}
175+
for _, c := range s.TrimSuffix {
176+
c.fn = func(s string) string { return strings.TrimSuffix(s, c.Suffix) }
177+
s.converters = append(s.converters, c)
178+
}
179+
180+
s.init = true
181+
}
182+
183+
func (s *Strings) Apply(in ...telegraf.Metric) []telegraf.Metric {
184+
s.initOnce()
185+
186+
for _, metric := range in {
187+
for _, converter := range s.converters {
188+
converter.convert(metric)
189+
}
190+
}
191+
192+
return in
193+
}
194+
195+
func init() {
196+
processors.Add("strings", func() telegraf.Processor {
197+
return &Strings{}
198+
})
199+
}

0 commit comments

Comments
 (0)