Skip to content

Commit 3d79645

Browse files
[pkg/ottl] Support dynamic pattern key in keep_keys and keep_matching_keys functions (open-telemetry#43729)
Co-authored-by: Evan Bradley <[email protected]>
1 parent 2fd959b commit 3d79645

File tree

6 files changed

+152
-26
lines changed

6 files changed

+152
-26
lines changed
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: Support paths and expressions as keys in `keep_keys` and `keep_matching_keys`
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: [43555]
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/e2e/e2e_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ func Test_e2e_editors(t *testing.T) {
6464
tCtx.GetLogRecord().Attributes().Remove("conflict")
6565
},
6666
},
67+
{
68+
statement: `keep_matching_keys(attributes, Concat(["^", "http"], ""))`,
69+
want: func(tCtx ottllog.TransformContext) {
70+
tCtx.GetLogRecord().Attributes().Remove("flags")
71+
tCtx.GetLogRecord().Attributes().Remove("total.string")
72+
tCtx.GetLogRecord().Attributes().Remove("foo")
73+
tCtx.GetLogRecord().Attributes().Remove("things")
74+
tCtx.GetLogRecord().Attributes().Remove("conflict.conflict1")
75+
tCtx.GetLogRecord().Attributes().Remove("conflict")
76+
},
77+
},
6778
{
6879
statement: `flatten(attributes)`,
6980
want: func(tCtx ottllog.TransformContext) {
@@ -1225,6 +1236,14 @@ func Test_e2e_converters(t *testing.T) {
12251236
tCtx.GetLogRecord().Attributes().PutStr("test", `"`)
12261237
},
12271238
},
1239+
{
1240+
statement: `keep_keys(attributes["foo"], [Concat(["ba", "r"], "")])`,
1241+
want: func(tCtx ottllog.TransformContext) {
1242+
// keep_keys should see two arguments
1243+
m := tCtx.GetLogRecord().Attributes().PutEmptyMap("foo")
1244+
m.PutStr("bar", "pass")
1245+
},
1246+
},
12281247
{
12291248
statement: `keep_keys(attributes["foo"], ["\\", "bar"])`,
12301249
want: func(tCtx ottllog.TransformContext) {

pkg/ottl/ottlfuncs/func_keep_keys.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
type KeepKeysArguments[K any] struct {
1616
Target ottl.PMapGetSetter[K]
17-
Keys []string
17+
Keys []ottl.StringGetter[K]
1818
}
1919

2020
func NewKeepKeysFactory[K any]() ottl.Factory[K] {
@@ -31,17 +31,40 @@ func createKeepKeysFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments)
3131
return keepKeys(args.Target, args.Keys), nil
3232
}
3333

34-
func keepKeys[K any](target ottl.PMapGetSetter[K], keys []string) ottl.ExprFunc[K] {
35-
keySet := make(map[string]struct{}, len(keys))
34+
func keepKeys[K any](target ottl.PMapGetSetter[K], keys []ottl.StringGetter[K]) ottl.ExprFunc[K] {
35+
// Check if all keys are literals and pre-build the key set if so
36+
literalKeySet := make(map[string]struct{}, len(keys))
3637
for _, key := range keys {
37-
keySet[key] = struct{}{}
38+
k, isLiteral := ottl.GetLiteralValue(key)
39+
if !isLiteral {
40+
literalKeySet = nil
41+
break
42+
}
43+
literalKeySet[k] = struct{}{}
3844
}
3945

4046
return func(ctx context.Context, tCtx K) (any, error) {
4147
val, err := target.Get(ctx, tCtx)
4248
if err != nil {
4349
return nil, err
4450
}
51+
52+
var keySet map[string]struct{}
53+
if literalKeySet != nil {
54+
// Use pre-built key set for literal keys
55+
keySet = literalKeySet
56+
} else {
57+
// Build key set at runtime for dynamic keys
58+
keySet = make(map[string]struct{}, len(keys))
59+
for _, key := range keys {
60+
k, err := key.Get(ctx, tCtx)
61+
if err != nil {
62+
return nil, err
63+
}
64+
keySet[k] = struct{}{}
65+
}
66+
}
67+
4568
val.RemoveIf(func(key string, _ pcommon.Value) bool {
4669
_, ok := keySet[key]
4770
return !ok

pkg/ottl/ottlfuncs/func_keep_keys_test.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,17 @@ func Test_keepKeys(t *testing.T) {
7171
},
7272
}
7373

74-
exprFunc := keepKeys(target, tt.keys)
74+
keys := make([]ottl.StringGetter[pcommon.Map], len(tt.keys))
75+
for i, key := range tt.keys {
76+
k := key
77+
keys[i] = ottl.StandardStringGetter[pcommon.Map]{
78+
Getter: func(_ context.Context, _ pcommon.Map) (any, error) {
79+
return k, nil
80+
},
81+
}
82+
}
83+
84+
exprFunc := keepKeys(target, keys)
7585

7686
_, err := exprFunc(nil, scenarioMap)
7787
assert.NoError(t, err)
@@ -96,7 +106,13 @@ func Test_keepKeys_bad_input(t *testing.T) {
96106
},
97107
}
98108

99-
keys := []string{"anything"}
109+
keys := []ottl.StringGetter[any]{
110+
ottl.StandardStringGetter[any]{
111+
Getter: func(_ context.Context, _ any) (any, error) {
112+
return "anything", nil
113+
},
114+
},
115+
}
100116

101117
exprFunc := keepKeys[any](target, keys)
102118

@@ -114,7 +130,13 @@ func Test_keepKeys_get_nil(t *testing.T) {
114130
},
115131
}
116132

117-
keys := []string{"anything"}
133+
keys := []ottl.StringGetter[any]{
134+
ottl.StandardStringGetter[any]{
135+
Getter: func(_ context.Context, _ any) (any, error) {
136+
return "anything", nil
137+
},
138+
},
139+
}
118140

119141
exprFunc := keepKeys[any](target, keys)
120142
_, err := exprFunc(nil, nil)

pkg/ottl/ottlfuncs/func_keep_matching_keys.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"
55
import (
66
"errors"
7-
"fmt"
8-
"regexp"
97

108
"go.opentelemetry.io/collector/pdata/pcommon"
119
"golang.org/x/net/context"
@@ -15,7 +13,7 @@ import (
1513

1614
type KeepMatchingKeysArguments[K any] struct {
1715
Target ottl.PMapGetSetter[K]
18-
Pattern string
16+
Pattern ottl.StringGetter[K]
1917
}
2018

2119
func NewKeepMatchingKeysFactory[K any]() ottl.Factory[K] {
@@ -32,20 +30,24 @@ func createKeepMatchingKeysFunction[K any](_ ottl.FunctionContext, oArgs ottl.Ar
3230
return keepMatchingKeys(args.Target, args.Pattern)
3331
}
3432

35-
func keepMatchingKeys[K any](target ottl.PMapGetSetter[K], pattern string) (ottl.ExprFunc[K], error) {
36-
compiledPattern, err := regexp.Compile(pattern)
33+
func keepMatchingKeys[K any](target ottl.PMapGetSetter[K], pattern ottl.StringGetter[K]) (ottl.ExprFunc[K], error) {
34+
compiledPattern, err := newDynamicRegex("keep_matching_keys", pattern)
3735
if err != nil {
38-
return nil, fmt.Errorf("the regex pattern provided to keep_matching_keys is not a valid pattern: %w", err)
36+
return nil, err
3937
}
40-
4138
return func(ctx context.Context, tCtx K) (any, error) {
39+
cp, err := compiledPattern.compile(ctx, tCtx)
40+
if err != nil {
41+
return nil, err
42+
}
43+
4244
val, err := target.Get(ctx, tCtx)
4345
if err != nil {
4446
return nil, err
4547
}
4648

4749
val.RemoveIf(func(key string, _ pcommon.Value) bool {
48-
return !compiledPattern.MatchString(key)
50+
return !cp.MatchString(key)
4951
})
5052
return nil, target.Set(ctx, tCtx, val)
5153
}, nil

pkg/ottl/ottlfuncs/func_keep_matching_keys_test.go

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,6 @@ func Test_keepMatchingKeys(t *testing.T) {
5858
return &m
5959
},
6060
},
61-
{
62-
name: "invalid pattern",
63-
pattern: "*",
64-
want: func() *pcommon.Map {
65-
return nil
66-
},
67-
wantError: true,
68-
},
6961
}
7062
for _, tt := range tests {
7163
t.Run(tt.name, func(t *testing.T) {
@@ -87,7 +79,12 @@ func Test_keepMatchingKeys(t *testing.T) {
8779
},
8880
}
8981

90-
exprFunc, err := keepMatchingKeys(target, tt.pattern)
82+
pattern := &ottl.StandardStringGetter[pcommon.Map]{
83+
Getter: func(_ context.Context, _ pcommon.Map) (any, error) {
84+
return tt.pattern, nil
85+
},
86+
}
87+
exprFunc, err := keepMatchingKeys(target, pattern)
9188

9289
if tt.wantError {
9390
assert.Error(t, err)
@@ -115,7 +112,37 @@ func Test_keepMatchingKeys_bad_input(t *testing.T) {
115112
},
116113
}
117114

118-
exprFunc, err := keepMatchingKeys[any](target, "anything")
115+
pattern := &ottl.StandardStringGetter[any]{
116+
Getter: func(_ context.Context, _ any) (any, error) {
117+
return "anything", nil
118+
},
119+
}
120+
121+
exprFunc, err := keepMatchingKeys[any](target, pattern)
122+
assert.NoError(t, err)
123+
124+
_, err = exprFunc(nil, input)
125+
assert.Error(t, err)
126+
}
127+
128+
func Test_keepMatchingKeys_invalid_pattern(t *testing.T) {
129+
input := pcommon.NewValueInt(1)
130+
target := &ottl.StandardPMapGetSetter[any]{
131+
Getter: func(_ context.Context, tCtx any) (pcommon.Map, error) {
132+
if v, ok := tCtx.(pcommon.Map); ok {
133+
return v, nil
134+
}
135+
return pcommon.Map{}, errors.New("expected pcommon.Map")
136+
},
137+
}
138+
139+
pattern := &ottl.StandardStringGetter[any]{
140+
Getter: func(_ context.Context, _ any) (any, error) {
141+
return "*", nil
142+
},
143+
}
144+
145+
exprFunc, err := keepMatchingKeys[any](target, pattern)
119146
assert.NoError(t, err)
120147

121148
_, err = exprFunc(nil, input)
@@ -132,7 +159,13 @@ func Test_keepMatchingKeys_get_nil(t *testing.T) {
132159
},
133160
}
134161

135-
exprFunc, err := keepMatchingKeys[any](target, "anything")
162+
pattern := &ottl.StandardStringGetter[any]{
163+
Getter: func(_ context.Context, _ any) (any, error) {
164+
return "anything", nil
165+
},
166+
}
167+
168+
exprFunc, err := keepMatchingKeys[any](target, pattern)
136169
assert.NoError(t, err)
137170
_, err = exprFunc(nil, nil)
138171
assert.Error(t, err)

0 commit comments

Comments
 (0)