Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(coreinternal/attraction): supports pattern for delete and hash attractions #10822

Merged
merged 6 commits into from
Jul 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 53 additions & 10 deletions internal/coreinternal/attraction/attraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,12 @@ const (
UPSERT Action = "upsert"

// DELETE deletes the attribute. If the key doesn't exist, no action is performed.
// Supports pattern which is matched against attribute key.
DELETE Action = "delete"

// HASH calculates the SHA-1 hash of an existing value and overwrites the
// value with it's SHA-1 hash result.
// Supports pattern which is matched against attribute key.
HASH Action = "hash"

// EXTRACT extracts values using a regular expression rule from the input
Expand Down Expand Up @@ -176,13 +178,22 @@ type AttrProc struct {
func NewAttrProc(settings *Settings) (*AttrProc, error) {
var attributeActions []attributeAction
for i, a := range settings.Actions {
// `key` is a required field
if a.Key == "" {
return nil, fmt.Errorf("error creating AttrProc due to missing required field \"key\" at the %d-th actions", i)
}

// Convert `action` to lowercase for comparison.
a.Action = Action(strings.ToLower(string(a.Action)))

switch a.Action {
case DELETE, HASH:
// requires `key` and/or `pattern`
if a.Key == "" && a.RegexPattern == "" {
return nil, fmt.Errorf("error creating AttrProc due to missing required field (at least one of \"key\" and \"pattern\" have to be used) at the %d-th actions", i)
}
default:
// `key` is a required field
if a.Key == "" {
return nil, fmt.Errorf("error creating AttrProc due to missing required field \"key\" at the %d-th actions", i)
}
}

action := attributeAction{
Key: a.Key,
Action: a.Action,
Expand Down Expand Up @@ -217,8 +228,16 @@ func NewAttrProc(settings *Settings) (*AttrProc, error) {
action.FromContext = a.FromContext
}
case HASH, DELETE:
if valueSourceCount > 0 || a.RegexPattern != "" {
return nil, fmt.Errorf("error creating AttrProc. Action \"%s\" does not use value sources or \"pattern\" field. These must not be specified for %d-th action", a.Action, i)
if a.Value != nil || a.FromAttribute != "" {
return nil, fmt.Errorf("error creating AttrProc. Action \"%s\" does not use \"value\" or \"from_attribute\" field. These must not be specified for %d-th action", a.Action, i)
}

if a.RegexPattern != "" {
re, err := regexp.Compile(a.RegexPattern)
if err != nil {
return nil, fmt.Errorf("error creating AttrProc. Field \"pattern\" has invalid pattern: \"%s\" to be set at the %d-th actions", a.RegexPattern, i)
}
action.Regex = re
}
if a.ConvertedType != "" {
return nil, fmt.Errorf("error creating AttrProc. Action \"%s\" does not use the \"converted_type\" field. This must not be specified for %d-th action", a.Action, i)
Expand Down Expand Up @@ -282,6 +301,10 @@ func (ap *AttrProc) Process(ctx context.Context, logger *zap.Logger, attrs pcomm
switch action.Action {
case DELETE:
attrs.Remove(action.Key)

for _, k := range getMatchingKeys(action.Regex, attrs) {
attrs.Remove(k)
}
case INSERT:
av, found := getSourceAttributeValue(ctx, action, attrs)
if !found {
Expand All @@ -301,7 +324,11 @@ func (ap *AttrProc) Process(ctx context.Context, logger *zap.Logger, attrs pcomm
}
attrs.Upsert(action.Key, av)
case HASH:
hashAttribute(action, attrs)
hashAttribute(action.Key, attrs)

for _, k := range getMatchingKeys(action.Regex, attrs) {
hashAttribute(k, attrs)
}
case EXTRACT:
extractAttributes(action, attrs)
case CONVERT:
Expand Down Expand Up @@ -365,8 +392,8 @@ func getSourceAttributeValue(ctx context.Context, action attributeAction, attrs
return attrs.Get(action.FromAttribute)
}

func hashAttribute(action attributeAction, attrs pcommon.Map) {
if value, exists := attrs.Get(action.Key); exists {
func hashAttribute(key string, attrs pcommon.Map) {
if value, exists := attrs.Get(key); exists {
sha1Hasher(value)
}
}
Expand Down Expand Up @@ -398,3 +425,19 @@ func extractAttributes(action attributeAction, attrs pcommon.Map) {
attrs.UpsertString(action.AttrNames[i], matches[i])
}
}

func getMatchingKeys(regexp *regexp.Regexp, attrs pcommon.Map) []string {
keys := []string{}

if regexp == nil {
return keys
}

attrs.Range(func(k string, v pcommon.Value) bool {
if regexp.MatchString(k) {
keys = append(keys, k)
}
return true
})
return keys
}
85 changes: 75 additions & 10 deletions internal/coreinternal/attraction/attraction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,11 +519,26 @@ func TestAttributes_Delete(t *testing.T) {
"original_key": 3245.6,
},
},
// Ensure `duplicate_key` is deleted by regexp for spans with the attribute set.
{
name: "DeleteAttributeExists",
inputAttributes: map[string]interface{}{
"duplicate_key_a": pcommon.NewValueDouble(3245.6),
"duplicate_key_b": pcommon.NewValueDouble(3245.6),
"duplicate_key_c": pcommon.NewValueDouble(3245.6),
"original_key": pcommon.NewValueDouble(3245.6),
"not_duplicate_key": pcommon.NewValueDouble(3246.6),
},
expectedAttributes: map[string]interface{}{
"original_key": pcommon.NewValueDouble(3245.6),
"not_duplicate_key": pcommon.NewValueDouble(3246.6),
},
},
}

cfg := &Settings{
Actions: []ActionKeyValue{
{Key: "duplicate_key", Action: DELETE},
{Key: "duplicate_key", RegexPattern: "^duplicate_key_.", Action: DELETE},
},
}

Expand All @@ -536,8 +551,53 @@ func TestAttributes_Delete(t *testing.T) {
}
}

func TestAttributes_HashValue(t *testing.T) {
func TestAttributes_Delete_Regexp(t *testing.T) {
testCases := []testCase{
// Ensure the span contains no changes.
{
name: "DeleteEmptyAttributes",
inputAttributes: map[string]interface{}{},
expectedAttributes: map[string]interface{}{},
},
// Ensure the span contains no changes because the key doesn't exist.
{
name: "DeleteAttributeNoExist",
inputAttributes: map[string]interface{}{
"boo": pcommon.NewValueString("ghosts are scary"),
},
expectedAttributes: map[string]interface{}{
"boo": pcommon.NewValueString("ghosts are scary"),
},
},
// Ensure `duplicate_key` is deleted for spans with the attribute set.
{
name: "DeleteAttributeExists",
inputAttributes: map[string]interface{}{
"duplicate_key": pcommon.NewValueDouble(3245.6),
"original_key": pcommon.NewValueDouble(3245.6),
},
expectedAttributes: map[string]interface{}{
"original_key": pcommon.NewValueDouble(3245.6),
},
},
}

cfg := &Settings{
Actions: []ActionKeyValue{
{RegexPattern: "duplicate.*", Action: DELETE},
},
}

ap, err := NewAttrProc(cfg)
require.Nil(t, err)
require.NotNil(t, ap)

for _, tt := range testCases {
runIndividualTestCase(t, tt, ap)
}
}

func TestAttributes_HashValue(t *testing.T) {
intVal := int64(24)
intBytes := make([]byte, int64ByteSize)
binary.LittleEndian.PutUint64(intBytes, uint64(intVal))
Expand Down Expand Up @@ -613,11 +673,23 @@ func TestAttributes_HashValue(t *testing.T) {
"updateme": sha1Hash([]byte{0}),
},
},
// Ensure regex pattern is being used
{
name: "HashRegex",
inputAttributes: map[string]interface{}{
"updatemebyregexp": false,
"donotupdatemebyregexp": false,
},
expectedAttributes: map[string]interface{}{
"updatemebyregexp": sha1Hash([]byte{0}),
"donotupdatemebyregexp": false,
},
},
}

cfg := &Settings{
Actions: []ActionKeyValue{
{Key: "updateme", Action: HASH},
{Key: "updateme", RegexPattern: "^updatemeby.*", Action: HASH},
},
}

Expand Down Expand Up @@ -815,13 +887,6 @@ func TestInvalidConfig(t *testing.T) {
},
errorString: "error creating AttrProc. Field \"pattern\" has invalid pattern: \"(?P<invalid.regex>.*?)$\" to be set at the 0-th actions",
},
{
name: "delete with regex",
actionLists: []ActionKeyValue{
{RegexPattern: "(?P<operation_website>.*?)$", Key: "ab", Action: DELETE},
},
errorString: "error creating AttrProc. Action \"delete\" does not use value sources or \"pattern\" field. These must not be specified for 0-th action",
},
{
name: "regex with unnamed capture group",
actionLists: []ActionKeyValue{
Expand Down
8 changes: 6 additions & 2 deletions processor/attributesprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,26 @@ For the actions `insert`, `update` and `upsert`,
```

For the `delete` action,
- `key` is required
- `key` and/or `pattern` is required
- `action: delete` is required.
```yaml
# Key specifies the attribute to act upon.
- key: <key>
action: delete
# Rule specifies the regex pattern for attribute names to act upon.
pattern: <regular pattern>
```


For the `hash` action,
- `key` is required
- `key` and/or `pattern` is required
- `action: hash` is required.
```yaml
# Key specifies the attribute to act upon.
- key: <key>
action: hash
# Rule specifies the regex pattern for attribute names to act upon.
pattern: <regular pattern>
```


Expand Down
11 changes: 11 additions & 0 deletions unreleased/drosiek-delete-by-pattern.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 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: coreinternal/attraction

# A brief description of the change
note: Supports pattern for delete and hash attractions

# One or more tracking issues related to the change
issues: [11886]