Skip to content

Commit

Permalink
[pkg/ottl] add keep_matching_keys function (#33337)
Browse files Browse the repository at this point in the history
**Description:** This PR adds a `keep_matching_keys` function.
**Link to tracking Issue:** Resolves #32989 

**Testing:** Added unit and end to end tests. Tested manually with the
following config:

```yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  transform:
    error_mode: ignore
    trace_statements:
      - context: span
        statements:
          - keep_matching_keys(attributes, "http.*")

exporters:
  debug:
    verbosity: detailed
  otlphttp:
    endpoint: ${env:DT_ENDPOINT}
    headers:
      Authorization: "Api-Token ${env:API_TOKEN}"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [transform]
      exporters: [otlphttp, debug]
```

Used the `telemetrygen` CLI to send traces with attributes to verify the
function was applied correctly:

```
telemetrygen traces --otlp-insecure --traces 10 --status-code Ok --span-duration 1s --telemetry-attributes http.foo=\"value1\" --telemetry-attributes http.bar=\"value2\" --telemetry-attributes foo=\"bar\"
```

**Documentation:** The documentation has been added to the function
explanations in the readme

---------

Signed-off-by: Florian Bacher <[email protected]>
Co-authored-by: Evan Bradley <[email protected]>
Co-authored-by: Tyler Helmuth <[email protected]>
  • Loading branch information
3 people authored Jun 10, 2024
1 parent 3e49740 commit 431f424
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 1 deletion.
27 changes: 27 additions & 0 deletions .chloggen/keep-matching-keys-function.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Added `keep_matching_keys` function to allow dropping all keys from a map that don't match the pattern.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [32989]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
8 changes: 8 additions & 0 deletions pkg/ottl/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ func Test_e2e_editors(t *testing.T) {
tCtx.GetLogRecord().Attributes().Remove("http.url")
},
},
{
statement: `keep_matching_keys(attributes, "^http")`,
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().Remove("flags")
tCtx.GetLogRecord().Attributes().Remove("total.string")
tCtx.GetLogRecord().Attributes().Remove("foo")
},
},
{
statement: `flatten(attributes)`,
want: func(tCtx ottllog.TransformContext) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/ottl/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.25.0
)

