Skip to content

Commit

Permalink
[internal/coreinternal/processor/filter*] adding filtering based on s…
Browse files Browse the repository at this point in the history
…pan 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 <pbaeyens31+github@gmail.com>

* added test for spankind config validation

* added comment

Co-authored-by: Pablo Baeyens <pbaeyens31+github@gmail.com>
  • Loading branch information
arun-shopify and mx-psi authored Sep 19, 2022
1 parent c06fda4 commit e043da8
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 11 deletions.
48 changes: 42 additions & 6 deletions internal/coreinternal/processor/filterconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand All @@ -142,23 +164,37 @@ 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
}

// 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
Expand Down
7 changes: 4 additions & 3 deletions internal/coreinternal/processor/filterlog/filterlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,22 @@ 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",
property: filterconfig.MatchProperties{
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",
Expand Down Expand Up @@ -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())
})
}
Expand Down
16 changes: 16 additions & 0 deletions internal/coreinternal/processor/filterspan/filterspan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
}

Expand Down
53 changes: 51 additions & 2 deletions internal/coreinternal/processor/filterspan/filterspan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
16 changes: 16 additions & 0 deletions unreleased/match_span_kind.yaml
Original file line number Diff line number Diff line change
@@ -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:

0 comments on commit e043da8

Please sign in to comment.