Skip to content

Commit 46d03a0

Browse files
authored
[internal/filter] filtermetric to filterottl bridge (#23141)
**Description:** This PR adds a bridge between `filtermetric.NewSkipExpr` and `filterottl.NewBoolExprForMetric` behind a feature gate. With the feature gate enabled, any component using `filtermetric.NewSkipExpr` will start using OTTL behind the scenes. In addition, the filterprocessor's implementation of `newSkipResExpr`, which is a skip expression for resources, is bridged to `filterottl.NewBoolExprForResource`. Since this implementation exists only for filtering metrics, the same feature gate is used for both. While investigating the existing `internal/filtermetric` uses with filterprocessor and attributesprocessor I found that: - The attributes processor does not support Expressions or Resource Attributes. The readme claims that it does, but the implementation does not support it (and there are no tests). This bridge DOES NOT rectify that. - The filterprocessor allows filtering by resource attributes, expressions, and metric name. Unlike it's implementation for filtering spans and logs, the filterprocessor handles filtering spans at the Resource level loop. This is the most performant solution, and how OTTL likes to think about the problem, but it results in a different pattern than filtering spans and logs. This bridge DOES NOT attempt to move the resource implementation into `internal/filtermetric`. **Link to tracking Issue:** Related to #18643 Related to #18642 Depends on: - [x] #23142 **Testing:** Added tests comparing the output of the existing config and the bridge.
1 parent f1068be commit 46d03a0

File tree

10 files changed

+554
-61
lines changed

10 files changed

+554
-61
lines changed

internal/filter/filtermetric/filtermetric.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,28 @@
44
package filtermetric // import "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filtermetric"
55

66
import (
7+
"go.opentelemetry.io/collector/featuregate"
8+
79
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/expr"
810
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterconfig"
11+
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterottl"
912
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric"
1013
)
1114

15+
var UseOTTLBridge = featuregate.GlobalRegistry().MustRegister(
16+
"filter.filtermetric.useOTTLBridge",
17+
featuregate.StageAlpha,
18+
featuregate.WithRegisterDescription("When enabled, filtermetric will convert filtermetric configuration to OTTL and use filterottl evaluation"),
19+
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/18642"),
20+
)
21+
1222
// NewSkipExpr creates a BoolExpr that on evaluation returns true if a metric should NOT be processed or kept.
1323
// The logic determining if a metric should be processed is based on include and exclude settings.
1424
// Include properties are checked before exclude settings are checked.
1525
func NewSkipExpr(include *filterconfig.MetricMatchProperties, exclude *filterconfig.MetricMatchProperties) (expr.BoolExpr[ottlmetric.TransformContext], error) {
26+
if UseOTTLBridge.IsEnabled() {
27+
return filterottl.NewMetricSkipExprBridge(include, exclude)
28+
}
1629
var matchers []expr.BoolExpr[ottlmetric.TransformContext]
1730
inclExpr, err := newExpr(include)
1831
if err != nil {

internal/filter/filtermetric/filtermetric_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ package filtermetric
55

66
import (
77
"context"
8+
"fmt"
89
"testing"
910

1011
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
1113
"go.opentelemetry.io/collector/pdata/pcommon"
1214
"go.opentelemetry.io/collector/pdata/pmetric"
1315

1416
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterconfig"
17+
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterottl"
1518
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset"
1619
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric"
1720
)
@@ -86,3 +89,123 @@ func TestMatcherMatches(t *testing.T) {
8689
})
8790
}
8891
}
92+
93+
func Test_NewSkipExpr_With_Bridge(t *testing.T) {
94+
tests := []struct {
95+
name string
96+
include *filterconfig.MetricMatchProperties
97+
exclude *filterconfig.MetricMatchProperties
98+
err error
99+
}{
100+
// Metric Name
101+
{
102+
name: "single static metric name include",
103+
include: &filterconfig.MetricMatchProperties{
104+
MatchType: filterconfig.MetricStrict,
105+
MetricNames: []string{"metricA"},
106+
},
107+
},
108+
{
109+
name: "multiple static service name include",
110+
include: &filterconfig.MetricMatchProperties{
111+
MatchType: filterconfig.MetricStrict,
112+
MetricNames: []string{"metricB", "metricC"},
113+
},
114+
},
115+
{
116+
name: "single regex service name include",
117+
include: &filterconfig.MetricMatchProperties{
118+
MatchType: filterconfig.MetricRegexp,
119+
MetricNames: []string{".*A"},
120+
},
121+
},
122+
{
123+
name: "multiple regex service name include",
124+
include: &filterconfig.MetricMatchProperties{
125+
MatchType: filterconfig.MetricRegexp,
126+
MetricNames: []string{".*B", ".*C"},
127+
},
128+
},
129+
{
130+
name: "single static metric name exclude",
131+
exclude: &filterconfig.MetricMatchProperties{
132+
MatchType: filterconfig.MetricStrict,
133+
MetricNames: []string{"metricA"},
134+
},
135+
},
136+
{
137+
name: "multiple static service name exclude",
138+
exclude: &filterconfig.MetricMatchProperties{
139+
MatchType: filterconfig.MetricStrict,
140+
MetricNames: []string{"metricB", "metricC"},
141+
},
142+
},
143+
{
144+
name: "single regex service name exclude",
145+
exclude: &filterconfig.MetricMatchProperties{
146+
MatchType: filterconfig.MetricRegexp,
147+
MetricNames: []string{".*A"},
148+
},
149+
},
150+
{
151+
name: "multiple regex service name exclude",
152+
exclude: &filterconfig.MetricMatchProperties{
153+
MatchType: filterconfig.MetricRegexp,
154+
MetricNames: []string{".*B", ".*C"},
155+
},
156+
},
157+
158+
// Expression
159+
{
160+
name: "expression errors",
161+
include: &filterconfig.MetricMatchProperties{
162+
MatchType: filterconfig.MetricExpr,
163+
Expressions: []string{"MetricName == metricA"},
164+
},
165+
err: fmt.Errorf("expressions configuration cannot be converted to OTTL - see https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/filterprocessor#configuration for OTTL configuration"),
166+
},
167+
168+
// Complex
169+
{
170+
name: "complex",
171+
include: &filterconfig.MetricMatchProperties{
172+
MatchType: filterconfig.MetricStrict,
173+
MetricNames: []string{"metricA"},
174+
},
175+
exclude: &filterconfig.MetricMatchProperties{
176+
MatchType: filterconfig.MetricRegexp,
177+
MetricNames: []string{".*B", ".*C"},
178+
},
179+
},
180+
}
181+
182+
for _, tt := range tests {
183+
t.Run(tt.name, func(t *testing.T) {
184+
metric := pmetric.NewMetric()
185+
metric.SetName("metricA")
186+
187+
resource := pcommon.NewResource()
188+
189+
scope := pcommon.NewInstrumentationScope()
190+
191+
tCtx := ottlmetric.NewTransformContext(metric, scope, resource)
192+
193+
boolExpr, err := NewSkipExpr(tt.include, tt.exclude)
194+
require.NoError(t, err)
195+
expectedResult, err := boolExpr.Eval(context.Background(), tCtx)
196+
assert.NoError(t, err)
197+
198+
ottlBoolExpr, err := filterottl.NewMetricSkipExprBridge(tt.include, tt.exclude)
199+
200+
if tt.err != nil {
201+
assert.Equal(t, tt.err, err)
202+
} else {
203+
assert.NoError(t, err)
204+
ottlResult, err := ottlBoolExpr.Eval(context.Background(), tCtx)
205+
assert.NoError(t, err)
206+
207+
assert.Equal(t, expectedResult, ottlResult)
208+
}
209+
})
210+
}
211+
}

