diff --git a/processor/cascadingfilterprocessor/README.md b/processor/cascadingfilterprocessor/README.md index 8cefa3ea81c5..a75e5f4d9161 100644 --- a/processor/cascadingfilterprocessor/README.md +++ b/processor/cascadingfilterprocessor/README.md @@ -47,12 +47,15 @@ filtering or additional policy evaluation. This typically happens e.g. when heal Each of the specified drop rules has several properties: - `name` (required): identifies the rule -- `numeric_attribute: {key: , min_value: , max_value: }`: selects span by matching numeric - attribute (either at resource of span level) -- `string_attribute: {key: , values: [, ]}`: selects span by matching string attribute that is one - of the provided values (either at resource of span level); when `use_regex` (`false` by default) is set to `true` - the provided collection of values is evaluated as regular expressions - `name_pattern: `: selects the span if its operation name matches the provided regular expression +- `attributes: `: list of attribute-level filters (both span level and resource level is being evaluated). + When several elements are specified, conditions for each of them must be met. Each entry might contain a number of fields: + - `key: `: name of the attribute key + - `values: [, value2>]` (default=`empty`): list of string values, when present at least + one of them must be matched + - `use_regex: ` (default=`false`): indication whether values provided should be treated as regular expressions + - `ranges: [{min_value: , max_value: }]` (default=`empty`): list of numeric ranges; when present at least + one must be matched ## Accepted trace configuration @@ -64,17 +67,25 @@ it selects the traces only if the global limit is not exceeded by other policies Additionally, each of the policy might have any of the following filtering criteria defined. They are evaluated for each of the trace spans. If at least one span matching all defined criteria is found, the trace is selected: -- `numeric_attribute: {key: , min_value: , max_value: }`: selects span by matching numeric -attribute (either at resource of span level) -- `string_attribute: {key: , values: [, ], use_regex: }`: selects span by matching string attribute that is one -of the provided values (either at resource of span level); when `use_regex` (`false` by default) is set to `true` -the provided collection of values is evaluated as regular expressions +- `attributes: `: list of attribute-level filters (both span level and resource level is being evaluated). +When several elements are specified, conditions for each of them must be met. Each entry might contain a number of fields: + - `key: `: name of the attribute key + - `values: [, value2>]` (default=`empty`): list of string values, when present at least + one of them must be matched + - `use_regex: ` (default=`false`): indication whether values provided should be treated as regular expressions + - `ranges: [{min_value: , max_value: }]` (default=`empty`): list of numeric ranges; when present at least + one must be matched - `properties: { min_number_of_errors: }`: selects the trace if it has at least provided number of errors (determined based on the span status field value) - `properties: { min_number_of_spans: }`: selects the trace if it has at least provided number of spans - `properties: { min_duration: }`: selects the span if the duration is greater or equal the given value (use `s` or `ms` as the suffix to indicate unit) - `properties: { name_pattern: `}: selects the span if its operation name matches the provided regular expression +- _(deprecated)_ `numeric_attribute: {key: , min_value: , max_value: }`: selects span by matching numeric + attribute (either at resource of span level) +- _(deprecated)_ `string_attribute: {key: , values: [, ], use_regex: }`: selects span by matching string attribute that is one + of the provided values (either at resource of span level); when `use_regex` (`false` by default) is set to `true` + the provided collection of values is evaluated as regular expressions To invert the decision (which is still a subject to rate limiting), additional property can be configured: - `invert_match: ` (default=`false`): when set to `true`, the opposite decision is selected for the trace. E.g. @@ -115,7 +126,11 @@ processors: - name: remove-all-traces-with-health-span name_pattern: "health.*" - name: remove-all-traces-with-healthcheck-service - string_attribute: {key: service.name, values: ["healthcheck/.*"], use_regex: true} + attributes: + - key: service.name + values: + - "healthcheck/.*" + use_regex: true ``` ### Filtering out healhtchecks and traffic shaping @@ -135,7 +150,11 @@ cascadingfilter: - name: remove-all-traces-with-health-span name_pattern: "health.*" - name: remove-all-traces-with-healthcheck-service - string_attribute: {key: service.name, values: [healthcheck]} + attributes: + - key: service.name + values: + - "healthcheck/.*" + use_regex: true trace_accept_filters: - name: tail-based-duration properties: @@ -164,8 +183,11 @@ cascadingfilter: - name: remove-all-traces-with-health-span name_pattern: "health.*" - name: remove-all-traces-with-healthcheck-service - string_attribute: {key: service.name, values: [healthcheck.*]} - use_regex: true + attributes: + - key: service.name + values: + - "healthcheck/.*" + use_regex: true trace_accept_filters: - name: tail-based-duration properties: @@ -180,8 +202,16 @@ cascadingfilter: name_pattern: "foo.*" min_duration: 10s spans_per_second: 1000 # <- adjust the output traffic level - - name: traces-with-some-attribute - string_attribute: {key: important-key, values: [value1, value2]} + - name: some-service-traces-with-some-attribute + attributes: + - key: service.name + values: + - some-service + - key: important-key + values: + - value1 + - value2 + use_regex: true spans_per_second: 300 # <- adjust the output traffic level - name: everything_else spans_per_second: -1 # If there's anything left in the budget, it will randomly select remaining traces diff --git a/processor/cascadingfilterprocessor/config/config.go b/processor/cascadingfilterprocessor/config/config.go index f4f939d905f2..31f7c7fc3d1d 100644 --- a/processor/cascadingfilterprocessor/config/config.go +++ b/processor/cascadingfilterprocessor/config/config.go @@ -28,6 +28,8 @@ type TraceAcceptCfg struct { NumericAttributeCfg *NumericAttributeCfg `mapstructure:"numeric_attribute"` // Configs for string attribute filter sampling policy evaluator. StringAttributeCfg *StringAttributeCfg `mapstructure:"string_attribute"` + // AttributesCfg keeps generic string/numeric attributes for multiple keys + AttributeCfg []AttributeCfg `mapstructure:"attributes"` // Configs for properties sampling policy evaluator. PropertiesCfg PropertiesCfg `mapstructure:"properties"` // SpansPerSecond specifies the rule budget that should never be exceeded for it @@ -70,6 +72,24 @@ type StringAttributeCfg struct { UseRegex bool `mapstructure:"use_regex"` } +// AttributeRange defines min/max range for single entry +type AttributeRange struct { + MinValue int64 `mapstructure:"min"` + MaxValue int64 `mapstructure:"max"` +} + +// AttributeCfg holds a universal config specification for a given key +type AttributeCfg struct { + // Tag that the filter is going to be matching against. + Key string `mapstructure:"key"` + // Values is the set of attribute values that if any is equal to the actual attribute value to be considered a match. + Values []string `mapstructure:"values"` + // UseRegex (default=false) treats the values provided as regular expressions when matching the string values + UseRegex bool `mapstructure:"use_regex"` + // Ranges keep numeric attribute ranges + Ranges []AttributeRange `mapstructure:"ranges"` +} + // TraceRejectCfg holds the configurable settings which drop all traces matching the specified criteria (all of them) // before further processing type TraceRejectCfg struct { @@ -79,6 +99,8 @@ type TraceRejectCfg struct { NumericAttributeCfg *NumericAttributeCfg `mapstructure:"numeric_attribute"` // StringAttributeCfg (config) configs string attribute filter evaluator. StringAttributeCfg *StringAttributeCfg `mapstructure:"string_attribute"` + // AttributesCfg keeps generic string/numeric attributes for multiple keys + AttributeCfg []AttributeCfg `mapstructure:"attributes"` // NamePattern (optional) describes a regular expression that must be met by any span operation name NamePattern *string `mapstructure:"name_pattern"` } diff --git a/processor/cascadingfilterprocessor/config_test.go b/processor/cascadingfilterprocessor/config_test.go index 986cd98d00d3..49e154bb24ee 100644 --- a/processor/cascadingfilterprocessor/config_test.go +++ b/processor/cascadingfilterprocessor/config_test.go @@ -84,6 +84,18 @@ func TestLoadConfig(t *testing.T) { MinDuration: &minDurationValue, }, }, + { + Name: "include-some-attrs", + SpansPerSecond: 500, + AttributeCfg: []cfconfig.AttributeCfg{ + { + Key: "foo", + Values: []string{"abc"}, + UseRegex: false, + Ranges: nil, + }, + }, + }, }, }) diff --git a/processor/cascadingfilterprocessor/sampling/attrs_filter_test.go b/processor/cascadingfilterprocessor/sampling/attrs_filter_test.go new file mode 100644 index 000000000000..4b7bf36195e6 --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/attrs_filter_test.go @@ -0,0 +1,158 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sampling + +import ( + "math" + "regexp" + "testing" + "time" + + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" +) + +func newAttrsFilter(filters []attributeFilter) policyEvaluator { + return policyEvaluator{ + logger: zap.NewNop(), + attrs: filters, + maxSpansPerSecond: math.MaxInt32, + } +} + +func newAttrFilter(key string, regexValues []string, ranges []attributeRange) attributeFilter { + var patterns []*regexp.Regexp + for _, value := range regexValues { + re := regexp.MustCompile(value) + patterns = append(patterns, re) + } + + return attributeFilter{ + key: key, + values: nil, + patterns: patterns, + ranges: ranges, + } +} + +func TestAttributesFilter(t *testing.T) { + filterFooPattern := newAttrFilter("foo", []string{"foob.*"}, nil) + filterBarPattern := newAttrFilter("bar", []string{"baz.*"}, nil) + filterCooNothing := newAttrFilter("coo", nil, nil) + filterFooRange := newAttrFilter("foo", nil, []attributeRange{{minValue: 100, maxValue: 150}}) + filterFooRangesOrPatterns := newAttrFilter("foo", []string{"foo.*", "claz.*"}, []attributeRange{{minValue: 100, maxValue: 150}, {minValue: 200, maxValue: 250}}) + + composite := newAttrsFilter([]attributeFilter{filterFooRangesOrPatterns, filterBarPattern}) + bar := newAttrsFilter([]attributeFilter{filterBarPattern}) + fooRange := newAttrsFilter([]attributeFilter{filterFooRange}) + fooPattern := newAttrsFilter([]attributeFilter{filterFooPattern}) + coo := newAttrsFilter([]attributeFilter{filterCooNothing}) + + fooTraces, fooAttrs := newTrace() + fooAttrs.InsertString("foo", "foobar") + + fooNumTraces, fooNumAttrs := newTrace() + fooNumAttrs.InsertInt("foo", 130) + + fooBarTraces, fooBarAttrs := newTrace() + fooBarAttrs.InsertString("foo", "foobar") + fooBarAttrs.InsertString("bar", "bazbar") + + booTraces, booAttrs := newTrace() + booAttrs.InsertString("bar", "bazboo") + + cooTraces, cooAttrs := newTrace() + cooAttrs.InsertString("coo", "fsdkfjsdkljsda") + + cases := []struct { + Desc string + Evaluator policyEvaluator + Match []*TraceData + DontMatch []*TraceData + }{ + { + Desc: "simple string pattern", + Evaluator: fooPattern, + Match: []*TraceData{fooTraces, fooBarTraces}, + DontMatch: []*TraceData{fooNumTraces, booTraces, cooTraces}, + }, + { + Desc: "simple numeric ranges", + Evaluator: fooRange, + Match: []*TraceData{fooNumTraces}, + DontMatch: []*TraceData{fooTraces, fooBarTraces, booTraces, cooTraces}, + }, + { + Desc: "simple pattern", + Evaluator: bar, + Match: []*TraceData{fooBarTraces, booTraces}, + DontMatch: []*TraceData{fooTraces, fooNumTraces}, + }, + { + Desc: "composite", + Evaluator: composite, + Match: []*TraceData{fooBarTraces}, + DontMatch: []*TraceData{fooTraces, fooNumTraces, booTraces, cooTraces}, + }, + { + Desc: "no pattern, just existence of key", + Evaluator: coo, + Match: []*TraceData{cooTraces}, + DontMatch: []*TraceData{fooTraces, fooNumTraces, fooBarTraces, booTraces}, + }, + } + + for _, c := range cases { + t.Run(c.Desc, func(t *testing.T) { + for _, traces := range c.Match { + c.Evaluator.invertMatch = false + evaluate(t, c.Evaluator, traces, Sampled) + c.Evaluator.invertMatch = true + evaluate(t, c.Evaluator, traces, NotSampled) + } + for _, traces := range c.DontMatch { + c.Evaluator.invertMatch = false + evaluate(t, c.Evaluator, traces, NotSampled) + c.Evaluator.invertMatch = true + evaluate(t, c.Evaluator, traces, Sampled) + } + }) + } +} + +func newTrace() (*TraceData, pdata.AttributeMap) { + endTs := time.Now().UnixNano() + startTs := endTs - 100000 + + var traceBatches []pdata.Traces + + traces := pdata.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + ils := rs.InstrumentationLibrarySpans().AppendEmpty() + + spans := ils.Spans() + spans.EnsureCapacity(1) + + span := spans.AppendEmpty() + span.SetName("fooname") + span.SetStartTimestamp(pdata.Timestamp(startTs)) + span.SetEndTimestamp(pdata.Timestamp(endTs)) + + traceBatches = append(traceBatches, traces) + + return &TraceData{ + ReceivedBatches: traceBatches, + }, span.Attributes() +} diff --git a/processor/cascadingfilterprocessor/sampling/drop_trace_factory.go b/processor/cascadingfilterprocessor/sampling/drop_trace_factory.go index cb94a772df2a..b5348bfe7bd8 100644 --- a/processor/cascadingfilterprocessor/sampling/drop_trace_factory.go +++ b/processor/cascadingfilterprocessor/sampling/drop_trace_factory.go @@ -26,6 +26,7 @@ import ( type dropTraceEvaluator struct { numericAttr *numericAttributeFilter stringAttr *stringAttributeFilter + attrs []attributeFilter operationRe *regexp.Regexp logger *zap.Logger @@ -40,6 +41,10 @@ func NewDropTraceEvaluator(logger *zap.Logger, cfg config.TraceRejectCfg) (DropT if err != nil { return nil, err } + attrsFilter, err := createAttributesFilter(cfg.AttributeCfg) + if err != nil { + return nil, err + } var operationRe *regexp.Regexp @@ -53,6 +58,7 @@ func NewDropTraceEvaluator(logger *zap.Logger, cfg config.TraceRejectCfg) (DropT return &dropTraceEvaluator{ stringAttr: stringAttrFilter, numericAttr: numericAttrFilter, + attrs: attrsFilter, operationRe: operationRe, logger: logger, }, nil @@ -67,19 +73,19 @@ func (dte *dropTraceEvaluator) ShouldDrop(_ pdata.TraceID, trace *TraceData) boo matchingOperationFound := false matchingStringAttrFound := false matchingNumericAttrFound := false + matchingAttrsFound := false for _, batch := range batches { rs := batch.ResourceSpans() for i := 0; i < rs.Len(); i++ { - if dte.stringAttr != nil || dte.numericAttr != nil { - res := rs.At(i).Resource() - if !matchingStringAttrFound && dte.stringAttr != nil { - matchingStringAttrFound = checkIfStringAttrFound(res.Attributes(), dte.stringAttr) - } - if !matchingNumericAttrFound && dte.numericAttr != nil { - matchingNumericAttrFound = checkIfNumericAttrFound(res.Attributes(), dte.numericAttr) - } + res := rs.At(i).Resource() + + if !matchingStringAttrFound && dte.stringAttr != nil { + matchingStringAttrFound = checkIfStringAttrFound(res.Attributes(), dte.stringAttr) + } + if !matchingNumericAttrFound && dte.numericAttr != nil { + matchingNumericAttrFound = checkIfNumericAttrFound(res.Attributes(), dte.numericAttr) } ils := rs.At(i).InstrumentationLibrarySpans() @@ -88,13 +94,14 @@ func (dte *dropTraceEvaluator) ShouldDrop(_ pdata.TraceID, trace *TraceData) boo for k := 0; k < spans.Len(); k++ { span := spans.At(k) - if dte.stringAttr != nil || dte.numericAttr != nil { - if !matchingStringAttrFound && dte.stringAttr != nil { - matchingStringAttrFound = checkIfStringAttrFound(span.Attributes(), dte.stringAttr) - } - if !matchingNumericAttrFound && dte.numericAttr != nil { - matchingNumericAttrFound = checkIfNumericAttrFound(span.Attributes(), dte.numericAttr) - } + if !matchingAttrsFound && len(dte.attrs) > 0 { + matchingAttrsFound = checkIfAttrsMatched(res.Attributes(), span.Attributes(), dte.attrs) + } + if !matchingStringAttrFound && dte.stringAttr != nil { + matchingStringAttrFound = checkIfStringAttrFound(span.Attributes(), dte.stringAttr) + } + if !matchingNumericAttrFound && dte.numericAttr != nil { + matchingNumericAttrFound = checkIfNumericAttrFound(span.Attributes(), dte.numericAttr) } if dte.operationRe != nil && !matchingOperationFound { @@ -108,11 +115,12 @@ func (dte *dropTraceEvaluator) ShouldDrop(_ pdata.TraceID, trace *TraceData) boo } conditionMet := struct { - operationName, stringAttr, numericAttr bool + operationName, stringAttr, numericAttr, attrs bool }{ operationName: true, stringAttr: true, numericAttr: true, + attrs: true, } if dte.operationRe != nil { @@ -124,6 +132,9 @@ func (dte *dropTraceEvaluator) ShouldDrop(_ pdata.TraceID, trace *TraceData) boo if dte.stringAttr != nil { conditionMet.stringAttr = matchingStringAttrFound } + if len(dte.attrs) > 0 { + conditionMet.attrs = matchingAttrsFound + } - return conditionMet.operationName && conditionMet.numericAttr && conditionMet.stringAttr + return conditionMet.operationName && conditionMet.numericAttr && conditionMet.stringAttr && conditionMet.attrs } diff --git a/processor/cascadingfilterprocessor/sampling/policy_factory.go b/processor/cascadingfilterprocessor/sampling/policy_factory.go index b20902902d1f..2c36284658be 100644 --- a/processor/cascadingfilterprocessor/sampling/policy_factory.go +++ b/processor/cascadingfilterprocessor/sampling/policy_factory.go @@ -35,9 +35,22 @@ type stringAttributeFilter struct { patterns []*regexp.Regexp } +type attributeRange struct { + minValue int64 + maxValue int64 +} + +type attributeFilter struct { + key string + values map[string]struct{} + patterns []*regexp.Regexp + ranges []attributeRange +} + type policyEvaluator struct { numericAttr *numericAttributeFilter stringAttr *stringAttributeFilter + attrs []attributeFilter operationRe *regexp.Regexp minDuration *time.Duration @@ -95,6 +108,55 @@ func createStringAttributeFilter(cfg *config.StringAttributeCfg) (*stringAttribu }, nil } +func createAttributeFilter(cfg config.AttributeCfg) (*attributeFilter, error) { + valuesMap := make(map[string]struct{}) + var patterns []*regexp.Regexp + for _, value := range cfg.Values { + if cfg.UseRegex { + re, err := regexp.Compile(value) + if err != nil { + return nil, err + } + patterns = append(patterns, re) + } else { + if value != "" { + valuesMap[value] = struct{}{} + } + } + } + var ranges []attributeRange + for _, r := range cfg.Ranges { + ranges = append(ranges, attributeRange{ + minValue: r.MinValue, + maxValue: r.MaxValue, + }) + } + + return &attributeFilter{ + key: cfg.Key, + values: valuesMap, + patterns: patterns, + ranges: ranges, + }, nil +} + +func createAttributesFilter(cfg []config.AttributeCfg) ([]attributeFilter, error) { + if cfg == nil { + return nil, nil + } + + var filters []attributeFilter + for _, attrCfg := range cfg { + filter, err := createAttributeFilter(attrCfg) + if err != nil { + return nil, err + } + filters = append(filters, *filter) + } + + return filters, nil +} + // NewProbabilisticFilter creates a policy evaluator intended for selecting samples probabilistically func NewProbabilisticFilter(logger *zap.Logger, maxSpanRate int32) (PolicyEvaluator, error) { return &policyEvaluator{ @@ -112,6 +174,10 @@ func NewFilter(logger *zap.Logger, cfg *config.TraceAcceptCfg) (PolicyEvaluator, if err != nil { return nil, err } + attrsFilter, err := createAttributesFilter(cfg.AttributeCfg) + if err != nil { + return nil, err + } var operationRe *regexp.Regexp @@ -133,6 +199,7 @@ func NewFilter(logger *zap.Logger, cfg *config.TraceAcceptCfg) (PolicyEvaluator, return &policyEvaluator{ stringAttr: stringAttrFilter, numericAttr: numericAttrFilter, + attrs: attrsFilter, operationRe: operationRe, minDuration: cfg.PropertiesCfg.MinDuration, minNumberOfSpans: cfg.PropertiesCfg.MinNumberOfSpans, diff --git a/processor/cascadingfilterprocessor/sampling/policy_filter.go b/processor/cascadingfilterprocessor/sampling/policy_filter.go index 82c9a1162180..69fb71d45fe0 100644 --- a/processor/cascadingfilterprocessor/sampling/policy_filter.go +++ b/processor/cascadingfilterprocessor/sampling/policy_filter.go @@ -24,6 +24,72 @@ func tsToMicros(ts pdata.Timestamp) int64 { return int64(ts / 1000) } +func checkIfAttrsMatched(resAttrs pdata.AttributeMap, spanAttrs pdata.AttributeMap, filters []attributeFilter) bool { + for _, filter := range filters { + var resAttrMatched bool + spanAttrMatched, spanAttrFound := checkAttributeFilterMatchedAndFound(spanAttrs, filter) + if !spanAttrFound { + resAttrMatched, _ = checkAttributeFilterMatchedAndFound(resAttrs, filter) + } + + if !resAttrMatched && !spanAttrMatched { + return false + } + } + return true +} + +func checkAttributeFilterMatchedAndFound(attrs pdata.AttributeMap, filter attributeFilter) (bool, bool) { + if v, ok := attrs.Get(filter.key); ok { + // String patterns vs values is exclusive + if len(filter.patterns) > 0 { + // Pattern matching + truncableStr := v.StringVal() + for _, re := range filter.patterns { + if re.MatchString(truncableStr) { + return true, true + } + } + } else if len(filter.values) > 0 { + // Exact matching + truncableStr := v.StringVal() + if len(truncableStr) > 0 { + if _, ok := filter.values[truncableStr]; ok { + return true, true + } + } + } + + if len(filter.ranges) > 0 { + if v.Type() == pdata.AttributeValueTypeDouble { + value := v.DoubleVal() + for _, r := range filter.ranges { + if value >= float64(r.minValue) && value <= float64(r.maxValue) { + return true, true + } + } + } else if v.Type() == pdata.AttributeValueTypeInt { + value := v.IntVal() + for _, r := range filter.ranges { + if value >= r.minValue && value <= r.maxValue { + return true, true + } + } + } + } + + // This is special condition which just checks if any filters were defined or not; For latter, pass if key found + if len(filter.ranges) == 0 && len(filter.values) == 0 && len(filter.patterns) == 0 { + return true, true + } + + return false, true + } + + // Not found and not matched + return false, false +} + func checkIfNumericAttrFound(attrs pdata.AttributeMap, filter *numericAttributeFilter) bool { if v, ok := attrs.Get(filter.key); ok { value := v.IntVal() @@ -65,6 +131,7 @@ func (pe *policyEvaluator) evaluateRules(_ pdata.TraceID, trace *TraceData) Deci matchingOperationFound := false matchingStringAttrFound := false matchingNumericAttrFound := false + matchingAttrsFound := false spanCount := 0 errorCount := 0 @@ -75,14 +142,14 @@ func (pe *policyEvaluator) evaluateRules(_ pdata.TraceID, trace *TraceData) Deci rs := batch.ResourceSpans() for i := 0; i < rs.Len(); i++ { - if pe.stringAttr != nil || pe.numericAttr != nil { - res := rs.At(i).Resource() - if !matchingStringAttrFound && pe.stringAttr != nil { - matchingStringAttrFound = checkIfStringAttrFound(res.Attributes(), pe.stringAttr) - } - if !matchingNumericAttrFound && pe.numericAttr != nil { - matchingNumericAttrFound = checkIfNumericAttrFound(res.Attributes(), pe.numericAttr) - } + res := rs.At(i).Resource() + + if !matchingStringAttrFound && pe.stringAttr != nil { + matchingStringAttrFound = checkIfStringAttrFound(res.Attributes(), pe.stringAttr) + } + + if !matchingNumericAttrFound && pe.numericAttr != nil { + matchingNumericAttrFound = checkIfNumericAttrFound(res.Attributes(), pe.numericAttr) } ils := rs.At(i).InstrumentationLibrarySpans() @@ -92,13 +159,16 @@ func (pe *policyEvaluator) evaluateRules(_ pdata.TraceID, trace *TraceData) Deci for k := 0; k < spans.Len(); k++ { span := spans.At(k) - if pe.stringAttr != nil || pe.numericAttr != nil { - if !matchingStringAttrFound && pe.stringAttr != nil { - matchingStringAttrFound = checkIfStringAttrFound(span.Attributes(), pe.stringAttr) - } - if !matchingNumericAttrFound && pe.numericAttr != nil { - matchingNumericAttrFound = checkIfNumericAttrFound(span.Attributes(), pe.numericAttr) - } + if !matchingAttrsFound && len(pe.attrs) > 0 { + matchingAttrsFound = checkIfAttrsMatched(res.Attributes(), span.Attributes(), pe.attrs) + } + + if !matchingStringAttrFound && pe.stringAttr != nil { + matchingStringAttrFound = checkIfStringAttrFound(span.Attributes(), pe.stringAttr) + } + + if !matchingNumericAttrFound && pe.numericAttr != nil { + matchingNumericAttrFound = checkIfNumericAttrFound(span.Attributes(), pe.numericAttr) } if pe.operationRe != nil && !matchingOperationFound { @@ -133,13 +203,14 @@ func (pe *policyEvaluator) evaluateRules(_ pdata.TraceID, trace *TraceData) Deci } conditionMet := struct { - operationName, minDuration, minSpanCount, stringAttr, numericAttr, minErrorCount bool + operationName, minDuration, minSpanCount, stringAttr, numericAttr, attrs, minErrorCount bool }{ operationName: true, minDuration: true, minSpanCount: true, stringAttr: true, numericAttr: true, + attrs: true, minErrorCount: true, } @@ -158,6 +229,9 @@ func (pe *policyEvaluator) evaluateRules(_ pdata.TraceID, trace *TraceData) Deci if pe.stringAttr != nil { conditionMet.stringAttr = matchingStringAttrFound } + if len(pe.attrs) > 0 { + conditionMet.attrs = matchingAttrsFound + } if pe.minNumberOfErrors != nil { conditionMet.minErrorCount = errorCount >= *pe.minNumberOfErrors } @@ -167,6 +241,7 @@ func (pe *policyEvaluator) evaluateRules(_ pdata.TraceID, trace *TraceData) Deci conditionMet.operationName && conditionMet.numericAttr && conditionMet.stringAttr && + conditionMet.attrs && conditionMet.minErrorCount { if pe.invertMatch { return NotSampled diff --git a/processor/cascadingfilterprocessor/testdata/cascading_filter_config.yaml b/processor/cascadingfilterprocessor/testdata/cascading_filter_config.yaml index bc4756cb4f15..8526f8745eb6 100644 --- a/processor/cascadingfilterprocessor/testdata/cascading_filter_config.yaml +++ b/processor/cascadingfilterprocessor/testdata/cascading_filter_config.yaml @@ -23,6 +23,12 @@ processors: spans_per_second: 400 properties: min_duration: 9s + - name: include-some-attrs + spans_per_second: 500 + attributes: + - key: foo + values: + - abc cascading_filter/2: decision_wait: 10s num_traces: 100