From ba529f098269aa81bc9fb8dc830b56fa28ddc5ad Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Fri, 30 Jun 2023 17:20:23 -0500 Subject: [PATCH 1/6] Add ottl truncate editor --- pkg/ottl/ottlfuncs/README.md | 15 +++ pkg/ottl/ottlfuncs/func_truncate.go | 58 ++++++++++ pkg/ottl/ottlfuncs/func_truncate_test.go | 133 +++++++++++++++++++++++ pkg/ottl/ottlfuncs/functions.go | 1 + 4 files changed, 207 insertions(+) create mode 100644 pkg/ottl/ottlfuncs/func_truncate.go create mode 100644 pkg/ottl/ottlfuncs/func_truncate_test.go diff --git a/pkg/ottl/ottlfuncs/README.md b/pkg/ottl/ottlfuncs/README.md index e63d09fe7cc2..5d3f5d9c594f 100644 --- a/pkg/ottl/ottlfuncs/README.md +++ b/pkg/ottl/ottlfuncs/README.md @@ -41,6 +41,7 @@ Available Editors: - [replace_match](#replace_match) - [replace_pattern](#replace_pattern) - [set](#set) +- [truncate](#truncate) - [truncate_all](#truncate_all) ### delete_key @@ -252,6 +253,20 @@ Examples: - `set(attributes["source"], trace_state["source"])` +### truncate + +`truncate(target, limit)` + +The `truncate` function truncates the string value such that the result is no longer than the limit. + +`target` is a path expression to a telemetry field. `pattern` is a string following [filepath.Match syntax](https://pkg.go.dev/path/filepath#Match). + +The `target` will be mutated such that the number of characters in it is less than or equal to the limit. Non-string values are ignored. + +Examples: + +- `truncate(body, 100)` + ### truncate_all `truncate_all(target, limit)` diff --git a/pkg/ottl/ottlfuncs/func_truncate.go b/pkg/ottl/ottlfuncs/func_truncate.go new file mode 100644 index 000000000000..c523855a5c70 --- /dev/null +++ b/pkg/ottl/ottlfuncs/func_truncate.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs" + +import ( + "context" + "fmt" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" +) + +type TruncateArguments[K any] struct { + Target ottl.GetSetter[K] `ottlarg:"0"` + Limit int64 `ottlarg:"1"` +} + +func NewTruncateFactory[K any]() ottl.Factory[K] { + return ottl.NewFactory("truncate", &TruncateArguments[K]{}, createTruncateFunction[K]) +} + +func createTruncateFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) { + args, ok := oArgs.(*TruncateArguments[K]) + + if !ok { + return nil, fmt.Errorf("TruncateFactory args must be of type *TruncateArguments[K]") + } + + return truncate(args.Target, args.Limit) +} + +func truncate[K any](target ottl.GetSetter[K], limit int64) (ottl.ExprFunc[K], error) { + if limit < 0 { + return nil, fmt.Errorf("invalid limit for truncate function, %d cannot be negative", limit) + } + return func(ctx context.Context, tCtx K) (interface{}, error) { + if limit < 0 { + return nil, nil + } + + val, err := target.Get(ctx, tCtx) + if err != nil { + return nil, err + } + if val == nil { + return nil, nil + } + if valStr, ok := val.(string); ok { + if int64(len(valStr)) > limit { + err = target.Set(ctx, tCtx, valStr[:limit]) + if err != nil { + return nil, err + } + } + } + return nil, nil + }, nil +} diff --git a/pkg/ottl/ottlfuncs/func_truncate_test.go b/pkg/ottl/ottlfuncs/func_truncate_test.go new file mode 100644 index 000000000000..0acb40f356f8 --- /dev/null +++ b/pkg/ottl/ottlfuncs/func_truncate_test.go @@ -0,0 +1,133 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package ottlfuncs + +import ( + "context" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/pdata/pcommon" + "testing" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" + "github.com/stretchr/testify/assert" +) + +func Test_truncate(t *testing.T) { + input := pcommon.NewValueStr("hello world") + + target := &ottl.StandardGetSetter[pcommon.Value]{ + Getter: func(ctx context.Context, tCtx pcommon.Value) (interface{}, error) { + return tCtx.Str(), nil + }, + Setter: func(ctx context.Context, tCtx pcommon.Value, val interface{}) error { + tCtx.SetStr(val.(string)) + return nil + }, + } + + tests := []struct { + name string + target ottl.GetSetter[pcommon.Value] + limit int64 + want func(value pcommon.Value) + }{ + { + name: "below limit", + target: target, + limit: 20, + want: func(expectedValue pcommon.Value) { + expectedValue.SetStr("hello world") + }, + }, + { + name: "above limit", + target: target, + limit: 10, + want: func(expectedValue pcommon.Value) { + expectedValue.SetStr("hello worl") + }, + }, + { + name: "at limit", + target: target, + limit: 11, + want: func(expectedValue pcommon.Value) { + expectedValue.SetStr("hello world") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + scenarioValue := pcommon.NewValueStr(input.Str()) + + exprFunc, err := truncate(tt.target, tt.limit) + assert.NoError(t, err) + result, err := exprFunc(nil, scenarioValue) + assert.NoError(t, err) + assert.Nil(t, result) + + expected := pcommon.NewValueStr("") + tt.want(expected) + + assert.Equal(t, expected, scenarioValue) + }) + } +} + +func Test_truncate_bad_input(t *testing.T) { + input := pcommon.NewValueInt(1) + target := &ottl.StandardGetSetter[interface{}]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return tCtx, nil + }, + Setter: func(ctx context.Context, tCtx interface{}, val interface{}) error { + t.Errorf("nothing should be set in this scenario") + return nil + }, + } + + exprFunc, err := truncate[interface{}](target, 10) + assert.NoError(t, err) + + result, err := exprFunc(nil, input) + assert.NoError(t, err) + assert.Nil(t, result) + + assert.Equal(t, pcommon.NewValueInt(1), input) +} + +func Test_truncate_get_nil(t *testing.T) { + target := &ottl.StandardGetSetter[interface{}]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return tCtx, nil + }, + Setter: func(ctx context.Context, tCtx interface{}, val interface{}) error { + t.Errorf("nothing should be set in this scenario") + return nil + }, + } + + exprFunc, err := truncate[interface{}](target, 10) + assert.NoError(t, err) + + result, err := exprFunc(nil, nil) + assert.NoError(t, err) + assert.Nil(t, result) +} + +func Test_truncate_validation(t *testing.T) { + target := &ottl.StandardGetSetter[interface{}]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return tCtx, nil + }, + Setter: func(ctx context.Context, tCtx interface{}, val interface{}) error { + t.Errorf("nothing should be set in this scenario") + return nil + }, + } + + _, err := truncate[interface{}](target, -1) + require.Error(t, err) + assert.ErrorContains(t, err, "invalid limit for truncate function, -1 cannot be negative") +} diff --git a/pkg/ottl/ottlfuncs/functions.go b/pkg/ottl/ottlfuncs/functions.go index 45b6f5a15a07..a27bf05c71ee 100644 --- a/pkg/ottl/ottlfuncs/functions.go +++ b/pkg/ottl/ottlfuncs/functions.go @@ -20,6 +20,7 @@ func StandardFuncs[K any]() map[string]ottl.Factory[K] { NewReplaceMatchFactory[K](), NewReplacePatternFactory[K](), NewSetFactory[K](), + NewTruncateFactory[K](), NewTruncateAllFactory[K](), } f = append(f, converters[K]()...) From 0e04f1f3c9a087be2cc65525a5909a1df7cc1c46 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 12 Jul 2023 09:31:48 -0500 Subject: [PATCH 2/6] PR feedback --- .chloggen/add-ottl-truncate-editor.yaml | 20 ++++++++++++++++++++ pkg/ottl/ottlfuncs/func_truncate.go | 21 +++++++++------------ pkg/ottl/ottlfuncs/func_truncate_all.go | 9 ++------- 3 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 .chloggen/add-ottl-truncate-editor.yaml diff --git a/.chloggen/add-ottl-truncate-editor.yaml b/.chloggen/add-ottl-truncate-editor.yaml new file mode 100644 index 000000000000..df636e52da32 --- /dev/null +++ b/.chloggen/add-ottl-truncate-editor.yaml @@ -0,0 +1,20 @@ +# Use this changelog template to create an entry for release notes. +# If your change doesn't affect end users, such as a test fix or a tooling change, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. + +# 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: Add truncate editor + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [23847] + +# (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: diff --git a/pkg/ottl/ottlfuncs/func_truncate.go b/pkg/ottl/ottlfuncs/func_truncate.go index c523855a5c70..0e03c9bf5373 100644 --- a/pkg/ottl/ottlfuncs/func_truncate.go +++ b/pkg/ottl/ottlfuncs/func_truncate.go @@ -34,25 +34,22 @@ func truncate[K any](target ottl.GetSetter[K], limit int64) (ottl.ExprFunc[K], e return nil, fmt.Errorf("invalid limit for truncate function, %d cannot be negative", limit) } return func(ctx context.Context, tCtx K) (interface{}, error) { - if limit < 0 { - return nil, nil - } - val, err := target.Get(ctx, tCtx) if err != nil { return nil, err } - if val == nil { - return nil, nil - } if valStr, ok := val.(string); ok { - if int64(len(valStr)) > limit { - err = target.Set(ctx, tCtx, valStr[:limit]) - if err != nil { - return nil, err - } + if truncatedVal, isTruncated := maybeTruncate(valStr, limit); isTruncated { + return nil, target.Set(ctx, tCtx, truncatedVal) } } return nil, nil }, nil } + +func maybeTruncate(value string, limit int64) (string, bool) { + if int64(len(value)) > limit { + return value[:limit], true + } + return value, false +} diff --git a/pkg/ottl/ottlfuncs/func_truncate_all.go b/pkg/ottl/ottlfuncs/func_truncate_all.go index 5196476a8b3f..4118d7bbc562 100644 --- a/pkg/ottl/ottlfuncs/func_truncate_all.go +++ b/pkg/ottl/ottlfuncs/func_truncate_all.go @@ -36,18 +36,13 @@ func TruncateAll[K any](target ottl.PMapGetter[K], limit int64) (ottl.ExprFunc[K return nil, fmt.Errorf("invalid limit for truncate_all function, %d cannot be negative", limit) } return func(ctx context.Context, tCtx K) (interface{}, error) { - if limit < 0 { - return nil, nil - } - val, err := target.Get(ctx, tCtx) if err != nil { return nil, err } val.Range(func(key string, value pcommon.Value) bool { - stringVal := value.Str() - if int64(len(stringVal)) > limit { - value.SetStr(stringVal[:limit]) + if truncatedVal, isTruncated := maybeTruncate(value.Str(), limit); isTruncated { + value.SetStr(truncatedVal) } return true }) From 58469ab28ae438acd5997b4072922db51aff7005 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 12 Jul 2023 10:02:33 -0500 Subject: [PATCH 3/6] gofmt --- pkg/ottl/ottlfuncs/func_truncate_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/ottl/ottlfuncs/func_truncate_test.go b/pkg/ottl/ottlfuncs/func_truncate_test.go index 0acb40f356f8..2e2252bc67fa 100644 --- a/pkg/ottl/ottlfuncs/func_truncate_test.go +++ b/pkg/ottl/ottlfuncs/func_truncate_test.go @@ -5,12 +5,14 @@ package ottlfuncs import ( "context" + "testing" + "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/pdata/pcommon" - "testing" - "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" "github.com/stretchr/testify/assert" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) func Test_truncate(t *testing.T) { From cdbee0e30df7dc6bb304cd51185224abe7a28a39 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 12 Jul 2023 11:17:10 -0500 Subject: [PATCH 4/6] Revert maybeTruncate --- pkg/ottl/ottlfuncs/func_truncate.go | 11 ++--------- pkg/ottl/ottlfuncs/func_truncate_all.go | 5 +++-- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/pkg/ottl/ottlfuncs/func_truncate.go b/pkg/ottl/ottlfuncs/func_truncate.go index 0e03c9bf5373..f6ab784d9147 100644 --- a/pkg/ottl/ottlfuncs/func_truncate.go +++ b/pkg/ottl/ottlfuncs/func_truncate.go @@ -39,17 +39,10 @@ func truncate[K any](target ottl.GetSetter[K], limit int64) (ottl.ExprFunc[K], e return nil, err } if valStr, ok := val.(string); ok { - if truncatedVal, isTruncated := maybeTruncate(valStr, limit); isTruncated { - return nil, target.Set(ctx, tCtx, truncatedVal) + if int64(len(valStr)) > limit { + return nil, target.Set(ctx, tCtx, valStr[:limit]) } } return nil, nil }, nil } - -func maybeTruncate(value string, limit int64) (string, bool) { - if int64(len(value)) > limit { - return value[:limit], true - } - return value, false -} diff --git a/pkg/ottl/ottlfuncs/func_truncate_all.go b/pkg/ottl/ottlfuncs/func_truncate_all.go index 4118d7bbc562..f8419ed6ef5d 100644 --- a/pkg/ottl/ottlfuncs/func_truncate_all.go +++ b/pkg/ottl/ottlfuncs/func_truncate_all.go @@ -41,8 +41,9 @@ func TruncateAll[K any](target ottl.PMapGetter[K], limit int64) (ottl.ExprFunc[K return nil, err } val.Range(func(key string, value pcommon.Value) bool { - if truncatedVal, isTruncated := maybeTruncate(value.Str(), limit); isTruncated { - value.SetStr(truncatedVal) + stringVal := value.Str() + if int64(len(stringVal)) > limit { + value.SetStr(stringVal[:limit]) } return true }) From 4c7f84120e16c0c2b5fd756c944cd0e6c54ffcd7 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 12 Jul 2023 11:31:44 -0500 Subject: [PATCH 5/6] golangci-lint --- pkg/ottl/ottlfuncs/func_truncate_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/ottl/ottlfuncs/func_truncate_test.go b/pkg/ottl/ottlfuncs/func_truncate_test.go index 2e2252bc67fa..0133d6218c6a 100644 --- a/pkg/ottl/ottlfuncs/func_truncate_test.go +++ b/pkg/ottl/ottlfuncs/func_truncate_test.go @@ -7,11 +7,10 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/pdata/pcommon" - "github.com/stretchr/testify/assert" - "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) From 922da0d5f0a0a42a5bb37cba4a8474e82bc4e90a Mon Sep 17 00:00:00 2001 From: jack-berg <34418638+jack-berg@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:06:37 -0500 Subject: [PATCH 6/6] Update .chloggen/add-ottl-truncate-editor.yaml Co-authored-by: Tyler Helmuth <12352919+TylerHelmuth@users.noreply.github.com> --- .chloggen/add-ottl-truncate-editor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chloggen/add-ottl-truncate-editor.yaml b/.chloggen/add-ottl-truncate-editor.yaml index df636e52da32..c54d6c24a7c7 100644 --- a/.chloggen/add-ottl-truncate-editor.yaml +++ b/.chloggen/add-ottl-truncate-editor.yaml @@ -9,7 +9,7 @@ change_type: enhancement component: pkg/ottl # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). -note: Add truncate editor +note: Add new `truncate` editor that allows truncating a string to a specific length # Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. issues: [23847]