Skip to content

Commit

Permalink
feat(coreinternal/attraction): supports pattern for delete and hash a…
Browse files Browse the repository at this point in the history
…ctions

Signed-off-by: Dominik Rosiek <[email protected]>
  • Loading branch information
Dominik Rosiek committed Jun 7, 2022
1 parent 1c7fa84 commit 0adfea0
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 22 deletions.
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 @@ -57,22 +57,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

0 comments on commit 0adfea0

Please sign in to comment.