Skip to content

Commit

Permalink
match spans on instrumentation library
Browse files Browse the repository at this point in the history
  • Loading branch information
zeitlinger committed May 7, 2020
1 parent 7482f90 commit 7397254
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 14 deletions.
15 changes: 15 additions & 0 deletions internal/processor/filterspan/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 <blank> yes
// nil 1 yes
// <blank> <blank> yes
// <blank> 1 no
// 1 <blank> no
// 1 1 yes
type InstrumentationLibrary struct {
Name string `mapstructure:"name"`
Version *string `mapstructure:"version"`
}
116 changes: 110 additions & 6 deletions internal/processor/filterspan/filterspan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -49,26 +49,40 @@ type strictPropertiesMatcher struct {
// Service names to compare to.
Services []string

Libraries []strictInstrumentationLibrary

// Span names to compare to.
SpanNames []string

// The attribute values are stored in the internal format.
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

// The attribute values are stored in the internal format.
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
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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
Expand Down Expand Up @@ -246,14 +309,41 @@ 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.
// The attributes are checked last, if specified.
// 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.
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 23 additions & 4 deletions internal/processor/filterspan/filterspan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
})
}
}
Expand All @@ -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) {
Expand All @@ -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
Expand All @@ -249,6 +251,18 @@ func TestSpan_Matching_True(t *testing.T) {
Attributes: []attributeMatcher{},
},
},
{
name: "library_match_regexp",
properties: &regexpPropertiesMatcher{
Libraries: []regexpInstrumentationLibrary{{regexp.MustCompile("li.*"), regexp.MustCompile("v.*")}},
},
},
{
name: "library_match_strict",
properties: &strictPropertiesMatcher{
Libraries: []strictInstrumentationLibrary{{"lib", &ver}},
},
},
{
name: "span_name_match",
properties: &regexpPropertiesMatcher{
Expand Down Expand Up @@ -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))

})
}
Expand Down
7 changes: 4 additions & 3 deletions processor/attributesprocessor/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand All @@ -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
}

Expand Down
3 changes: 2 additions & 1 deletion processor/spanprocessor/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 7397254

Please sign in to comment.