From 7397254060c0d114f4517ebacb8b7d243c82f965 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 7 May 2020 10:37:20 +0200 Subject: [PATCH] match spans on instrumentation library --- internal/processor/filterspan/config.go | 15 +++ internal/processor/filterspan/filterspan.go | 116 +++++++++++++++++- .../processor/filterspan/filterspan_test.go | 27 +++- processor/attributesprocessor/attributes.go | 7 +- processor/spanprocessor/span.go | 3 +- 5 files changed, 154 insertions(+), 14 deletions(-) diff --git a/internal/processor/filterspan/config.go b/internal/processor/filterspan/config.go index 90b64029735..52157a8e24e 100644 --- a/internal/processor/filterspan/config.go +++ b/internal/processor/filterspan/config.go @@ -73,6 +73,8 @@ type MatchProperties struct { // This is an optional field. Services []string `mapstructure:"services"` + Libraries []InstrumentationLibrary `mapstructure:"libraries"` + // SpanNames specify the list of items to match span name against. // A match occurs if the span name matches at least one item in this list. // This is an optional field. @@ -108,3 +110,16 @@ type Attribute struct { // If it is not set, any value will match. Value interface{} `mapstructure:"value"` } + +// version match +// expected actual match +// nil yes +// nil 1 yes +// yes +// 1 no +// 1 no +// 1 1 yes +type InstrumentationLibrary struct { + Name string `mapstructure:"name"` + Version *string `mapstructure:"version"` +} diff --git a/internal/processor/filterspan/filterspan.go b/internal/processor/filterspan/filterspan.go index 85453391ef3..c8380cdd22e 100644 --- a/internal/processor/filterspan/filterspan.go +++ b/internal/processor/filterspan/filterspan.go @@ -38,7 +38,7 @@ var ( // Matcher is an interface that allows matching a span against a configuration // of a match. type Matcher interface { - MatchSpan(span pdata.Span, serviceName string) bool + MatchSpan(span pdata.Span, serviceName string, library pdata.InstrumentationLibrary) bool } type attributesMatcher []attributeMatcher @@ -49,6 +49,8 @@ type strictPropertiesMatcher struct { // Service names to compare to. Services []string + Libraries []strictInstrumentationLibrary + // Span names to compare to. SpanNames []string @@ -56,12 +58,19 @@ type strictPropertiesMatcher struct { Attributes attributesMatcher } +type strictInstrumentationLibrary struct { + Name string + Version *string +} + // regexpPropertiesMatcher allows matching a span against a "regexp" match type // configuration. type regexpPropertiesMatcher struct { // Precompiled service name regexp-es. Services []*regexp.Regexp + Libraries []regexpInstrumentationLibrary + // Precompiled span name regexp-es. SpanNames []*regexp.Regexp @@ -69,6 +78,11 @@ type regexpPropertiesMatcher struct { Attributes attributesMatcher } +type regexpInstrumentationLibrary struct { + Name *regexp.Regexp + Version *regexp.Regexp +} + // attributeMatcher is a attribute key/value pair to match to. type attributeMatcher struct { Key string @@ -108,6 +122,13 @@ func newStrictPropertiesMatcher(config *MatchProperties) (*strictPropertiesMatch SpanNames: config.SpanNames, } + for _, library := range config.Libraries { + properties.Libraries = append(properties.Libraries, strictInstrumentationLibrary{ + Name: library.Name, + Version: library.Version, + }) + } + var err error properties.Attributes, err = newAttributesMatcher(config) if err != nil { @@ -132,6 +153,34 @@ func newRegexpPropertiesMatcher(config *MatchProperties) (*regexpPropertiesMatch properties.Services = append(properties.Services, g) } + // Precompile Library regexp patterns. + for _, library := range config.Libraries { + n, err := regexp.Compile(library.Name) + if err != nil { + return nil, fmt.Errorf( + "error creating processor. %s is not a valid library name regexp pattern", + library.Name, + ) + } + + l := regexpInstrumentationLibrary{ + Name: n, + } + + if library.Version != nil { + v, err := regexp.Compile(*library.Version) + if err != nil { + return nil, fmt.Errorf( + "error creating processor. %s is not a valid library version regexp pattern", + *library.Version, + ) + } + l.Version = v + } + + properties.Libraries = append(properties.Libraries, l) + } + // Precompile SpanNames regexp patterns. for _, pattern := range config.SpanNames { g, err := regexp.Compile(pattern) @@ -185,17 +234,17 @@ func newAttributesMatcher(config *MatchProperties) (attributesMatcher, error) { // The logic determining if a span should be processed is set // in the attribute configuration with the include and exclude settings. // Include properties are checked before exclude settings are checked. -func SkipSpan(include Matcher, exclude Matcher, span pdata.Span, serviceName string) bool { +func SkipSpan(include Matcher, exclude Matcher, span pdata.Span, serviceName string, library pdata.InstrumentationLibrary) bool { if include != nil { // A false returned in this case means the span should not be processed. - if i := include.MatchSpan(span, serviceName); !i { + if i := include.MatchSpan(span, serviceName, library); !i { return true } } if exclude != nil { // A true returned in this case means the span should not be processed. - if e := exclude.MatchSpan(span, serviceName); e { + if e := exclude.MatchSpan(span, serviceName, library); e { return true } } @@ -210,7 +259,21 @@ func SkipSpan(include Matcher, exclude Matcher, span pdata.Span, serviceName str // At least one of services, span names or attributes must be specified. It is supported // to have more than one of these specified, and all specified must evaluate // to true for a match to occur. -func (mp *strictPropertiesMatcher) MatchSpan(span pdata.Span, serviceName string) bool { +func (mp *strictPropertiesMatcher) MatchSpan(span pdata.Span, serviceName string, library pdata.InstrumentationLibrary) bool { + if len(mp.Libraries) > 0 { + // Verify library name matches at least one of the items. + matched := false + for _, item := range mp.Libraries { + if item.match(library) { + matched = true + break + } + } + if !matched { + return false + } + } + if len(mp.Services) > 0 { // Verify service name matches at least one of the items. matched := false @@ -246,6 +309,20 @@ func (mp *strictPropertiesMatcher) MatchSpan(span pdata.Span, serviceName string return mp.Attributes.match(span) } +func (sil *strictInstrumentationLibrary) match(library pdata.InstrumentationLibrary) bool { + if library.IsNil() { + return true + } + + if sil.Version != nil { + if *sil.Version != library.Version() { + return false + } + } + + return sil.Name == library.Name() +} + // MatchSpan matches a span and service to a set of properties. // There are 3 sets of properties to match against. // The service name is checked first, if specified. Then span names are matched, if specified. @@ -253,7 +330,20 @@ func (mp *strictPropertiesMatcher) MatchSpan(span pdata.Span, serviceName string // At least one of services, span names or attributes must be specified. It is supported // to have more than one of these specified, and all specified must evaluate // to true for a match to occur. -func (mp *regexpPropertiesMatcher) MatchSpan(span pdata.Span, serviceName string) bool { +func (mp *regexpPropertiesMatcher) MatchSpan(span pdata.Span, serviceName string, library pdata.InstrumentationLibrary) bool { + if len(mp.Libraries) > 0 { + // Verify library name matches at least one of the items. + matched := false + for _, item := range mp.Libraries { + if item.match(library) { + matched = true + break + } + } + if !matched { + return false + } + } if len(mp.Services) > 0 { // Verify service name matches at least one of the regexp patterns. @@ -290,6 +380,20 @@ func (mp *regexpPropertiesMatcher) MatchSpan(span pdata.Span, serviceName string return mp.Attributes.match(span) } +func (ril *regexpInstrumentationLibrary) match(library pdata.InstrumentationLibrary) bool { + if library.IsNil() { + return true + } + + if ril.Version != nil { + if !ril.Version.MatchString(library.Version()) { + return false + } + } + + return ril.Name.MatchString(library.Name()) +} + // match attributes specification against a span. func (ma attributesMatcher) match(span pdata.Span) bool { // If there are no attributes to match against, the span matches. diff --git a/internal/processor/filterspan/filterspan_test.go b/internal/processor/filterspan/filterspan_test.go index c3dc30e9145..4c39ea981f5 100644 --- a/internal/processor/filterspan/filterspan_test.go +++ b/internal/processor/filterspan/filterspan_test.go @@ -193,7 +193,7 @@ func TestSpan_Matching_False(t *testing.T) { span.Attributes().InitFromMap(map[string]pdata.AttributeValue{"keyInt": pdata.NewAttributeValueInt(123)}) for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - assert.False(t, tc.properties.MatchSpan(span, "wrongSvc")) + assert.False(t, tc.properties.MatchSpan(span, "wrongSvc", pdata.NewInstrumentationLibrary())) }) } } @@ -210,7 +210,7 @@ func TestSpan_MatchingCornerCases(t *testing.T) { } emptySpan := pdata.NewSpan() emptySpan.InitEmpty() - assert.False(t, mp.MatchSpan(emptySpan, "svcA")) + assert.False(t, mp.MatchSpan(emptySpan, "svcA", pdata.NewInstrumentationLibrary())) } func TestSpan_MissingServiceName(t *testing.T) { @@ -220,10 +220,12 @@ func TestSpan_MissingServiceName(t *testing.T) { emptySpan := pdata.NewSpan() emptySpan.InitEmpty() - assert.False(t, mp.MatchSpan(emptySpan, "")) + assert.False(t, mp.MatchSpan(emptySpan, "", pdata.NewInstrumentationLibrary())) } func TestSpan_Matching_True(t *testing.T) { + ver := "ver" + testcases := []struct { name string properties Matcher @@ -249,6 +251,18 @@ func TestSpan_Matching_True(t *testing.T) { Attributes: []attributeMatcher{}, }, }, + { + name: "library_match_regexp", + properties: ®expPropertiesMatcher{ + Libraries: []regexpInstrumentationLibrary{{regexp.MustCompile("li.*"), regexp.MustCompile("v.*")}}, + }, + }, + { + name: "library_match_strict", + properties: &strictPropertiesMatcher{ + Libraries: []strictInstrumentationLibrary{{"lib", &ver}}, + }, + }, { name: "span_name_match", properties: ®expPropertiesMatcher{ @@ -335,7 +349,12 @@ func TestSpan_Matching_True(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - assert.True(t, tc.properties.MatchSpan(span, "svcA")) + library := pdata.NewInstrumentationLibrary() + library.InitEmpty() + library.SetName("lib") + library.SetVersion("ver") + assert.True(t, tc.properties.MatchSpan(span, "svcA", + library)) }) } diff --git a/processor/attributesprocessor/attributes.go b/processor/attributesprocessor/attributes.go index 4c19865cb75..90da8edf88a 100644 --- a/processor/attributesprocessor/attributes.go +++ b/processor/attributesprocessor/attributes.go @@ -79,8 +79,9 @@ func (a *attributesProcessor) ConsumeTraces(ctx context.Context, td pdata.Traces continue } spans := ils.Spans() + library := ils.InstrumentationLibrary() for k := 0; k < spans.Len(); k++ { - a.processSpan(spans.At(k), serviceName) + a.processSpan(spans.At(k), serviceName, library) } } } @@ -101,13 +102,13 @@ func (a *attributesProcessor) Shutdown(context.Context) error { return nil } -func (a *attributesProcessor) processSpan(span pdata.Span, serviceName string) { +func (a *attributesProcessor) processSpan(span pdata.Span, serviceName string, library pdata.InstrumentationLibrary) { if span.IsNil() { // Do not create empty spans just to add attributes return } - if filterspan.SkipSpan(a.config.include, a.config.exclude, span, serviceName) { + if filterspan.SkipSpan(a.config.include, a.config.exclude, span, serviceName, library) { return } diff --git a/processor/spanprocessor/span.go b/processor/spanprocessor/span.go index 931a6cd1bc5..b061c38c345 100644 --- a/processor/spanprocessor/span.go +++ b/processor/spanprocessor/span.go @@ -106,13 +106,14 @@ func (sp *spanProcessor) ConsumeTraces(ctx context.Context, td pdata.Traces) err continue } spans := ils.Spans() + library := ils.InstrumentationLibrary() for k := 0; k < spans.Len(); k++ { s := spans.At(k) if s.IsNil() { continue } - if filterspan.SkipSpan(sp.include, sp.exclude, s, serviceName) { + if filterspan.SkipSpan(sp.include, sp.exclude, s, serviceName, library) { continue } sp.processFromAttributes(s)