Skip to content

Commit 7a3644b

Browse files
Readme, code changes and test coverage expansion based on review comments
1 parent 5189734 commit 7a3644b

10 files changed

+297
-25
lines changed

.chloggen/ottl-replace-pattern.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ change_type: enhancement
77
component: pkg/ottl
88

99
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10-
note: Add optional parameters to converters
10+
note: Add optional Converter parameters to replacement Editors
1111

1212
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
1313
issues: [27235]
@@ -16,7 +16,7 @@ issues: [27235]
1616
# These lines will be padded with 2 spaces and then inserted directly into the document.
1717
# Use pipe (|) for multiline entries.
1818
subtext: |
19-
Hashing functions are passed as optional arguments to the following converters:
19+
Functions to modify matched text during replacement can now be passed as optional arguments to the following Editors:
2020
- `replace_pattern`
2121
- `replace_all_patterns`
2222
- `replace_match`

pkg/ottl/ottlfuncs/README.md

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ The key will be deleted from the map.
5757

5858
Examples:
5959

60+
6061
- `delete_key(attributes, "http.request.header.authorization")`
6162

6263
- `delete_key(resource.attributes, "http.request.header.authorization")`
@@ -73,6 +74,7 @@ All keys that match the pattern will be deleted from the map.
7374

7475
Examples:
7576

77+
7678
- `delete_key(attributes, "http.request.header.authorization")`
7779

7880
- `delete_key(resource.attributes, "http.request.header.authorization")`
@@ -91,6 +93,7 @@ Examples:
9193

9294
- `keep_keys(attributes, ["http.method"])`
9395

96+
9497
- `keep_keys(resource.attributes, ["http.method", "http.route", "http.url"])`
9598

9699
### limit
@@ -113,6 +116,7 @@ Examples:
113116

114117
- `limit(attributes, 100, [])`
115118

119+
116120
- `limit(resource.attributes, 50, ["http.host", "http.method"])`
117121

118122
### merge_maps
@@ -135,18 +139,19 @@ Examples:
135139

136140
- `merge_maps(attributes, ParseJSON(body), "upsert")`
137141

142+
138143
- `merge_maps(attributes, ParseJSON(attributes["kubernetes"]), "update")`
139144

145+
140146
- `merge_maps(attributes, resource.attributes, "insert")`
141147

142148
### replace_all_matches
143149

144-
`replace_all_matches(target, pattern, replacement, hashFunction)`
150+
`replace_all_matches(target, pattern, replacement, function)`
145151

146152
The `replace_all_matches` function replaces any matching string value with the replacement string.
147153

148-
`target` is a path expression to a `pdata.Map` type field. `pattern` is a string following [filepath.Match syntax](https://pkg.go.dev/path/filepath#Match). `replacement` is either a path expression to a string telemetry field or a literal string. `hashFunction` is an optional argument
149-
that creates a hash of `replacement` and then replaces any matching string with the hash value.
154+
`target` is a path expression to a `pdata.Map` type field. `pattern` is a string following [filepath.Match syntax](https://pkg.go.dev/path/filepath#Match). `replacement` is either a path expression to a string telemetry field or a literal string. `function` is an optional argument that can take in any Converter that accepts a (`replacement`) string and returns a string. An example is a hash function that replaces any matching string with the hash value of `replacement`.
150155

151156
Each string value in `target` that matches `pattern` will get replaced with `replacement`. Non-string values are ignored.
152157

@@ -160,7 +165,7 @@ Examples:
160165

161166
### replace_all_patterns
162167

163-
`replace_all_patterns(target, mode, regex, replacement, hashFunction)`
168+
`replace_all_patterns(target, mode, regex, replacement, function)`
164169

165170
The `replace_all_patterns` function replaces any segments in a string value or key that match the regex pattern with the replacement string.
166171

@@ -172,7 +177,7 @@ If one or more sections of `target` match `regex` they will get replaced with `r
172177

173178
The `replacement` string can refer to matched groups using [regexp.Expand syntax](https://pkg.go.dev/regexp#Regexp.Expand).
174179

175-
The `hashFunction` is an optional argument that creates a hash of `replacement` and then replaces the regex pattern with the hash value.
180+
The `function` is an optional argument that can take in any Converter that accepts a (`replacement`) string and returns a string. An example is a hash function that replaces any matching regex pattern with the hash value of `replacement`.
176181

177182
There is currently a bug with OTTL that does not allow the pattern to end with `\\"`.
178183
If your pattern needs to end with backslashes, add something inconsequential to the end of the pattern such as `{1}`, `$`, or `.*`.
@@ -191,15 +196,15 @@ If using OTTL outside of collector configuration, `$` should not be escaped and
191196

192197
### replace_match
193198

194-
`replace_match(target, pattern, replacement, hashFunction)`
199+
`replace_match(target, pattern, replacement, function)`
195200

196201
The `replace_match` function allows replacing entire strings if they match a glob pattern.
197202

198203
`target` is a path expression to a telemetry field. `pattern` is a string following [filepath.Match syntax](https://pkg.go.dev/path/filepath#Match). `replacement` is either a path expression to a string telemetry field or a literal string.
199204

200205
If `target` matches `pattern` it will get replaced with `replacement`.
201206

202-
The `hashFunction` is an optional argument that creates a hash of `replacement` and then replaces entire strings if they match a glob pattern with the hash value.
207+
The `function` is an optional argument that can take in any Converter that accepts a (`replacement`) string and returns a string. An example is a hash function that replaces any matching glob pattern with the hash value of `replacement`.
203208

204209
There is currently a bug with OTTL that does not allow the pattern to end with `\\"`.
205210
[See Issue 23238 for details](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/23238).
@@ -211,7 +216,7 @@ Examples:
211216

212217
### replace_pattern
213218

214-
`replace_pattern(target, regex, replacement, hashFunction)`
219+
`replace_pattern(target, regex, replacement, function)`
215220

216221
The `replace_pattern` function allows replacing all string sections that match a regex pattern with a new value.
217222

@@ -221,7 +226,7 @@ If one or more sections of `target` match `regex` they will get replaced with `r
221226

222227
The `replacement` string can refer to matched groups using [regexp.Expand syntax](https://pkg.go.dev/regexp#Regexp.Expand).
223228

224-
The `hashFunction` is an optional argument that creates a hash of `replacement` and then replaces all string sections that match a regex pattern with the hash value.
229+
The `function` is an optional argument that can take in any Converter that accepts a (`replacement`) string and returns a string. An example is a hash function that replaces a matching regex pattern with the hash value of `replacement`.
225230

226231
There is currently a bug with OTTL that does not allow the pattern to end with `\\"`.
227232
If your pattern needs to end with backslashes, add something inconsequential to the end of the pattern such as `{1}`, `$`, or `.*`.
@@ -251,10 +256,13 @@ Examples:
251256

252257
- `set(attributes["http.path"], "/foo")`
253258

259+
254260
- `set(name, attributes["http.route"])`
255261

262+
256263
- `set(trace_state["svc"], "example")`
257264

265+
258266
- `set(attributes["source"], trace_state["source"])`
259267

260268
### truncate_all
@@ -271,6 +279,7 @@ Examples:
271279

272280
- `truncate_all(attributes, 100)`
273281

282+
274283
- `truncate_all(resource.attributes, 50)`
275284

276285
## Converters
@@ -327,8 +336,10 @@ Examples:
327336

328337
- `Concat([attributes["http.method"], attributes["http.path"]], ": ")`
329338

339+
330340
- `Concat([name, 1], " ")`
331341

342+
332343
- `Concat(["HTTP method is: ", attributes["http.method"]], "")`
333344

334345
### ConvertCase
@@ -402,6 +413,7 @@ Examples:
402413

403414
- `FNV(attributes["device.name"])`
404415

416+
405417
- `FNV("name")`
406418

407419
### Hours
@@ -441,6 +453,7 @@ Examples:
441453

442454
- `Int(attributes["http.status_code"])`
443455

456+
444457
- `Int("2.0")`
445458

446459
### IsMap
@@ -457,6 +470,7 @@ Examples:
457470

458471
- `IsMap(body)`
459472

473+
460474
- `IsMap(attributes["maybe a map"])`
461475

462476
### IsMatch
@@ -485,6 +499,7 @@ Examples:
485499

486500
- `IsMatch(attributes["http.path"], "foo")`
487501

502+
488503
- `IsMatch("string", ".*ring")`
489504

490505
### IsString
@@ -540,6 +555,7 @@ Examples:
540555

541556
- `Log(attributes["duration_ms"])`
542557

558+
543559
- `Int(Log(attributes["duration_ms"])`
544560

545561
### Microseconds
@@ -636,8 +652,10 @@ Examples:
636652

637653
- `ParseJSON("{\"attr\":true}")`
638654

655+
639656
- `ParseJSON(attributes["kubernetes"])`
640657

658+
641659
- `ParseJSON(body)`
642660

643661
### Seconds
@@ -670,6 +688,7 @@ Examples:
670688

671689
- `SHA1(attributes["device.name"])`
672690

691+
673692
- `SHA1("name")`
674693

675694
**Note:** According to the National Institute of Standards and Technology (NIST), SHA1 is no longer a recommended hash function. It should be avoided except when required for compatibility. New uses should prefer FNV whenever possible.
@@ -690,6 +709,7 @@ Examples:
690709

691710
- `SHA256(attributes["device.name"])`
692711

712+
693713
- `SHA256("name")`
694714

695715
**Note:** According to the National Institute of Standards and Technology (NIST), SHA256 is no longer a recommended hash function. It should be avoided except when required for compatibility. New uses should prefer FNV whenever possible.
@@ -708,7 +728,7 @@ Examples:
708728

709729
### Split
710730

711-
`Split(target, delimiter)`
731+
```Split(target, delimiter)```
712732

713733
The `Split` Converter separates a string by the delimiter, and returns an array of substrings.
714734

pkg/ottl/ottlfuncs/func_replace_all_matches.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ type ReplaceAllMatchesArguments[K any] struct {
2020
Function ottl.Optional[ottl.FunctionGetter[K]]
2121
}
2222

23+
type replaceAllMatchesFuncArgs[K any] struct {
24+
Input ottl.StringGetter[K]
25+
}
26+
2327
func NewReplaceAllMatchesFactory[K any]() ottl.Factory[K] {
2428
return ottl.NewFactory("replace_all_matches", &ReplaceAllMatchesArguments[K]{}, createReplaceAllMatchesFunction[K])
2529
}
@@ -36,12 +40,12 @@ func createReplaceAllMatchesFunction[K any](_ ottl.FunctionContext, oArgs ottl.A
3640

3741
func replaceAllMatches[K any](target ottl.PMapGetter[K], pattern string, replacement ottl.StringGetter[K], fn ottl.Optional[ottl.FunctionGetter[K]]) (ottl.ExprFunc[K], error) {
3842
glob, err := glob.Compile(pattern)
39-
var replacementVal string
4043
if err != nil {
4144
return nil, fmt.Errorf("the pattern supplied to replace_match is not a valid pattern: %w", err)
4245
}
4346
return func(ctx context.Context, tCtx K) (interface{}, error) {
4447
val, err := target.Get(ctx, tCtx)
48+
var replacementVal string
4549
if err != nil {
4650
return nil, err
4751
}
@@ -52,15 +56,19 @@ func replaceAllMatches[K any](target ottl.PMapGetter[K], pattern string, replace
5256
}
5357
} else {
5458
fnVal := fn.Get()
55-
replacementExpr, errNew := fnVal.Get(&FuncArgs[K]{Input: replacement})
59+
replacementExpr, errNew := fnVal.Get(&replaceAllMatchesFuncArgs[K]{Input: replacement})
5660
if errNew != nil {
5761
return nil, errNew
5862
}
5963
replacementValRaw, errNew := replacementExpr.Eval(ctx, tCtx)
6064
if errNew != nil {
6165
return nil, errNew
6266
}
63-
replacementVal = replacementValRaw.(string)
67+
replacementValStr, ok := replacementValRaw.(string)
68+
if !ok {
69+
return nil, fmt.Errorf("replacement value is not a string")
70+
}
71+
replacementVal = replacementValStr
6472
}
6573
val.Range(func(key string, value pcommon.Value) bool {
6674
if glob.Match(value.Str()) {

pkg/ottl/ottlfuncs/func_replace_all_matches_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
1112
"go.opentelemetry.io/collector/component/componenttest"
1213
"go.opentelemetry.io/collector/pdata/pcommon"
1314

@@ -131,6 +132,57 @@ func Test_replaceAllMatches_bad_input(t *testing.T) {
131132
assert.Error(t, err)
132133
}
133134

135+
func Test_replaceAllMatches_bad_function_input(t *testing.T) {
136+
input := pcommon.NewValueInt(1)
137+
target := &ottl.StandardPMapGetter[interface{}]{
138+
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
139+
return tCtx, nil
140+
},
141+
}
142+
replacement := &ottl.StandardStringGetter[interface{}]{
143+
Getter: func(context.Context, interface{}) (interface{}, error) {
144+
return nil, nil
145+
},
146+
}
147+
function := ottl.Optional[ottl.FunctionGetter[interface{}]]{}
148+
149+
exprFunc, err := replaceAllMatches[interface{}](target, "regexp", replacement, function)
150+
assert.NoError(t, err)
151+
152+
result, err := exprFunc(nil, input)
153+
require.Error(t, err)
154+
assert.ErrorContains(t, err, "expected pcommon.Map")
155+
assert.Nil(t, result)
156+
}
157+
158+
func Test_replaceAllMatches_bad_function_result(t *testing.T) {
159+
input := pcommon.NewValueInt(1)
160+
target := &ottl.StandardPMapGetter[interface{}]{
161+
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {
162+
return tCtx, nil
163+
},
164+
}
165+
replacement := &ottl.StandardStringGetter[interface{}]{
166+
Getter: func(context.Context, interface{}) (interface{}, error) {
167+
return "{replacement}", nil
168+
},
169+
}
170+
ottlValue := ottl.StandardFunctionGetter[interface{}]{
171+
FCtx: ottl.FunctionContext{
172+
Set: componenttest.NewNopTelemetrySettings(),
173+
},
174+
Fact: StandardConverters[interface{}]()["IsString"],
175+
}
176+
function := ottl.NewTestingOptional[ottl.FunctionGetter[interface{}]](ottlValue)
177+
178+
exprFunc, err := replaceAllMatches[interface{}](target, "regexp", replacement, function)
179+
assert.NoError(t, err)
180+
181+
result, err := exprFunc(nil, input)
182+
require.Error(t, err)
183+
assert.Nil(t, result)
184+
}
185+
134186
func Test_replaceAllMatches_get_nil(t *testing.T) {
135187
target := &ottl.StandardPMapGetter[interface{}]{
136188
Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) {

pkg/ottl/ottlfuncs/func_replace_all_patterns.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ type ReplaceAllPatternsArguments[K any] struct {
2626
Function ottl.Optional[ottl.FunctionGetter[K]]
2727
}
2828

29+
type replaceAllPatternFuncArgs[K any] struct {
30+
Input ottl.StringGetter[K]
31+
}
32+
2933
func NewReplaceAllPatternsFactory[K any]() ottl.Factory[K] {
3034
return ottl.NewFactory("replace_all_patterns", &ReplaceAllPatternsArguments[K]{}, createReplaceAllPatternsFunction[K])
3135
}
@@ -42,7 +46,6 @@ func createReplaceAllPatternsFunction[K any](_ ottl.FunctionContext, oArgs ottl.
4246

4347
func replaceAllPatterns[K any](target ottl.PMapGetter[K], mode string, regexPattern string, replacement ottl.StringGetter[K], fn ottl.Optional[ottl.FunctionGetter[K]]) (ottl.ExprFunc[K], error) {
4448
compiledPattern, err := regexp.Compile(regexPattern)
45-
var replacementVal string
4649
if err != nil {
4750
return nil, fmt.Errorf("the regex pattern supplied to replace_all_patterns is not a valid pattern: %w", err)
4851
}
@@ -52,6 +55,7 @@ func replaceAllPatterns[K any](target ottl.PMapGetter[K], mode string, regexPatt
5255

5356
return func(ctx context.Context, tCtx K) (interface{}, error) {
5457
val, err := target.Get(ctx, tCtx)
58+
var replacementVal string
5559
if err != nil {
5660
return nil, err
5761
}
@@ -62,15 +66,19 @@ func replaceAllPatterns[K any](target ottl.PMapGetter[K], mode string, regexPatt
6266
}
6367
} else {
6468
fnVal := fn.Get()
65-
replacementExpr, errNew := fnVal.Get(&FuncArgs[K]{Input: replacement})
69+
replacementExpr, errNew := fnVal.Get(&replaceAllPatternFuncArgs[K]{Input: replacement})
6670
if errNew != nil {
6771
return nil, errNew
6872
}
6973
replacementValRaw, errNew := replacementExpr.Eval(ctx, tCtx)
7074
if errNew != nil {
7175
return nil, errNew
7276
}
73-
replacementVal = replacementValRaw.(string)
77+
replacementValStr, ok := replacementValRaw.(string)
78+
if !ok {
79+
return nil, fmt.Errorf("replacement value is not a string")
80+
}
81+
replacementVal = replacementValStr
7482
}
7583
updated := pcommon.NewMap()
7684
updated.EnsureCapacity(val.Len())

0 commit comments

Comments
 (0)