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

adding filtering based on span kinds #13612

Merged
merged 7 commits into from
Sep 19, 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
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: