Skip to content

Commit 0ecca4d

Browse files
rnishtala-sumoTylerHelmuthevan-bradley
authored andcommitted
feat(ottl): Add a new ottl trim function that trims leading and trailing whitespace from a string (open-telemetry#36400)
#### Description A field test exists in an event, which contains the string " this is a test ". I would like to be able to transform this using an ottl function trim, that I would define as trim(attribute["test"]), which would trim this down to "this is a test". - Trim(" this is a test ") results in `this is a test` Link to the issue - open-telemetry#34100 --------- Co-authored-by: Tyler Helmuth <[email protected]> Co-authored-by: Evan Bradley <[email protected]>
1 parent e94f367 commit 0ecca4d

File tree

5 files changed

+166
-1
lines changed

5 files changed

+166
-1
lines changed

.chloggen/ottl-trim-function.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: 'enhancement'
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: ottl
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add a new ottl trim function that trims leading and trailing characters from a string (default- whitespace).
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [34100]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

pkg/ottl/ottlfuncs/README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1792,9 +1792,21 @@ The `Split` Converter separates a string by the delimiter, and returns an array
17921792

17931793
If the `target` is not a string or does not exist, the `Split` Converter will return an error.
17941794

1795+
### Trim
1796+
1797+
```Trim(target, Optional[replacement])```
1798+
1799+
The `Trim` Converter removes the leading and trailing character (default: a space character).
1800+
1801+
If the `target` is not a string or does not exist, the `Trim` Converter will return an error.
1802+
1803+
`target` is a string.
1804+
`replacement` is an optional string representing the character to replace with (default: a space character).
1805+
17951806
Examples:
17961807

1797-
- `Split("A|B|C", "|")`
1808+
- `Trim(" this is a test ", " ")`
1809+
- `Trim("!!this is a test!!", "!!")`
17981810

17991811
### String
18001812

pkg/ottl/ottlfuncs/func_trim.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"strings"
10+
11+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
12+
)
13+
14+
type TrimArguments[K any] struct {
15+
Target ottl.StringGetter[K]
16+
Replacement ottl.Optional[string]
17+
}
18+
19+
func NewTrimFactory[K any]() ottl.Factory[K] {
20+
return ottl.NewFactory("Trim", &TrimArguments[K]{}, createTrimFunction[K])
21+
}
22+
23+
func createTrimFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
24+
args, ok := oArgs.(*TrimArguments[K])
25+
26+
if !ok {
27+
return nil, fmt.Errorf("TrimFactory args must be of type *TrimArguments[K]")
28+
}
29+
30+
return trim(args.Target, args.Replacement), nil
31+
}
32+
33+
func trim[K any](target ottl.StringGetter[K], replacement ottl.Optional[string]) ottl.ExprFunc[K] {
34+
replacementString := " "
35+
if !replacement.IsEmpty() {
36+
replacementString = replacement.Get()
37+
}
38+
return func(ctx context.Context, tCtx K) (any, error) {
39+
val, err := target.Get(ctx, tCtx)
40+
if err != nil {
41+
return nil, err
42+
}
43+
return strings.Trim(val, replacementString), nil
44+
}
45+
}

pkg/ottl/ottlfuncs/func_trim_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
12+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
13+
)
14+
15+
func Test_trim(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
target ottl.StringGetter[any]
19+
replacement ottl.Optional[string]
20+
expected any
21+
shouldError bool
22+
}{
23+
{
24+
name: "trim string",
25+
target: &ottl.StandardStringGetter[any]{
26+
Getter: func(_ context.Context, _ any) (any, error) {
27+
return " this is a test ", nil
28+
},
29+
},
30+
replacement: ottl.NewTestingOptional[string](" "),
31+
expected: "this is a test",
32+
shouldError: false,
33+
},
34+
{
35+
name: "trim empty string",
36+
target: &ottl.StandardStringGetter[any]{
37+
Getter: func(_ context.Context, _ any) (any, error) {
38+
return "", nil
39+
},
40+
},
41+
replacement: ottl.NewTestingOptional[string](" "),
42+
expected: "",
43+
shouldError: false,
44+
},
45+
{
46+
name: "No replacement string",
47+
target: &ottl.StandardStringGetter[any]{
48+
Getter: func(_ context.Context, _ any) (any, error) {
49+
return " this is a test ", nil
50+
},
51+
},
52+
replacement: ottl.Optional[string]{},
53+
expected: "this is a test",
54+
shouldError: false,
55+
},
56+
{
57+
name: "Set replacement string to \"\"",
58+
target: &ottl.StandardStringGetter[any]{
59+
Getter: func(_ context.Context, _ any) (any, error) {
60+
return " this is a test ", nil
61+
},
62+
},
63+
replacement: ottl.NewTestingOptional[string](""),
64+
expected: " this is a test ",
65+
shouldError: false,
66+
},
67+
}
68+
for _, tt := range tests {
69+
t.Run(tt.name, func(t *testing.T) {
70+
exprFunc := trim(tt.target, tt.replacement)
71+
result, err := exprFunc(nil, nil)
72+
if tt.shouldError {
73+
assert.Error(t, err)
74+
return
75+
}
76+
assert.NoError(t, err)
77+
assert.Equal(t, tt.expected, result)
78+
})
79+
}
80+
}

pkg/ottl/ottlfuncs/functions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func converters[K any]() []ottl.Factory[K] {
8787
NewStringFactory[K](),
8888
NewSubstringFactory[K](),
8989
NewTimeFactory[K](),
90+
NewTrimFactory[K](),
9091
NewToKeyValueStringFactory[K](),
9192
NewTruncateTimeFactory[K](),
9293
NewTraceIDFactory[K](),

0 commit comments

Comments
 (0)