internal/filter/filterottl/bridge.go

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import (
1515
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset"
1616
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
1717
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottllog"
18+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric"
19+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlresource"
1820
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan"
1921
)
2022

2123
const (
2224
serviceNameStaticStatement = `resource.attributes["service.name"] == "%v"`
23-
spanNameStaticStatement = `name == "%v"`
25+
nameStaticStatement = `name == "%v"`
2426
spanKindStaticStatement = `kind.deprecated_string == "%v"`
2527
scopeNameStaticStatement = `instrumentation_scope.name == "%v"`
2628
scopeVersionStaticStatement = `instrumentation_scope.version == "%v"`
@@ -30,7 +32,7 @@ const (
3032
severityTextStaticStatement = `severity_text == "%v"`
3133

3234
serviceNameRegexStatement = `IsMatch(resource.attributes["service.name"], "%v")`
33-
spanNameRegexStatement = `IsMatch(name, "%v")`
35+
nameRegexStatement = `IsMatch(name, "%v")`
3436
spanKindRegexStatement = `IsMatch(kind.deprecated_string, "%v")`
3537
scopeNameRegexStatement = `IsMatch(instrumentation_scope.name, "%v")`
3638
scopeVersionRegexStatement = `IsMatch(instrumentation_scope.version, "%v")`
@@ -83,6 +85,35 @@ func NewLogSkipExprBridge(mc *filterconfig.MatchConfig) (expr.BoolExpr[ottllog.T
8385
return NewBoolExprForLog(statements, StandardLogFuncs(), ottl.PropagateError, component.TelemetrySettings{Logger: zap.NewNop()})
8486
}
8587

88+
func NewResourceSkipExprBridge(mc *filterconfig.MatchConfig) (expr.BoolExpr[ottlresource.TransformContext], error) {
89+
statements := make([]string, 0, 2)
90+
if mc.Include != nil {
91+
// OTTL treats resource attributes as attributes for the resource context.
92+
mc.Include.Attributes = mc.Include.Resources
93+
mc.Include.Resources = nil
94+
95+
statement, err := createStatement(*mc.Include)
96+
if err != nil {
97+
return nil, err
98+
}
99+
statements = append(statements, fmt.Sprintf("not (%v)", statement))
100+
}
101+
102+
if mc.Exclude != nil {
103+
// OTTL treats resource attributes as attributes for the resource context.
104+
mc.Exclude.Attributes = mc.Exclude.Resources
105+
mc.Exclude.Resources = nil
106+
107+
statement, err := createStatement(*mc.Exclude)
108+
if err != nil {
109+
return nil, err
110+
}
111+
statements = append(statements, fmt.Sprintf("%v", statement))
112+
}
113+
114+
return NewBoolExprForResource(statements, StandardResourceFuncs(), ottl.PropagateError, component.TelemetrySettings{Logger: zap.NewNop()})
115+
}
116+
86117
func NewSpanSkipExprBridge(mc *filterconfig.MatchConfig) (expr.BoolExpr[ottlspan.TransformContext], error) {
87118
statements := make([]string, 0, 2)
88119
if mc.Include != nil {
@@ -239,7 +270,7 @@ func createStatementTemplates(matchType filterset.MatchType) (statementTemplates
239270
case filterset.Strict:
240271
return statementTemplates{
241272
serviceNameStatement: serviceNameStaticStatement,
242-
spanNameStatement: spanNameStaticStatement,
273+
spanNameStatement: nameStaticStatement,
243274
spanKindStatement: spanKindStaticStatement,
244275
scopeNameStatement: scopeNameStaticStatement,
245276
scopeVersionStatement: scopeVersionStaticStatement,
@@ -251,7 +282,7 @@ func createStatementTemplates(matchType filterset.MatchType) (statementTemplates
251282
case filterset.Regexp:
252283
return statementTemplates{
253284
serviceNameStatement: serviceNameRegexStatement,
254-
spanNameStatement: spanNameRegexStatement,
285+
spanNameStatement: nameRegexStatement,
255286
spanKindStatement: spanKindRegexStatement,
256287
scopeNameStatement: scopeNameRegexStatement,
257288
scopeVersionStatement: scopeVersionRegexStatement,
@@ -315,3 +346,59 @@ func createSeverityNumberConditions(severityNumberProperties *filterconfig.LogSe
315346
severityNumberCondition := fmt.Sprintf(severityNumberStatement, severityNumberProperties.MatchUndefined, severityNumberProperties.Min)
316347
return &severityNumberCondition
317348
}
349+
350+
func NewMetricSkipExprBridge(include *filterconfig.MetricMatchProperties, exclude *filterconfig.MetricMatchProperties) (expr.BoolExpr[ottlmetric.TransformContext], error) {
351+
statements := make([]string, 0, 2)
352+
if include != nil {
353+
statement, err := createMetricStatement(*include)
354+
if err != nil {
355+
return nil, err
356+
}
357+
if statement != nil {
358+
statements = append(statements, fmt.Sprintf("not (%v)", *statement))
359+
}
360+
}
361+
362+
if exclude != nil {
363+
statement, err := createMetricStatement(*exclude)
364+
if err != nil {
365+
return nil, err
366+
}
367+
if statement != nil {
368+
statements = append(statements, fmt.Sprintf("%v", *statement))
369+
}
370+
}
371+
372+
if len(statements) == 0 {
373+
return nil, nil
374+
}
375+
376+
return NewBoolExprForMetric(statements, StandardMetricFuncs(), ottl.PropagateError, component.TelemetrySettings{Logger: zap.NewNop()})
377+
}
378+
379+
func createMetricStatement(mp filterconfig.MetricMatchProperties) (*string, error) {
380+
if mp.MatchType == filterconfig.MetricExpr {
381+
if len(mp.Expressions) == 0 {
382+
return nil, nil
383+
}
384+
return nil, fmt.Errorf("expressions configuration cannot be converted to OTTL - see https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/filterprocessor#configuration for OTTL configuration")
385+
}
386+
387+
if len(mp.MetricNames) == 0 {
388+
return nil, nil
389+
}
390+
391+
metricNameStatement := nameStaticStatement
392+
if mp.MatchType == filterconfig.MetricRegexp {
393+
metricNameStatement = nameRegexStatement
394+
}
395+
metricNameConditions := createBasicConditions(metricNameStatement, mp.MetricNames)
396+
var format string
397+
if len(metricNameConditions) > 1 {
398+
format = "(%v)"
399+
} else {
400+
format = "%v"
401+
}
402+
statement := fmt.Sprintf(format, strings.Join(metricNameConditions, " or "))
403+
return &statement, nil
404+
}

0 commit comments

Comments
 (0)