From e043da8ff3d4a28ed33a11584e625a5404a6049a Mon Sep 17 00:00:00 2001 From: Arun Mahendra <89400134+arun-shopify@users.noreply.github.com> Date: Mon, 19 Sep 2022 04:40:52 -0400 Subject: [PATCH] [internal/coreinternal/processor/filter*] adding filtering based on span kinds (#13612) * adding filtering based on span kinds * fixed tests * unreleased yaml * span kind validation * Update internal/coreinternal/processor/filterconfig/config.go Co-authored-by: Pablo Baeyens * added test for spankind config validation * added comment Co-authored-by: Pablo Baeyens --- .../processor/filterconfig/config.go | 48 ++++++++++++++--- .../processor/filterlog/filterlog_test.go | 7 +-- .../processor/filterspan/filterspan.go | 16 ++++++ .../processor/filterspan/filterspan_test.go | 53 ++++++++++++++++++- unreleased/match_span_kind.yaml | 16 ++++++ 5 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 unreleased/match_span_kind.yaml diff --git a/internal/coreinternal/processor/filterconfig/config.go b/internal/coreinternal/processor/filterconfig/config.go index 1e930c612a29..0ecbc3cec079 100644 --- a/internal/coreinternal/processor/filterconfig/config.go +++ b/internal/coreinternal/processor/filterconfig/config.go @@ -16,8 +16,11 @@ package filterconfig // import "github.com/open-telemetry/opentelemetry-collecto import ( "errors" + "fmt" + "sort" "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/pdata/ptrace" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/processor/filterset" ) @@ -125,8 +128,27 @@ type MatchProperties struct { // A match occurs if the span's implementation library matches at least one item in this list. // This is an optional field. Libraries []InstrumentationLibrary `mapstructure:"libraries"` + + // SpanKinds specify the list of items to match the span kind against. + // A match occurs if the span's span kind matches at least one item in this list. + // This is an optional field + SpanKinds []string `mapstructure:"span_kinds"` } +var ( + ErrMissingRequiredField = errors.New(`at least one of "attributes", "libraries", or "resources" field must be specified`) + ErrInvalidLogField = errors.New("services, span_names, and span_kinds are not valid for log records") + ErrMissingRequiredLogField = errors.New(`at least one of "attributes", "libraries", "span_kinds", "resources", "log_bodies", "log_severity_texts" or "log_severity_number" field must be specified`) + + spanKinds = map[string]bool{ + ptrace.SpanKindInternal.String(): true, + ptrace.SpanKindClient.String(): true, + ptrace.SpanKindServer.String(): true, + ptrace.SpanKindConsumer.String(): true, + ptrace.SpanKindProducer.String(): true, + } +) + // ValidateForSpans validates properties for spans. func (mp *MatchProperties) ValidateForSpans() error { if len(mp.LogBodies) > 0 { @@ -142,8 +164,21 @@ func (mp *MatchProperties) ValidateForSpans() error { } if len(mp.Services) == 0 && len(mp.SpanNames) == 0 && len(mp.Attributes) == 0 && - len(mp.Libraries) == 0 && len(mp.Resources) == 0 { - return errors.New(`at least one of "services", "span_names", "attributes", "libraries" or "resources" field must be specified`) + len(mp.Libraries) == 0 && len(mp.Resources) == 0 && len(mp.SpanKinds) == 0 { + return ErrMissingRequiredField + } + + if len(mp.SpanKinds) > 0 && mp.MatchType == "strict" { + for _, kind := range mp.SpanKinds { + if !spanKinds[kind] { + validSpanKinds := make([]string, len(spanKinds)) + for k := range spanKinds { + validSpanKinds = append(validSpanKinds, k) + } + sort.Strings(validSpanKinds) + return fmt.Errorf("span_kinds string must match one of the standard span kinds when match_type=strict: %v", validSpanKinds) + } + } } return nil @@ -151,14 +186,15 @@ func (mp *MatchProperties) ValidateForSpans() error { // ValidateForLogs validates properties for logs. func (mp *MatchProperties) ValidateForLogs() error { - if len(mp.SpanNames) > 0 || len(mp.Services) > 0 { - return errors.New("neither services nor span_names should be specified for log records") + if len(mp.SpanNames) > 0 || len(mp.Services) > 0 || len(mp.SpanKinds) > 0 { + return ErrInvalidLogField } if len(mp.Attributes) == 0 && len(mp.Libraries) == 0 && len(mp.Resources) == 0 && len(mp.LogBodies) == 0 && - len(mp.LogSeverityTexts) == 0 && mp.LogSeverityNumber == nil { - return errors.New(`at least one of "attributes", "libraries", "resources", "log_bodies", "log_severity_texts" or "log_severity_number" field must be specified`) + len(mp.LogSeverityTexts) == 0 && mp.LogSeverityNumber == nil && + len(mp.SpanKinds) == 0 { + return ErrMissingRequiredLogField } return nil diff --git a/internal/coreinternal/processor/filterlog/filterlog_test.go b/internal/coreinternal/processor/filterlog/filterlog_test.go index 6c851b65e6b1..4062e1ad4f37 100644 --- a/internal/coreinternal/processor/filterlog/filterlog_test.go +++ b/internal/coreinternal/processor/filterlog/filterlog_test.go @@ -41,7 +41,7 @@ func TestLogRecord_validateMatchesConfiguration_InvalidConfig(t *testing.T) { { name: "empty_property", property: filterconfig.MatchProperties{}, - errorString: `at least one of "attributes", "libraries", "resources", "log_bodies", "log_severity_texts" or "log_severity_number" field must be specified`, + errorString: filterconfig.ErrMissingRequiredLogField.Error(), }, { name: "empty_log_bodies_and_attributes", @@ -49,14 +49,14 @@ func TestLogRecord_validateMatchesConfiguration_InvalidConfig(t *testing.T) { LogBodies: []string{}, LogSeverityTexts: []string{}, }, - errorString: `at least one of "attributes", "libraries", "resources", "log_bodies", "log_severity_texts" or "log_severity_number" field must be specified`, + errorString: filterconfig.ErrMissingRequiredLogField.Error(), }, { name: "span_properties", property: filterconfig.MatchProperties{ SpanNames: []string{"span"}, }, - errorString: "neither services nor span_names should be specified for log records", + errorString: filterconfig.ErrInvalidLogField.Error(), }, { name: "invalid_match_type", @@ -87,6 +87,7 @@ func TestLogRecord_validateMatchesConfiguration_InvalidConfig(t *testing.T) { output, err := NewMatcher(&tc.property) assert.Nil(t, output) require.NotNil(t, err) + println(tc.name) assert.Equal(t, tc.errorString, err.Error()) }) } diff --git a/internal/coreinternal/processor/filterspan/filterspan.go b/internal/coreinternal/processor/filterspan/filterspan.go index fac30a376b2e..871fff7787ea 100644 --- a/internal/coreinternal/processor/filterspan/filterspan.go +++ b/internal/coreinternal/processor/filterspan/filterspan.go @@ -44,6 +44,9 @@ type propertiesMatcher struct { // Span names to compare to. nameFilters filterset.FilterSet + + // Span kinds to compare to + kindFilters filterset.FilterSet } // NewMatcher creates a span Matcher that matches based on the given MatchProperties. @@ -77,10 +80,19 @@ func NewMatcher(mp *filterconfig.MatchProperties) (Matcher, error) { } } + var kindFS filterset.FilterSet + if len(mp.SpanKinds) > 0 { + kindFS, err = filterset.CreateFilterSet(mp.SpanKinds, &mp.Config) + if err != nil { + return nil, fmt.Errorf("error creating span kind filters: %w", err) + } + } + return &propertiesMatcher{ PropertiesMatcher: rm, serviceFilters: serviceFS, nameFilters: nameFS, + kindFilters: kindFS, }, nil } @@ -125,6 +137,10 @@ func (mp *propertiesMatcher) MatchSpan(span ptrace.Span, resource pcommon.Resour return false } + if mp.kindFilters != nil && !mp.kindFilters.Matches(span.Kind().String()) { + return false + } + return mp.PropertiesMatcher.Match(span.Attributes(), resource, library) } diff --git a/internal/coreinternal/processor/filterspan/filterspan_test.go b/internal/coreinternal/processor/filterspan/filterspan_test.go index d1188530eb35..3d065fd35513 100644 --- a/internal/coreinternal/processor/filterspan/filterspan_test.go +++ b/internal/coreinternal/processor/filterspan/filterspan_test.go @@ -43,14 +43,14 @@ func TestSpan_validateMatchesConfiguration_InvalidConfig(t *testing.T) { { name: "empty_property", property: filterconfig.MatchProperties{}, - errorString: "at least one of \"services\", \"span_names\", \"attributes\", \"libraries\" or \"resources\" field must be specified", + errorString: filterconfig.ErrMissingRequiredField.Error(), }, { name: "empty_service_span_names_and_attributes", property: filterconfig.MatchProperties{ Services: []string{}, }, - errorString: "at least one of \"services\", \"span_names\", \"attributes\", \"libraries\" or \"resources\" field must be specified", + errorString: filterconfig.ErrMissingRequiredField.Error(), }, { name: "log_properties", @@ -90,6 +90,17 @@ func TestSpan_validateMatchesConfiguration_InvalidConfig(t *testing.T) { }, errorString: "error creating span name filters: error parsing regexp: missing closing ]: `[`", }, + { + name: "invalid_strict_span_kind_match", + property: filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + SpanKinds: []string{ + "test_invalid_span_kind", + }, + Attributes: []filterconfig.Attribute{}, + }, + errorString: "span_kinds string must match one of the standard span kinds when match_type=strict: [ SPAN_KIND_CLIENT SPAN_KIND_CONSUMER SPAN_KIND_INTERNAL SPAN_KIND_PRODUCER SPAN_KIND_SERVER]", + }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { @@ -144,6 +155,22 @@ func TestSpan_Matching_False(t *testing.T) { Attributes: []filterconfig.Attribute{}, }, }, + { + name: "span_kind_doesnt_match_regexp", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + Attributes: []filterconfig.Attribute{}, + SpanKinds: []string{ptrace.SpanKindProducer.String()}, + }, + }, + { + name: "span_kind_doesnt_match_strict", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + Attributes: []filterconfig.Attribute{}, + SpanKinds: []string{ptrace.SpanKindProducer.String()}, + }, + }, } span := ptrace.NewSpan() @@ -218,15 +245,37 @@ func TestSpan_Matching_True(t *testing.T) { Attributes: []filterconfig.Attribute{}, }, }, + { + name: "span_kind_match_strict", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + SpanKinds: []string{ + ptrace.SpanKindClient.String(), + }, + Attributes: []filterconfig.Attribute{}, + }, + }, + { + name: "span_kind_match_regexp", + properties: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + SpanKinds: []string{ + "CLIENT", + }, + Attributes: []filterconfig.Attribute{}, + }, + }, } span := ptrace.NewSpan() span.SetName("spanName") + span.Attributes().PutString("keyString", "arithmetic") span.Attributes().PutInt("keyInt", 123) span.Attributes().PutDouble("keyDouble", 3245.6) span.Attributes().PutBool("keyBool", true) span.Attributes().PutString("keyExists", "present") + span.SetKind(ptrace.SpanKindClient) assert.NotNil(t, span) resource := pcommon.NewResource() diff --git a/unreleased/match_span_kind.yaml b/unreleased/match_span_kind.yaml new file mode 100644 index 000000000000..f1e29da91dde --- /dev/null +++ b/unreleased/match_span_kind.yaml @@ -0,0 +1,16 @@ +# 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: filterspan + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add span kind filtering. + +# One or more tracking issues related to the change +issues: [13612] + +# (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: