diff --git a/internal/filter/filterlog/filterlog.go b/internal/filter/filterlog/filterlog.go index 42e5242a9cb4..b394397c193f 100644 --- a/internal/filter/filterlog/filterlog.go +++ b/internal/filter/filterlog/filterlog.go @@ -7,17 +7,31 @@ import ( "context" "fmt" + "go.opentelemetry.io/collector/featuregate" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/expr" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterconfig" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filtermatcher" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterottl" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottllog" ) +var useOTTLBridge = featuregate.GlobalRegistry().MustRegister( + "filter.filterlog.useOTTLBridge", + featuregate.StageAlpha, + featuregate.WithRegisterDescription("When enabled, filterlog will convert filterlog configuration to OTTL and use filterottl evaluation"), + featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/18642"), +) + // NewSkipExpr creates a BoolExpr that on evaluation returns true if a log should NOT be processed or kept. // The logic determining if a log should be processed is based on include and exclude settings. // Include properties are checked before exclude settings are checked. func NewSkipExpr(mp *filterconfig.MatchConfig) (expr.BoolExpr[ottllog.TransformContext], error) { + + if useOTTLBridge.IsEnabled() { + return filterottl.NewLogSkipExprBridge(mp) + } var matchers []expr.BoolExpr[ottllog.TransformContext] inclExpr, err := newExpr(mp.Include) if err != nil { diff --git a/internal/filter/filterlog/filterlog_test.go b/internal/filter/filterlog/filterlog_test.go index 931b0653cbd7..c01889a55dff 100644 --- a/internal/filter/filterlog/filterlog_test.go +++ b/internal/filter/filterlog/filterlog_test.go @@ -9,12 +9,16 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/featuregate" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/plog" + conventions "go.opentelemetry.io/collector/semconv/v1.6.1" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterconfig" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterottl" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottllog" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottltest" ) func createConfig(matchType filterset.MatchType) *filterset.Config { @@ -235,3 +239,1157 @@ func TestLogRecord_Matching_True(t *testing.T) { }) } } + +func Test_NewSkipExpr_With_Bridge(t *testing.T) { + tests := []struct { + name string + condition *filterconfig.MatchConfig + logSeverity plog.SeverityNumber + }{ + // Body + { + name: "single static body include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogBodies: []string{"body"}, + }, + }, + }, + { + name: "multiple static body include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogBodies: []string{"hand", "foot"}, + }, + }, + }, + { + name: "single regex body include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogBodies: []string{"bod.*"}, + }, + }, + }, + { + name: "multiple regex body include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogBodies: []string{"hand.*", "foot.*"}, + }, + }, + }, + { + name: "single static body exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogBodies: []string{"body"}, + }, + }, + }, + { + name: "multiple static body exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogBodies: []string{"hand", "foot"}, + }, + }, + }, + { + name: "single regex body exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogBodies: []string{"bod.*"}, + }, + }, + }, + { + name: "multiple regex body exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogBodies: []string{"hand.*", "foot.*"}, + }, + }, + }, + + // Severity text + { + name: "single static severity text include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityTexts: []string{"severity text"}, + }, + }, + }, + { + name: "multiple static severity text include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityTexts: []string{"not", "correct"}, + }, + }, + }, + { + name: "single regex severity text include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogSeverityTexts: []string{"severity.*"}, + }, + }, + }, + { + name: "multiple regex severity text include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogSeverityTexts: []string{"not.*", "correct.*"}, + }, + }, + }, + { + name: "single static severity text exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityTexts: []string{"severity text"}, + }, + }, + }, + { + name: "multiple static severity text exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityTexts: []string{"not", "correct"}, + }, + }, + }, + { + name: "single regex severity text exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogSeverityTexts: []string{"severity.*"}, + }, + }, + }, + { + name: "multiple regex severity text exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogSeverityTexts: []string{"not.*", "correct.*"}, + }, + }, + }, + + // Severity number + { + name: "severity number unspecified, match unspecified true, min unspecified, include", + logSeverity: plog.SeverityNumberUnspecified, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: true, + Min: plog.SeverityNumberUnspecified, + }, + }, + }, + }, + { + name: "severity number unspecified, match unspecified true, min info, include", + logSeverity: plog.SeverityNumberUnspecified, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: true, + Min: plog.SeverityNumberInfo, + }, + }, + }, + }, + { + name: "severity number unspecified, match unspecified false, min unspecified, include", + logSeverity: plog.SeverityNumberUnspecified, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberUnspecified, + }, + }, + }, + }, + { + name: "severity number unspecified, match unspecified false, min info, include", + logSeverity: plog.SeverityNumberUnspecified, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberInfo, + }, + }, + }, + }, + { + name: "severity number not unspecified, match unspecified true, min info, include", + logSeverity: plog.SeverityNumberInfo, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: true, + Min: plog.SeverityNumberInfo, + }, + }, + }, + }, + { + name: "severity number not unspecified, match unspecified true, min fatal, include", + logSeverity: plog.SeverityNumberInfo, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: true, + Min: plog.SeverityNumberFatal, + }, + }, + }, + }, + { + name: "severity number not unspecified, match unspecified false, min info, include", + logSeverity: plog.SeverityNumberInfo, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberInfo, + }, + }, + }, + }, + { + name: "severity number not unspecified, match unspecified false, min fatal, include", + logSeverity: plog.SeverityNumberInfo, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberFatal, + }, + }, + }, + }, + { + name: "severity number unspecified, match unspecified true, min unspecified, exclude", + logSeverity: plog.SeverityNumberUnspecified, + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: true, + Min: plog.SeverityNumberUnspecified, + }, + }, + }, + }, + { + name: "severity number unspecified, match unspecified true, min info, exclude", + logSeverity: plog.SeverityNumberUnspecified, + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: true, + Min: plog.SeverityNumberInfo, + }, + }, + }, + }, + { + name: "severity number unspecified, match unspecified false, min unspecified, exclude", + logSeverity: plog.SeverityNumberUnspecified, + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberUnspecified, + }, + }, + }, + }, + { + name: "severity number unspecified, match unspecified false, min info, exclude", + logSeverity: plog.SeverityNumberUnspecified, + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberInfo, + }, + }, + }, + }, + { + name: "severity number not unspecified, match unspecified true, min info, exclude", + logSeverity: plog.SeverityNumberInfo, + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: true, + Min: plog.SeverityNumberInfo, + }, + }, + }, + }, + { + name: "severity number not unspecified, match unspecified true, min fatal, exclude", + logSeverity: plog.SeverityNumberInfo, + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: true, + Min: plog.SeverityNumberFatal, + }, + }, + }, + }, + { + name: "severity number not unspecified, match unspecified false, min info, exclude", + logSeverity: plog.SeverityNumberInfo, + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberInfo, + }, + }, + }, + }, + { + name: "severity number not unspecified, match unspecified false, min fatal, exclude", + logSeverity: plog.SeverityNumberInfo, + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberFatal, + }, + }, + }, + }, + + // Scope name + { + name: "single static scope name include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + }, + }, + }, + }, + }, + { + name: "multiple static scope name include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "foo", + }, + { + Name: "bar", + }, + }, + }, + }, + }, + { + name: "single regex scope name include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + }, + }, + }, + }, + }, + { + name: "multiple regex scope name include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "foo.*", + }, + { + Name: "bar.*", + }, + }, + }, + }, + }, + { + name: "single static scope name exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + }, + }, + }, + }, + }, + { + name: "multiple static scope name exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "foo", + }, + { + Name: "bar", + }, + }, + }, + }, + }, + { + name: "single regex scope name exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + }, + }, + }, + }, + }, + { + name: "multiple regex scope name exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "foo.*", + }, + { + Name: "bar.*", + }, + }, + }, + }, + }, + + // Scope version + { + name: "single static scope version include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("0.1.0"), + }, + }, + }, + }, + }, + { + name: "multiple static scope version include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("2.0.0"), + }, + { + Name: "scope", + Version: ottltest.Strp(`1.1.0`), + }, + }, + }, + }, + }, + { + name: "single regex scope version include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("0.*"), + }, + }, + }, + }, + }, + { + name: "multiple regex scope version include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("2.*"), + }, + { + Name: "scope", + Version: ottltest.Strp("^1\\\\.1.*"), + }, + }, + }, + }, + }, + { + name: "single static scope version exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("0.1.0"), + }, + }, + }, + }, + }, + { + name: "multiple static scope version exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("2.0.0"), + }, + { + Name: "scope", + Version: ottltest.Strp(`1.1.0`), + }, + }, + }, + }, + }, + { + name: "single regex scope version exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("0.*"), + }, + }, + }, + }, + }, + { + name: "multiple regex scope version exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("2.*"), + }, + { + Name: "scope", + Version: ottltest.Strp(`1\\.1.*`), + }, + }, + }, + }, + }, + + // attributes + { + name: "single static attribute include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val1", + }, + }, + }, + }, + }, + { + name: "multiple static attribute include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val2", + }, + { + Key: "attr2", + Value: "val2", + }, + }, + }, + }, + }, + { + name: "single regex attribute include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val.*", + }, + }, + }, + }, + }, + { + name: "multiple regex attribute include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val", + }, + { + Key: "attr3", + Value: "val.*", + }, + }, + }, + }, + }, + { + name: "single static attribute exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val1", + }, + }, + }, + }, + }, + { + name: "multiple static attribute exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val2", + }, + { + Key: "attr2", + Value: "val2", + }, + }, + }, + }, + }, + { + name: "single regex attribute exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val.*", + }, + }, + }, + }, + }, + { + name: "multiple regex attribute exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val", + }, + { + Key: "attr3", + Value: "val.*", + }, + }, + }, + }, + }, + + // resource attributes + { + name: "single static resource attribute include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: "svcA", + }, + }, + }, + }, + }, + { + name: "multiple static resource attribute include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: "svc2", + }, + { + Key: "service.version", + Value: "v1", + }, + }, + }, + }, + }, + { + name: "single regex resource attribute include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: "svc.*", + }, + }, + }, + }, + }, + { + name: "multiple regex resource attribute include", + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: ".*2", + }, + { + Key: "service.name", + Value: ".*3", + }, + }, + }, + }, + }, + { + name: "single static resource attribute exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: "svcA", + }, + }, + }, + }, + }, + { + name: "multiple static resource attribute exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: "svc2", + }, + { + Key: "service.version", + Value: "v1", + }, + }, + }, + }, + }, + { + name: "single regex resource attribute exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: "svc.*", + }, + }, + }, + }, + }, + { + name: "multiple regex resource attribute exclude", + condition: &filterconfig.MatchConfig{ + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: ".*2", + }, + { + Key: "service.name", + Value: ".*3", + }, + }, + }, + }, + }, + + // complex + { + name: "complex", + logSeverity: plog.SeverityNumberDebug, + condition: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Regexp, + }, + LogBodies: []string{"body"}, + LogSeverityTexts: []string{"severity text"}, + Libraries: []filterconfig.InstrumentationLibrary{ + { + Name: "scope", + Version: ottltest.Strp("0.1.0"), + }, + }, + Resources: []filterconfig.Attribute{ + { + Key: "service.name", + Value: "svcA", + }, + }, + }, + Exclude: &filterconfig.MatchProperties{ + Config: filterset.Config{ + MatchType: filterset.Strict, + }, + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + MatchUndefined: false, + Min: plog.SeverityNumberInfo, + }, + Attributes: []filterconfig.Attribute{ + { + Key: "attr1", + Value: "val1", + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + log := plog.NewLogRecord() + log.Body().SetStr("body") + log.Attributes().PutStr("keyString", "arithmetic") + log.Attributes().PutInt("keyInt", 123) + log.Attributes().PutDouble("keyDouble", 3245.6) + log.Attributes().PutBool("keyBool", true) + log.Attributes().PutStr("keyExists", "present") + log.SetSeverityText("severity text") + log.SetSeverityNumber(tt.logSeverity) + + resource := pcommon.NewResource() + resource.Attributes().PutStr(conventions.AttributeServiceName, "svcA") + + scope := pcommon.NewInstrumentationScope() + + tCtx := ottllog.NewTransformContext(log, scope, resource) + + boolExpr, err := NewSkipExpr(tt.condition) + require.NoError(t, err) + expectedResult, err := boolExpr.Eval(context.Background(), tCtx) + assert.NoError(t, err) + + ottlBoolExpr, err := filterottl.NewLogSkipExprBridge(tt.condition) + assert.NoError(t, err) + ottlResult, err := ottlBoolExpr.Eval(context.Background(), tCtx) + assert.NoError(t, err) + + assert.Equal(t, expectedResult, ottlResult) + }) + } +} + +func BenchmarkFilterlog_NewSkipExpr(b *testing.B) { + testCases := []struct { + name string + mc *filterconfig.MatchConfig + skip bool + }{ + { + name: "body_match_regexp", + mc: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Regexp), + LogBodies: []string{"body"}, + }, + }, + skip: false, + }, + { + name: "body_match_static", + mc: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + LogBodies: []string{"body"}, + }, + }, + skip: false, + }, + { + name: "severity_number_match", + mc: &filterconfig.MatchConfig{ + Include: &filterconfig.MatchProperties{ + Config: *createConfig(filterset.Strict), + LogSeverityNumber: &filterconfig.LogSeverityNumberMatchProperties{ + Min: plog.SeverityNumberInfo, + MatchUndefined: true, + }, + }, + }, + skip: false, + }, + } + + for _, tt := range testCases { + origVal := useOTTLBridge.IsEnabled() + err := featuregate.GlobalRegistry().Set("filter.filterlog.useOTTLBridge", true) + assert.NoError(b, err) + + skipExpr, err := NewSkipExpr(tt.mc) + assert.NoError(b, err) + + log := plog.NewLogRecord() + log.Body().SetStr("body") + log.SetSeverityNumber(plog.SeverityNumberUnspecified) + + resource := pcommon.NewResource() + + scope := pcommon.NewInstrumentationScope() + + tCtx := ottllog.NewTransformContext(log, scope, resource) + + b.Run(tt.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + var skip bool + skip, err = skipExpr.Eval(context.Background(), tCtx) + assert.NoError(b, err) + assert.Equal(b, tt.skip, skip) + } + }) + + err = featuregate.GlobalRegistry().Set("filter.filterlog.useOTTLBridge", origVal) + assert.NoError(b, err) + } +} diff --git a/internal/filter/filterottl/bridge.go b/internal/filter/filterottl/bridge.go index 7261e63be563..398fc09aa759 100644 --- a/internal/filter/filterottl/bridge.go +++ b/internal/filter/filterottl/bridge.go @@ -14,6 +14,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterconfig" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterset" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottllog" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlspan" ) @@ -25,6 +26,8 @@ const ( scopeVersionStaticStatement = `instrumentation_scope.version == "%v"` attributesStaticStatement = `attributes["%v"] == %v` resourceAttributesStaticStatement = `resource.attributes["%v"] == %v` + bodyStaticStatement = `body.string == "%v"` + severityTextStaticStatement = `severity_text == "%v"` serviceNameRegexStatement = `IsMatch(resource.attributes["service.name"], "%v")` spanNameRegexStatement = `IsMatch(name, "%v")` @@ -33,8 +36,53 @@ const ( scopeVersionRegexStatement = `IsMatch(instrumentation_scope.version, "%v")` attributesRegexStatement = `IsMatch(attributes["%v"], "%v")` resourceAttributesRegexStatement = `IsMatch(resource.attributes["%v"], "%v")` + bodyRegexStatement = `IsMatch(body.string, "%v")` + severityTextRegexStatement = `IsMatch(severity_text, "%v")` + + // Boolean expression for existing severity number matching + // a -> lr.SeverityNumber() == plog.SeverityNumberUnspecified + // b -> snm.matchUndefined + // c -> lr.SeverityNumber() >= snm.minSeverityNumber + // (a AND b) OR ( NOT a AND c) + // a b c X + // 0 0 0 0 + // 0 0 1 1 + // 0 1 0 0 + // 0 1 1 1 + // 1 0 0 0 + // 1 0 1 0 + // 1 1 0 1 + // 1 1 1 1 + severityNumberStatement = `((severity_number == SEVERITY_NUMBER_UNSPECIFIED and %v) or (severity_number != SEVERITY_NUMBER_UNSPECIFIED and severity_number >= %d))` ) +func NewLogSkipExprBridge(mc *filterconfig.MatchConfig) (expr.BoolExpr[ottllog.TransformContext], error) { + statements := make([]string, 0, 2) + if mc.Include != nil { + if err := mc.Include.ValidateForLogs(); err != nil { + return nil, err + } + statement, err := createStatement(*mc.Include) + if err != nil { + return nil, err + } + statements = append(statements, fmt.Sprintf("not (%v)", statement)) + } + + if mc.Exclude != nil { + if err := mc.Exclude.ValidateForLogs(); err != nil { + return nil, err + } + statement, err := createStatement(*mc.Exclude) + if err != nil { + return nil, err + } + statements = append(statements, fmt.Sprintf("%v", statement)) + } + + return NewBoolExprForLog(statements, StandardLogFuncs(), ottl.PropagateError, component.TelemetrySettings{Logger: zap.NewNop()}) +} + func NewSpanSkipExprBridge(mc *filterconfig.MatchConfig) (expr.BoolExpr[ottlspan.TransformContext], error) { statements := make([]string, 0, 2) if mc.Include != nil { @@ -57,92 +105,170 @@ func NewSpanSkipExprBridge(mc *filterconfig.MatchConfig) (expr.BoolExpr[ottlspan } func createStatement(mp filterconfig.MatchProperties) (string, error) { - serviceNameConditions, spanNameConditions, spanKindConditions, scopeNameConditions, scopeVersionConditions, attributeConditions, resourceAttributeConditions, err := createConditions(mp) + c, err := createConditions(mp) if err != nil { return "", err } var conditions []string var format string - if serviceNameConditions != nil { - if len(serviceNameConditions) > 1 { + if c.serviceNameConditions != nil { + if len(c.serviceNameConditions) > 1 { format = "(%v)" } else { format = "%v" } - conditions = append(conditions, fmt.Sprintf(format, strings.Join(serviceNameConditions, " or "))) + conditions = append(conditions, fmt.Sprintf(format, strings.Join(c.serviceNameConditions, " or "))) } - if spanNameConditions != nil { - if len(spanNameConditions) > 1 { + if c.spanNameConditions != nil { + if len(c.spanNameConditions) > 1 { format = "(%v)" } else { format = "%v" } - conditions = append(conditions, fmt.Sprintf(format, strings.Join(spanNameConditions, " or "))) + conditions = append(conditions, fmt.Sprintf(format, strings.Join(c.spanNameConditions, " or "))) } - if spanKindConditions != nil { - if len(spanKindConditions) > 1 { + if c.spanKindConditions != nil { + if len(c.spanKindConditions) > 1 { format = "(%v)" } else { format = "%v" } - conditions = append(conditions, fmt.Sprintf(format, strings.Join(spanKindConditions, " or "))) + conditions = append(conditions, fmt.Sprintf(format, strings.Join(c.spanKindConditions, " or "))) } - if scopeNameConditions != nil { - if len(scopeNameConditions) > 1 { + if c.scopeNameConditions != nil { + if len(c.scopeNameConditions) > 1 { format = "(%v)" } else { format = "%v" } - conditions = append(conditions, fmt.Sprintf(format, strings.Join(scopeNameConditions, " or "))) + conditions = append(conditions, fmt.Sprintf(format, strings.Join(c.scopeNameConditions, " or "))) } - if scopeVersionConditions != nil { - if len(scopeVersionConditions) > 1 { + if c.scopeVersionConditions != nil { + if len(c.scopeVersionConditions) > 1 { format = "(%v)" } else { format = "%v" } - conditions = append(conditions, fmt.Sprintf(format, strings.Join(scopeVersionConditions, " or "))) + conditions = append(conditions, fmt.Sprintf(format, strings.Join(c.scopeVersionConditions, " or "))) } - if attributeConditions != nil { - conditions = append(conditions, fmt.Sprintf("%v", strings.Join(attributeConditions, " and "))) + if c.attributeConditions != nil { + conditions = append(conditions, fmt.Sprintf("%v", strings.Join(c.attributeConditions, " and "))) } - if resourceAttributeConditions != nil { - conditions = append(conditions, fmt.Sprintf("%v", strings.Join(resourceAttributeConditions, " and "))) + if c.resourceAttributeConditions != nil { + conditions = append(conditions, fmt.Sprintf("%v", strings.Join(c.resourceAttributeConditions, " and "))) + } + if c.bodyConditions != nil { + if len(c.bodyConditions) > 1 { + format = "(%v)" + } else { + format = "%v" + } + conditions = append(conditions, fmt.Sprintf(format, strings.Join(c.bodyConditions, " or "))) + } + if c.severityTextConditions != nil { + if len(c.severityTextConditions) > 1 { + format = "(%v)" + } else { + format = "%v" + } + conditions = append(conditions, fmt.Sprintf(format, strings.Join(c.severityTextConditions, " or "))) + } + if c.severityNumberCondition != nil { + conditions = append(conditions, *c.severityNumberCondition) } return strings.Join(conditions, " and "), nil } -func createConditions(mp filterconfig.MatchProperties) ([]string, []string, []string, []string, []string, []string, []string, error) { - serviceNameStatement, spanNameStatement, spanKindStatement, scopeNameStatement, scopeVersionStatement, attrStatement, resourceAttrStatement, err := createStatementTemplates(mp.MatchType) +type conditionStatements struct { + serviceNameConditions []string + spanNameConditions []string + spanKindConditions []string + scopeNameConditions []string + scopeVersionConditions []string + attributeConditions []string + resourceAttributeConditions []string + bodyConditions []string + severityTextConditions []string + severityNumberCondition *string +} + +func createConditions(mp filterconfig.MatchProperties) (conditionStatements, error) { + templates, err := createStatementTemplates(mp.MatchType) if err != nil { - return nil, nil, nil, nil, nil, nil, nil, err + return conditionStatements{}, err } - serviceNameConditions := createBasicConditions(serviceNameStatement, mp.Services) - spanNameConditions := createBasicConditions(spanNameStatement, mp.SpanNames) - spanKindConditions := createBasicConditions(spanKindStatement, mp.SpanKinds) - scopeNameConditions, scopeVersionConditions := createLibraryConditions(scopeNameStatement, scopeVersionStatement, mp.Libraries) - attributeConditions := createAttributeConditions(attrStatement, mp.Attributes, mp.MatchType) - resourceAttributeConditions := createAttributeConditions(resourceAttrStatement, mp.Resources, mp.MatchType) + serviceNameConditions := createBasicConditions(templates.serviceNameStatement, mp.Services) + spanNameConditions := createBasicConditions(templates.spanNameStatement, mp.SpanNames) + spanKindConditions := createBasicConditions(templates.spanKindStatement, mp.SpanKinds) + scopeNameConditions, scopeVersionConditions := createLibraryConditions(templates.scopeNameStatement, templates.scopeVersionStatement, mp.Libraries) + attributeConditions := createAttributeConditions(templates.attrStatement, mp.Attributes, mp.MatchType) + resourceAttributeConditions := createAttributeConditions(templates.resourceAttrStatement, mp.Resources, mp.MatchType) + bodyConditions := createBasicConditions(templates.bodyStatement, mp.LogBodies) + severityTextConditions := createBasicConditions(templates.severityTextStatement, mp.LogSeverityTexts) + severityNumberCondition := createSeverityNumberConditions(mp.LogSeverityNumber) - return serviceNameConditions, spanNameConditions, spanKindConditions, scopeNameConditions, scopeVersionConditions, attributeConditions, resourceAttributeConditions, nil + return conditionStatements{ + serviceNameConditions: serviceNameConditions, + spanNameConditions: spanNameConditions, + spanKindConditions: spanKindConditions, + scopeNameConditions: scopeNameConditions, + scopeVersionConditions: scopeVersionConditions, + attributeConditions: attributeConditions, + resourceAttributeConditions: resourceAttributeConditions, + bodyConditions: bodyConditions, + severityTextConditions: severityTextConditions, + severityNumberCondition: severityNumberCondition, + }, nil } -func createStatementTemplates(matchType filterset.MatchType) (string, string, string, string, string, string, string, error) { +type statementTemplates struct { + serviceNameStatement string + spanNameStatement string + spanKindStatement string + scopeNameStatement string + scopeVersionStatement string + attrStatement string + resourceAttrStatement string + bodyStatement string + severityTextStatement string +} + +func createStatementTemplates(matchType filterset.MatchType) (statementTemplates, error) { switch matchType { case filterset.Strict: - return serviceNameStaticStatement, spanNameStaticStatement, spanKindStaticStatement, scopeNameStaticStatement, scopeVersionStaticStatement, attributesStaticStatement, resourceAttributesStaticStatement, nil + return statementTemplates{ + serviceNameStatement: serviceNameStaticStatement, + spanNameStatement: spanNameStaticStatement, + spanKindStatement: spanKindStaticStatement, + scopeNameStatement: scopeNameStaticStatement, + scopeVersionStatement: scopeVersionStaticStatement, + attrStatement: attributesStaticStatement, + resourceAttrStatement: resourceAttributesStaticStatement, + bodyStatement: bodyStaticStatement, + severityTextStatement: severityTextStaticStatement, + }, nil case filterset.Regexp: - return serviceNameRegexStatement, spanNameRegexStatement, spanKindRegexStatement, scopeNameRegexStatement, scopeVersionRegexStatement, attributesRegexStatement, resourceAttributesRegexStatement, nil + return statementTemplates{ + serviceNameStatement: serviceNameRegexStatement, + spanNameStatement: spanNameRegexStatement, + spanKindStatement: spanKindRegexStatement, + scopeNameStatement: scopeNameRegexStatement, + scopeVersionStatement: scopeVersionRegexStatement, + attrStatement: attributesRegexStatement, + resourceAttrStatement: resourceAttributesRegexStatement, + bodyStatement: bodyRegexStatement, + severityTextStatement: severityTextRegexStatement, + }, nil default: - return "", "", "", "", "", "", "", filterset.NewUnrecognizedMatchTypeError(matchType) + return statementTemplates{}, filterset.NewUnrecognizedMatchTypeError(matchType) } } func createBasicConditions(template string, input []string) []string { var conditions []string - for _, serviceName := range input { - conditions = append(conditions, fmt.Sprintf(template, serviceName)) + for _, i := range input { + conditions = append(conditions, fmt.Sprintf(template, i)) } return conditions } @@ -181,3 +307,11 @@ func convertAttribute(value any) string { return fmt.Sprintf(`%v`, val) } } + +func createSeverityNumberConditions(severityNumberProperties *filterconfig.LogSeverityNumberMatchProperties) *string { + if severityNumberProperties == nil { + return nil + } + severityNumberCondition := fmt.Sprintf(severityNumberStatement, severityNumberProperties.MatchUndefined, severityNumberProperties.Min) + return &severityNumberCondition +} diff --git a/internal/filter/filterspan/filterspan_test.go b/internal/filter/filterspan/filterspan_test.go index 38c8562cf680..b849e8203a89 100644 --- a/internal/filter/filterspan/filterspan_test.go +++ b/internal/filter/filterspan/filterspan_test.go @@ -1160,7 +1160,7 @@ func Test_NewSkipExpr_With_Bridge(t *testing.T) { // complex { - name: "single static resource attribute include", + name: "complex", condition: &filterconfig.MatchConfig{ Include: &filterconfig.MatchProperties{ Config: filterset.Config{ @@ -1258,7 +1258,7 @@ func BenchmarkFilterspan_NewSkipExpr(b *testing.B) { for _, tt := range testCases { origVal := useOTTLBridge.IsEnabled() - err := featuregate.GlobalRegistry().Set("useOTTLBridge", true) + err := featuregate.GlobalRegistry().Set("filter.filterspan.useOTTLBridge", true) assert.NoError(b, err) skipExpr, err := NewSkipExpr(tt.mc) @@ -1290,7 +1290,7 @@ func BenchmarkFilterspan_NewSkipExpr(b *testing.B) { } }) - err = featuregate.GlobalRegistry().Set("useOTTLBridge", origVal) + err = featuregate.GlobalRegistry().Set("filter.filterspan.useOTTLBridge", origVal) assert.NoError(b, err) } }