diff --git a/model/modelprocessor/groupingkey.go b/model/modelprocessor/groupingkey.go index 82b3af46..173f0513 100644 --- a/model/modelprocessor/groupingkey.go +++ b/model/modelprocessor/groupingkey.go @@ -56,7 +56,11 @@ func (s SetGroupingKey) processError(ctx context.Context, event *modelpb.Error) } var haveExceptionStacktrace bool if event.Exception != nil { - haveExceptionStacktrace = s.hashExceptionTree(event.Exception, hash, s.hashExceptionStacktrace) + if s.exceptionTreeHasStackTrace(event.Exception) { + haveExceptionStacktrace = s.hashExceptionTree(event.Exception, hash, s.hashExceptionStacktrace) + } else { + haveExceptionStacktrace = s.hashExceptionTree(event.Exception, hash, s.hashExceptionMessage) + } updated = updated || haveExceptionStacktrace } if !haveExceptionStacktrace && event.Log != nil { @@ -71,10 +75,27 @@ func (s SetGroupingKey) processError(ctx context.Context, event *modelpb.Error) if !updated && event.Log != nil { s.maybeWriteString(event.Log.Message, hash) } + // if all else false hash over stacktrace + if !updated { + s.maybeWriteString(event.StackTrace, hash) + } } event.GroupingKey = hex.EncodeToString(hash.Sum(nil)) } +func (s SetGroupingKey) exceptionTreeHasStackTrace(e *modelpb.Exception) bool { + updated := e.Stacktrace != nil + if updated { + return updated + } + for _, cause := range e.Cause { + if s.exceptionTreeHasStackTrace(cause) { + return true + } + } + return false +} + func (s SetGroupingKey) hashExceptionTree(e *modelpb.Exception, out hash.Hash, f func(*modelpb.Exception, hash.Hash) bool) bool { updated := f(e, out) for _, cause := range e.Cause { diff --git a/model/modelprocessor/groupingkey_test.go b/model/modelprocessor/groupingkey_test.go index 9a12c070..54261508 100644 --- a/model/modelprocessor/groupingkey_test.go +++ b/model/modelprocessor/groupingkey_test.go @@ -30,6 +30,9 @@ import ( ) func TestSetGroupingKey(t *testing.T) { + stackTrace1 := "System.Exception: outer 1 ---> System.Exception: message\n at Program.Main() in d:\\Windows\\Temp\\vv05tcpr.0.cs:line 11\n --- End of inner exception stack trace ---\n at Program.Main() in d:\\Windows\\Temp\\vv05tcpr.0.cs:line 15" + stackTrace2 := "System.Exception: outer 2 ---> System.Exception: message\n at Program.Main() in d:\\Windows\\Temp\\vv05tcpr.0.cs:line 11\n --- End of inner exception stack trace ---\n at Program.Main() in d:\\Windows\\Temp\\vv05tcpr.0.cs:line 15" + tests := map[string]struct { input *modelpb.Error groupingKey string @@ -103,14 +106,48 @@ func TestSetGroupingKey(t *testing.T) { }, groupingKey: hashStrings("message_1", "message_2", "message_3", "message_4"), }, - "log_message": { + "exception_message_types": { input: &modelpb.Error{ + Exception: &modelpb.Exception{ + Message: "message_1", + Type: "type_1", + Cause: []*modelpb.Exception{{ + Message: "message_2", + Type: "type_2", + Cause: []*modelpb.Exception{ + {Message: "message_3", Type: "type_3"}, + }, + }, { + Message: "message_4", + Type: "type_4", + }}, + }, Log: &modelpb.ErrorLog{Message: "log_message"}, // ignored }, + groupingKey: hashStrings("type_1", "type_2", "type_3", "type_4", "message_1", "message_2", "message_3", "message_4"), + }, + "log_message": { + input: &modelpb.Error{ + Log: &modelpb.ErrorLog{Message: "log_message"}, + }, groupingKey: hashStrings("log_message"), }, + "error_stack_trace_string": { + input: &modelpb.Error{ + StackTrace: stackTrace1, + }, + groupingKey: hashStrings(stackTrace1), + }, + "error_stack_trace_string_different_message": { + input: &modelpb.Error{ + StackTrace: stackTrace2, + }, + groupingKey: hashStrings(stackTrace2), + }, + // } + groupingKeys := []string{} for name, test := range tests { t.Run(name, func(t *testing.T) { batch := modelpb.Batch{{ @@ -118,11 +155,18 @@ func TestSetGroupingKey(t *testing.T) { }} processor := modelprocessor.SetGroupingKey{} err := processor.ProcessBatch(context.Background(), &batch) + groupingKeys = append(groupingKeys, batch[0].Error.GroupingKey) assert.NoError(t, err) assert.Equal(t, test.groupingKey, batch[0].Error.GroupingKey) }) } + t.Run("grouping_keys_are_unique", func(t *testing.T) { + uniqueKeys := uniqueNonEmptyElementsOf(groupingKeys) + assert.Greater(t, len(uniqueKeys), 0) + assert.Len(t, uniqueKeys, len(groupingKeys)) + }) + } func hashStrings(s ...string) string { @@ -132,3 +176,19 @@ func hashStrings(s ...string) string { } return hex.EncodeToString(md5.Sum(nil)) } + +func uniqueNonEmptyElementsOf(s []string) []string { + unique := make(map[string]bool, len(s)) + us := make([]string, len(unique)) + for _, elem := range s { + if len(elem) != 0 { + if !unique[elem] { + us = append(us, elem) + unique[elem] = true + } + } + } + + return us + +}