Skip to content

Commit 2df3c0e

Browse files
Bo Zhaootherpirate
authored andcommitted
Add replace function to strings processor (influxdata#4686)
1 parent e7f06c3 commit 2df3c0e

File tree

3 files changed

+122
-19
lines changed

3 files changed

+122
-19
lines changed

plugins/processors/strings/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ Implemented functions are:
1010
- trim_right
1111
- trim_prefix
1212
- trim_suffix
13+
- replace
1314

1415
Please note that in this implementation these are processed in the order that they appear above.
1516

1617
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.
1718

19+
If you'd like to apply the change to every `tag`, `field`, or `measurement`, use the value "*" for each respective field. Note that the `dest` field will be ignored if "*" is used
20+
1821
### Configuration:
1922

2023
```toml
@@ -45,6 +48,11 @@ Specify the `measurement`, `tag` or `field` that you want processed in each sect
4548
# [[processors.strings.trim_suffix]]
4649
# field = "read_count"
4750
# suffix = "_count"
51+
52+
# [[processors.strings.replace]]
53+
# measurement = "*"
54+
# old = ":"
55+
# new = "_"
4856
```
4957

5058
#### Trim, TrimLeft, TrimRight
@@ -56,6 +64,16 @@ The `trim`, `trim_left`, and `trim_right` functions take an optional parameter:
5664
The `trim_prefix` and `trim_suffix` functions remote the given `prefix` or `suffix`
5765
respectively from the string.
5866

67+
#### Replace
68+
69+
The `replace` function does a substring replacement across the entire
70+
string to allow for different conventions between various input and output
71+
plugins. Some example usages are eliminating disallowed characters in
72+
field names or replacing separators between different separators.
73+
Can also be used to eliminate unneeded chars that were in metrics.
74+
If the entire name would be deleted, it will refuse to perform
75+
the operation and keep the old name.
76+
5977
### Example
6078
**Config**
6179
```toml

plugins/processors/strings/strings.go

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Strings struct {
1616
TrimRight []converter `toml:"trim_right"`
1717
TrimPrefix []converter `toml:"trim_prefix"`
1818
TrimSuffix []converter `toml:"trim_suffix"`
19+
Replace []converter `toml:"replace"`
1920

2021
converters []converter
2122
init bool
@@ -31,6 +32,8 @@ type converter struct {
3132
Cutset string
3233
Suffix string
3334
Prefix string
35+
Old string
36+
New string
3437

3538
fn ConvertFunc
3639
}
@@ -68,6 +71,12 @@ const sampleConfig = `
6871
# [[processors.strings.trim_suffix]]
6972
# field = "read_count"
7073
# suffix = "_count"
74+
75+
## Replace substrings within field names
76+
# [[processors.strings.trim_suffix]]
77+
# measurement = "*"
78+
# old = ":"
79+
# new = "_"
7180
`
7281

7382
func (s *Strings) SampleConfig() string {
@@ -79,37 +88,53 @@ func (s *Strings) Description() string {
7988
}
8089

8190
func (c *converter) convertTag(metric telegraf.Metric) {
82-
tv, ok := metric.GetTag(c.Tag)
83-
if !ok {
84-
return
91+
var tags map[string]string
92+
if c.Tag == "*" {
93+
tags = metric.Tags()
94+
} else {
95+
tags = make(map[string]string)
96+
tv, ok := metric.GetTag(c.Tag)
97+
if !ok {
98+
return
99+
}
100+
tags[c.Tag] = tv
85101
}
86102

87-
dest := c.Tag
88-
if c.Dest != "" {
89-
dest = c.Dest
103+
for tag, value := range tags {
104+
dest := tag
105+
if c.Tag != "*" && c.Dest != "" {
106+
dest = c.Dest
107+
}
108+
metric.AddTag(dest, c.fn(value))
90109
}
91-
92-
metric.AddTag(dest, c.fn(tv))
93110
}
94111

95112
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
113+
var fields map[string]interface{}
114+
if c.Field == "*" {
115+
fields = metric.Fields()
116+
} else {
117+
fields = make(map[string]interface{})
118+
fv, ok := metric.GetField(c.Field)
119+
if !ok {
120+
return
121+
}
122+
fields[c.Field] = fv
104123
}
105124

106-
if fv, ok := fv.(string); ok {
107-
metric.AddField(dest, c.fn(fv))
125+
for tag, value := range fields {
126+
dest := tag
127+
if c.Tag != "*" && c.Dest != "" {
128+
dest = c.Dest
129+
}
130+
if fv, ok := value.(string); ok {
131+
metric.AddField(dest, c.fn(fv))
132+
}
108133
}
109134
}
110135

111136
func (c *converter) convertMeasurement(metric telegraf.Metric) {
112-
if metric.Name() != c.Measurement {
137+
if metric.Name() != c.Measurement && c.Measurement != "*" {
113138
return
114139
}
115140

@@ -176,6 +201,17 @@ func (s *Strings) initOnce() {
176201
c.fn = func(s string) string { return strings.TrimSuffix(s, c.Suffix) }
177202
s.converters = append(s.converters, c)
178203
}
204+
for _, c := range s.Replace {
205+
c.fn = func(s string) string {
206+
newString := strings.Replace(s, c.Old, c.New, -1)
207+
if newString == "" {
208+
return s
209+
} else {
210+
return newString
211+
}
212+
}
213+
s.converters = append(s.converters, c)
214+
}
179215

180216
s.init = true
181217
}

plugins/processors/strings/strings_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,3 +481,52 @@ func TestReadmeExample(t *testing.T) {
481481
assert.Equal(t, expectedFields, processed[0].Fields())
482482
assert.Equal(t, expectedTags, processed[0].Tags())
483483
}
484+
485+
func newMetric(name string) telegraf.Metric {
486+
tags := map[string]string{}
487+
fields := map[string]interface{}{}
488+
m, _ := metric.New(name, tags, fields, time.Now())
489+
return m
490+
}
491+
492+
func TestMeasurementReplace(t *testing.T) {
493+
plugin := &Strings{
494+
Replace: []converter{
495+
converter{
496+
Old: "_",
497+
New: "-",
498+
Measurement: "*",
499+
},
500+
},
501+
}
502+
metrics := []telegraf.Metric{
503+
newMetric("foo:some_value:bar"),
504+
newMetric("average:cpu:usage"),
505+
newMetric("average_cpu_usage"),
506+
}
507+
results := plugin.Apply(metrics...)
508+
assert.Equal(t, "foo:some-value:bar", results[0].Name(), "`_` was not changed to `-`")
509+
assert.Equal(t, "average:cpu:usage", results[1].Name(), "Input name should have been unchanged")
510+
assert.Equal(t, "average-cpu-usage", results[2].Name(), "All instances of `_` should have been changed to `-`")
511+
}
512+
513+
func TestMeasurementCharDeletion(t *testing.T) {
514+
plugin := &Strings{
515+
Replace: []converter{
516+
converter{
517+
Old: "foo",
518+
New: "",
519+
Measurement: "*",
520+
},
521+
},
522+
}
523+
metrics := []telegraf.Metric{
524+
newMetric("foo:bar:baz"),
525+
newMetric("foofoofoo"),
526+
newMetric("barbarbar"),
527+
}
528+
results := plugin.Apply(metrics...)
529+
assert.Equal(t, ":bar:baz", results[0].Name(), "Should have deleted the initial `foo`")
530+
assert.Equal(t, "foofoofoo", results[1].Name(), "Should have refused to delete the whole string")
531+
assert.Equal(t, "barbarbar", results[2].Name(), "Should not have changed the input")
532+
}

0 commit comments

Comments
 (0)