require (
Expand All @@ -41,7 +42,6 @@ require (
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect
Expand Down
18 changes: 18 additions & 0 deletions pkg/ottl/ottlfuncs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Available Editors:

- [delete_key](#delete_key)
- [delete_matching_keys](#delete_matching_keys)
- [keep_matching_keys](#keep_matching_keys)
- [flatten](#flatten)
- [keep_keys](#keep_keys)
- [limit](#limit)
Expand Down Expand Up @@ -92,6 +93,23 @@ Examples:

- `delete_matching_keys(resource.attributes, "(?i).*password.*")`

### keep_matching_keys

`keep_matching_keys(target, pattern)`

The `keep_matching_keys` function keeps all keys from a `pcommon.Map` that match a regex pattern.

`target` is a path expression to a `pcommon.Map` type field. `pattern` is a regex string.

All keys that match the pattern will remain in the map, while non matching keys will be removed.

Examples:


- `keep_matching_keys(attributes, "(?i).*version.*")`

- `keep_matching_keys(resource.attributes, "(?i).*version.*")`

### flatten

`flatten(target, Optional[prefix], Optional[depth])`
Expand Down
51 changes: 51 additions & 0 deletions pkg/ottl/ottlfuncs/func_keep_matching_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"
import (
"fmt"
"regexp"

"go.opentelemetry.io/collector/pdata/pcommon"
"golang.org/x/net/context"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

type KeepMatchingKeysArguments[K any] struct {
Target ottl.PMapGetter[K]
Pattern string
}

func NewKeepMatchingKeysFactory[K any]() ottl.Factory[K] {
return ottl.NewFactory("keep_matching_keys", &KeepMatchingKeysArguments[K]{}, createKeepMatchingKeysFunction[K])
}

func createKeepMatchingKeysFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
args, ok := oArgs.(*KeepMatchingKeysArguments[K])

if !ok {
return nil, fmt.Errorf("KeepMatchingKeysFactory args must be of type *KeepMatchingKeysArguments[K")
}

return keepMatchingKeys(args.Target, args.Pattern)
}

func keepMatchingKeys[K any](target ottl.PMapGetter[K], pattern string) (ottl.ExprFunc[K], error) {
compiledPattern, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("the regex pattern provided to keep_matching_keys is not a valid pattern: %w", err)
}

return func(ctx context.Context, tCtx K) (any, error) {
val, err := target.Get(ctx, tCtx)
if err != nil {
return nil, err
}

val.RemoveIf(func(key string, _ pcommon.Value) bool {
return !compiledPattern.MatchString(key)
})
return nil, nil
}, nil
}
128 changes: 128 additions & 0 deletions pkg/ottl/ottlfuncs/func_keep_matching_keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

func Test_keepMatchingKeys(t *testing.T) {

in := pcommon.NewMap()
in.PutStr("foo", "bar")
in.PutStr("foo1", "bar")
in.PutInt("foo2", 3)

target := &ottl.StandardPMapGetter[pcommon.Map]{
Getter: func(_ context.Context, tCtx pcommon.Map) (any, error) {
return tCtx, nil
},
}

tests := []struct {
name string
target ottl.PMapGetter[pcommon.Map]
pattern string
want func() *pcommon.Map
wantError bool
}{
{
name: "keep everything that ends with a number",
target: target,
pattern: "\\d$",
want: func() *pcommon.Map {
m := pcommon.NewMap()
m.PutStr("foo1", "bar")
m.PutInt("foo2", 3)
return &m
},
},
{
name: "keep nothing",
target: target,
pattern: "bar.*",
want: func() *pcommon.Map {
m := pcommon.NewMap()
// add and remove something to have an empty map instead of nil
m.PutStr("k", "")
m.Remove("k")
return &m
},
},
{
name: "keep everything",
target: target,
pattern: "foo.*",
want: func() *pcommon.Map {
m := pcommon.NewMap()
m.PutStr("foo", "bar")
m.PutStr("foo1", "bar")
m.PutInt("foo2", 3)
return &m
},
},
{
name: "invalid pattern",
target: target,
pattern: "*",
want: func() *pcommon.Map {
return nil
},
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scenarioMap := pcommon.NewMap()
in.CopyTo(scenarioMap)

exprFunc, err := keepMatchingKeys(tt.target, tt.pattern)

if tt.wantError {
assert.Error(t, err)
return
}
assert.NoError(t, err)

_, err = exprFunc(nil, scenarioMap)
assert.NoError(t, err)

assert.Equal(t, *tt.want(), scenarioMap)
})
}
}

func Test_keepMatchingKeys_bad_input(t *testing.T) {
input := pcommon.NewValueInt(1)
target := &ottl.StandardPMapGetter[any]{
Getter: func(_ context.Context, tCtx any) (any, error) {
return tCtx, nil
},
}

exprFunc, err := keepMatchingKeys[any](target, "anything")
assert.NoError(t, err)

_, err = exprFunc(nil, input)
assert.Error(t, err)
}

func Test_keepMatchingKeys_get_nil(t *testing.T) {
target := &ottl.StandardPMapGetter[any]{
Getter: func(_ context.Context, tCtx any) (any, error) {
return tCtx, nil
},
}

exprFunc, err := keepMatchingKeys[any](target, "anything")
assert.NoError(t, err)
_, err = exprFunc(nil, nil)
assert.Error(t, err)
}
1 change: 1 addition & 0 deletions pkg/ottl/ottlfuncs/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func StandardFuncs[K any]() map[string]ottl.Factory[K] {
// Editors
NewDeleteKeyFactory[K](),
NewDeleteMatchingKeysFactory[K](),
NewKeepMatchingKeysFactory[K](),
NewFlattenFactory[K](),
NewKeepKeysFactory[K](),
NewLimitFactory[K](),
Expand Down

0 comments on commit 431f424

Please sign in to comment.