Skip to content

Commit 7de547d

Browse files
authored
Add TrimPrefix and TrimSuffix to OTTL (open-telemetry#43883)
This is a much optimal way to remove prefix/suffix compare with `replace_pattern(name, "^prefixed", "")` Signed-off-by: Bogdan Drutu <[email protected]>
1 parent a99e1ab commit 7de547d

File tree

7 files changed

+345
-0
lines changed

7 files changed

+345
-0
lines changed

.chloggen/trim-suffix-prefix.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. receiver/filelog)
7+
component: pkg/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 TrimPrefix and TrimSuffix to OTTL
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: [43883]
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: This is a much optimal way to remove prefix/suffix compare with `replace_pattern(name, "^prefixed", "")`
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: [user]

pkg/ottl/ottlfuncs/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,6 +2147,44 @@ Examples:
21472147
- `Trim(" this is a test ", " ")`
21482148
- `Trim("!!this is a test!!", "!!")`
21492149

2150+
### TrimPrefix
2151+
2152+
`TrimPrefix(value, prefix)`
2153+
2154+
The `TrimPrefix` function returns the `value` without the provided leading `prefix` string. If `value` doesn't start with `prefix`, `value` is returned unchanged.
2155+
2156+
The returned type is `string`.
2157+
2158+
If the `value` is not a string or does not exist, the `TrimPrefix` converter will return an error.
2159+
2160+
The `value` is either a path expression to a telemetry field to retrieve or a literal.
2161+
2162+
Examples:
2163+
2164+
- `set(resource.attributes["service.name"], TrimPrefix(resource.attributes["service.name"], "ingest_"))`
2165+
2166+
2167+
- `TrimPrefix("ingest_service", "ingest_")`
2168+
2169+
### TrimSuffix
2170+
2171+
`TrimSuffix(value, suffix)`
2172+
2173+
The `TrimSuffix` function returns the `value` without the provided trailing `suffix` string. If `value` doesn't start with `suffix`, `value` is returned unchanged.
2174+
2175+
The returned type is `string`.
2176+
2177+
If the `value` is not a string or does not exist, the `TrimSuffix` converter will return an error.
2178+
2179+
The `value` is either a path expression to a telemetry field to retrieve or a literal.
2180+
2181+
Examples:
2182+
2183+
- `set(resource.attributes["service.name"], TrimSuffix(resource.attributes["service.name"], "_service"))`
2184+
2185+
2186+
- `TrimSuffix("ingest_service", "_service")`
2187+
21502188
### String
21512189

21522190
`String(value)`
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+
"errors"
9+
"strings"
10+
11+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
12+
)
13+
14+
type TrimPrefixArguments[K any] struct {
15+
Target ottl.StringGetter[K]
16+
Prefix ottl.StringGetter[K]
17+
}
18+
19+
func NewTrimPrefixFactory[K any]() ottl.Factory[K] {
20+
return ottl.NewFactory("Trim", &TrimPrefixArguments[K]{}, createTrimPrefixFunction[K])
21+
}
22+
23+
func createTrimPrefixFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
24+
args, ok := oArgs.(*TrimPrefixArguments[K])
25+
26+
if !ok {
27+
return nil, errors.New("TrimFactory args must be of type *TrimPrefixArguments[K]")
28+
}
29+
30+
return trimPrefix(args.Target, args.Prefix), nil
31+
}
32+
33+
func trimPrefix[K any](target, prefix ottl.StringGetter[K]) ottl.ExprFunc[K] {
34+
return func(ctx context.Context, tCtx K) (any, error) {
35+
val, err := target.Get(ctx, tCtx)
36+
if err != nil {
37+
return nil, err
38+
}
39+
prefixVal, err := prefix.Get(ctx, tCtx)
40+
if err != nil {
41+
return nil, err
42+
}
43+
return strings.TrimPrefix(val, prefixVal), nil
44+
}
45+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
"github.com/stretchr/testify/require"
12+
"go.opentelemetry.io/collector/pdata/pcommon"
13+
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
15+
)
16+
17+
func Test_TrimPrefix(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
target any
21+
prefix ottl.StringGetter[any]
22+
expected string
23+
}{
24+
{
25+
name: "has prefix true",
26+
target: "hello world",
27+
prefix: &ottl.StandardStringGetter[any]{Getter: func(context.Context, any) (any, error) { return "hello ", nil }},
28+
expected: "world",
29+
},
30+
{
31+
name: "has prefix false",
32+
target: "hello world",
33+
prefix: &ottl.StandardStringGetter[any]{Getter: func(context.Context, any) (any, error) { return " world", nil }},
34+
expected: "hello world",
35+
},
36+
{
37+
name: "target pcommon.Value",
38+
target: pcommon.NewValueStr("hello world"),
39+
prefix: &ottl.StandardStringGetter[any]{Getter: func(context.Context, any) (any, error) { return "hello", nil }},
40+
expected: " world",
41+
},
42+
}
43+
for _, tt := range tests {
44+
t.Run(tt.name, func(t *testing.T) {
45+
factory := NewTrimPrefixFactory[any]()
46+
exprFunc, err := factory.CreateFunction(
47+
ottl.FunctionContext{},
48+
&TrimPrefixArguments[any]{
49+
Target: ottl.StandardStringGetter[any]{
50+
Getter: func(context.Context, any) (any, error) {
51+
return tt.target, nil
52+
},
53+
},
54+
Prefix: tt.prefix,
55+
})
56+
assert.NoError(t, err)
57+
result, err := exprFunc(t.Context(), nil)
58+
require.NoError(t, err)
59+
assert.Equal(t, tt.expected, result)
60+
})
61+
}
62+
}
63+
64+
func Test_TrimPrefix_Error(t *testing.T) {
65+
target := &ottl.StandardStringGetter[any]{
66+
Getter: func(context.Context, any) (any, error) {
67+
return true, nil
68+
},
69+
}
70+
prefix := &ottl.StandardStringGetter[any]{
71+
Getter: func(context.Context, any) (any, error) {
72+
return "test", nil
73+
},
74+
}
75+
exprFunc := trimPrefix[any](target, prefix)
76+
_, err := exprFunc(t.Context(), nil)
77+
require.Error(t, err)
78+
}
79+
80+
func Test_TrimPrefix_Error_prefix(t *testing.T) {
81+
target := &ottl.StandardStringGetter[any]{
82+
Getter: func(context.Context, any) (any, error) {
83+
return true, nil
84+
},
85+
}
86+
prefix := &ottl.StandardStringGetter[any]{
87+
Getter: func(context.Context, any) (any, error) {
88+
return true, nil
89+
},
90+
}
91+
exprFunc := trimPrefix[any](target, prefix)
92+
_, err := exprFunc(t.Context(), nil)
93+
require.Error(t, err)
94+
}
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+
"errors"
9+
"strings"
10+
11+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
12+
)
13+
14+
type TrimSuffixArguments[K any] struct {
15+
Target ottl.StringGetter[K]
16+
Suffix ottl.StringGetter[K]
17+
}
18+
19+
func NewTrimSuffixFactory[K any]() ottl.Factory[K] {
20+
return ottl.NewFactory("Trim", &TrimSuffixArguments[K]{}, createTrimSuffixFunction[K])
21+
}
22+
23+
func createTrimSuffixFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
24+
args, ok := oArgs.(*TrimSuffixArguments[K])
25+
26+
if !ok {
27+
return nil, errors.New("TrimFactory args must be of type *TrimSuffixArguments[K]")
28+
}
29+
30+
return trimSuffix(args.Target, args.Suffix), nil
31+
}
32+
33+
func trimSuffix[K any](target, prefix ottl.StringGetter[K]) ottl.ExprFunc[K] {
34+
return func(ctx context.Context, tCtx K) (any, error) {
35+
val, err := target.Get(ctx, tCtx)
36+
if err != nil {
37+
return nil, err
38+
}
39+
prefixVal, err := prefix.Get(ctx, tCtx)
40+
if err != nil {
41+
return nil, err
42+
}
43+
return strings.TrimSuffix(val, prefixVal), nil
44+
}
45+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
"github.com/stretchr/testify/require"
12+
"go.opentelemetry.io/collector/pdata/pcommon"
13+
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
15+
)
16+
17+
func Test_TrimSuffix(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
target any
21+
prefix ottl.StringGetter[any]
22+
expected string
23+
}{
24+
{
25+
name: "has prefix true",
26+
target: "hello world",
27+
prefix: &ottl.StandardStringGetter[any]{Getter: func(context.Context, any) (any, error) { return "hello ", nil }},
28+
expected: "hello world",
29+
},
30+
{
31+
name: "has prefix false",
32+
target: "hello world",
33+
prefix: &ottl.StandardStringGetter[any]{Getter: func(context.Context, any) (any, error) { return " world", nil }},
34+
expected: "hello",
35+
},
36+
{
37+
name: "target pcommon.Value",
38+
target: pcommon.NewValueStr("hello world"),
39+
prefix: &ottl.StandardStringGetter[any]{Getter: func(context.Context, any) (any, error) { return "world", nil }},
40+
expected: "hello ",
41+
},
42+
}
43+
for _, tt := range tests {
44+
t.Run(tt.name, func(t *testing.T) {
45+
factory := NewTrimSuffixFactory[any]()
46+
exprFunc, err := factory.CreateFunction(
47+
ottl.FunctionContext{},
48+
&TrimSuffixArguments[any]{
49+
Target: ottl.StandardStringGetter[any]{
50+
Getter: func(context.Context, any) (any, error) {
51+
return tt.target, nil
52+
},
53+
},
54+
Suffix: tt.prefix,
55+
})
56+
assert.NoError(t, err)
57+
result, err := exprFunc(t.Context(), nil)
58+
require.NoError(t, err)
59+
assert.Equal(t, tt.expected, result)
60+
})
61+
}
62+
}
63+
64+
func Test_TrimSuffix_Error(t *testing.T) {
65+
target := &ottl.StandardStringGetter[any]{
66+
Getter: func(context.Context, any) (any, error) {
67+
return true, nil
68+
},
69+
}
70+
prefix := &ottl.StandardStringGetter[any]{
71+
Getter: func(context.Context, any) (any, error) {
72+
return "test", nil
73+
},
74+
}
75+
exprFunc := trimSuffix[any](target, prefix)
76+
_, err := exprFunc(t.Context(), nil)
77+
require.Error(t, err)
78+
}
79+
80+
func Test_TrimSuffix_Error_prefix(t *testing.T) {
81+
target := &ottl.StandardStringGetter[any]{
82+
Getter: func(context.Context, any) (any, error) {
83+
return true, nil
84+
},
85+
}
86+
prefix := &ottl.StandardStringGetter[any]{
87+
Getter: func(context.Context, any) (any, error) {
88+
return true, nil
89+
},
90+
}
91+
exprFunc := trimSuffix[any](target, prefix)
92+
_, err := exprFunc(t.Context(), nil)
93+
require.Error(t, err)
94+
}

pkg/ottl/ottlfuncs/functions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ func converters[K any]() []ottl.Factory[K] {
100100
NewTimeFactory[K](),
101101
NewFormatTimeFactory[K](),
102102
NewTrimFactory[K](),
103+
NewTrimPrefixFactory[K](),
104+
NewTrimSuffixFactory[K](),
103105
NewToKeyValueStringFactory[K](),
104106
NewToCamelCaseFactory[K](),
105107
NewToLowerCaseFactory[K](),

0 commit comments

Comments
 (0)