From 3c37b131871d76a21552b6b179d66a52831bba67 Mon Sep 17 00:00:00 2001 From: edmocosta <11836452+edmocosta@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:19:48 +0100 Subject: [PATCH] Update draft --- pkg/ottl/parser_collection.go | 216 +++++++++++------- pkg/ottl/parser_collection_test.go | 211 ++++++++++++----- .../internal/common/logs.go | 4 +- .../internal/common/metrics.go | 6 +- .../internal/common/processor.go | 4 +- .../internal/common/traces.go | 6 +- 6 files changed, 292 insertions(+), 155 deletions(-) diff --git a/pkg/ottl/parser_collection.go b/pkg/ottl/parser_collection.go index ec70c403913d..04cf5a73d533 100644 --- a/pkg/ottl/parser_collection.go +++ b/pkg/ottl/parser_collection.go @@ -11,7 +11,9 @@ import ( "go.uber.org/zap" ) -var _ ottlParser[any] = (*Parser[any])(nil) +var _ interface { + ParseStatements(statements []string) ([]*Statement[any], error) +} = (*Parser[any])(nil) var _ ParsedStatementConverter[any, StatementsGetter, any] = func( _ *ParserCollection[StatementsGetter, any], @@ -22,65 +24,93 @@ var _ ParsedStatementConverter[any, StatementsGetter, any] = func( return nil, nil } -type ottlParser[K any] interface { - // ParseStatements is the same as Parser.ParseStatements - ParseStatements(statements []string) ([]*Statement[K], error) - // AppendStatementPathsContext is the same as Parser.AppendStatementPathsContext - AppendStatementPathsContext(context string, statement string) (string, error) -} - -// StatementsGetter represents the input statements to be parsed +// StatementsGetter represents a set of statements to be parsed type StatementsGetter interface { + // GetStatements retrieves the OTTL statements to be parsed GetStatements() []string } +// ottlParserWrapper wraps an ottl.Parser using reflection, so it can invoke exported +// methods without knowing its generic type (transform context). type ottlParserWrapper[S StatementsGetter] struct { - reflect.Value + parser reflect.Value + prependContextToStatementPaths func(context string, statement string) (string, error) } -func (g *ottlParserWrapper[S]) ParseStatements(statements []string) (reflect.Value, error) { - method := g.MethodByName("ParseStatements") - psr := method.Call([]reflect.Value{reflect.ValueOf(statements)}) - err := psr[1] - if !err.IsNil() { - return reflect.Value{}, err.Interface().(error) +func newParserWrapper[K any, S StatementsGetter](parser *Parser[K]) *ottlParserWrapper[S] { + return &ottlParserWrapper[S]{ + parser: reflect.ValueOf(parser), + prependContextToStatementPaths: parser.prependContextToStatementPaths, } - return psr[0], nil } -func (g *ottlParserWrapper[S]) AppendStatementPathsContext(context string, statement string) (string, error) { - method := g.MethodByName("AppendStatementPathsContext") - psr := method.Call([]reflect.Value{reflect.ValueOf(context), reflect.ValueOf(statement)}) - err := psr[1] +func (g *ottlParserWrapper[S]) parseStatements(statements []string) (reflect.Value, error) { + method := g.parser.MethodByName("ParseStatements") + parseStatementsRes := method.Call([]reflect.Value{reflect.ValueOf(statements)}) + err := parseStatementsRes[1] if !err.IsNil() { - return "", err.Interface().(error) + return reflect.Value{}, err.Interface().(error) } - return psr[0].Interface().(string), nil + return parseStatementsRes[0], nil } -func newParserWrapper[K any, S StatementsGetter](parser *Parser[K]) *ottlParserWrapper[S] { - return &ottlParserWrapper[S]{reflect.ValueOf(parser)} +func (g *ottlParserWrapper[S]) prependContextToStatementsPaths(context string, statements []string) ([]string, error) { + result := make([]string, 0, len(statements)) + for _, s := range statements { + prependedStatement, err := g.prependContextToStatementPaths(context, s) + if err != nil { + return nil, err + } + result = append(result, prependedStatement) + } + return result, nil } +// statementsConverterWrapper is reflection-based wrapper to the ParsedStatementConverter function, +// which does not require knowing all generic parameters to be called. type statementsConverterWrapper[S StatementsGetter] reflect.Value -func (s statementsConverterWrapper[S]) Call(in []reflect.Value) []reflect.Value { - return reflect.Value(s).Call(in) -} - func newStatementsConverterWrapper[K any, S StatementsGetter, R any](converter ParsedStatementConverter[K, S, R]) statementsConverterWrapper[S] { return statementsConverterWrapper[S](reflect.ValueOf(converter)) } -type contextParserWrapper[S StatementsGetter] struct { +func (s statementsConverterWrapper[S]) call( + parserCollection reflect.Value, + ottlParser *ottlParserWrapper[S], + context string, + statements S, + parsedStatements reflect.Value, +) (reflect.Value, error) { + result := reflect.Value(s).Call([]reflect.Value{ + parserCollection, + ottlParser.parser, + reflect.ValueOf(context), + reflect.ValueOf(statements), + parsedStatements, + }) + + resultValue := result[0] + resultError := result[1] + if !resultError.IsNil() { + return reflect.Value{}, resultError.Interface().(error) + } + + return resultValue, nil +} + +// parserCollectionParser holds an ottlParserWrapper and its respectively +// statementsConverter function. +type parserCollectionParser[S StatementsGetter] struct { ottlParser *ottlParserWrapper[S] statementsConverter statementsConverterWrapper[S] } +// ParserCollection is a configurable set of ottl.Parser that can handle multiple OTTL contexts +// parsings, inferring the context and choosing the right parser for the given statements. type ParserCollection[S StatementsGetter, R any] struct { - contextParsers map[string]*contextParserWrapper[S] - contextInferrer ContextInferrer - contextRewriteLogEnabled bool + contextParsers map[string]*parserCollectionParser[S] + contextInferrer contextInferrer + modifiedStatementLogging bool Settings component.TelemetrySettings ErrorMode ErrorMode } @@ -92,8 +122,9 @@ func NewParserCollection[S StatementsGetter, R any]( options ...ParserCollectionOption[S, R]) (*ParserCollection[S, R], error) { pc := &ParserCollection[S, R]{ - Settings: settings, - contextParsers: map[string]*contextParserWrapper[S]{}, + Settings: settings, + contextParsers: map[string]*parserCollectionParser[S]{}, + contextInferrer: defaultPriorityContextInferrer(), } for _, op := range options { @@ -103,13 +134,14 @@ func NewParserCollection[S StatementsGetter, R any]( } } - if pc.contextInferrer == nil { - pc.contextInferrer = NewDefaultContextInferrer() - } - return pc, nil } +// ParsedStatementConverter is a function that converts the parsed ottl.Statement[K] into +// a common representation to all parser collection contexts WithParserCollectionContext. +// Given each parser has its own transform context type, they must agree on a common type [R] +// so is can be returned by the ParserCollection.ParseStatements and ParserCollection.ParseStatementsWithContext +// functions. type ParsedStatementConverter[T any, S StatementsGetter, R any] func( collection *ParserCollection[S, R], parser *Parser[T], @@ -118,7 +150,7 @@ type ParsedStatementConverter[T any, S StatementsGetter, R any] func( parsedStatements []*Statement[T], ) (R, error) -func NewNopParsedStatementConverter[T any, S StatementsGetter]() ParsedStatementConverter[T, S, any] { +func newNopParsedStatementConverter[T any, S StatementsGetter]() ParsedStatementConverter[T, S, any] { return func( _ *ParserCollection[S, any], _ *Parser[T], @@ -129,16 +161,19 @@ func NewNopParsedStatementConverter[T any, S StatementsGetter]() ParsedStatement } } -func WithContextParser[K any, S StatementsGetter, R any]( +// WithParserCollectionContext configures an ottl.Parser for the given context. +// The provided ottl.Parser must be configured to support the provided context using +// the ottl.WithPathContextNames option. +func WithParserCollectionContext[K any, S StatementsGetter, R any]( context string, parser *Parser[K], converter ParsedStatementConverter[K, S, R], ) ParserCollectionOption[S, R] { return func(mp *ParserCollection[S, R]) error { - if len(parser.pathContextNames) == 0 { - WithPathContextNames[K]([]string{context})(parser) + if _, ok := parser.pathContextNames[context]; !ok { + return fmt.Errorf(`context "%s" must be a valid "%T" path context name`, context, parser) } - mp.contextParsers[context] = &contextParserWrapper[S]{ + mp.contextParsers[context] = &parserCollectionParser[S]{ ottlParser: newParserWrapper[K, S](parser), statementsConverter: newStatementsConverterWrapper(converter), } @@ -146,6 +181,8 @@ func WithContextParser[K any, S StatementsGetter, R any]( } } +// WithParserCollectionErrorMode has no effect on the ParserCollection, but might be used +// by the ParsedStatementConverter functions to handle/create StatementSequence. func WithParserCollectionErrorMode[S StatementsGetter, R any](errorMode ErrorMode) ParserCollectionOption[S, R] { return func(tp *ParserCollection[S, R]) error { tp.ErrorMode = errorMode @@ -153,91 +190,96 @@ func WithParserCollectionErrorMode[S StatementsGetter, R any](errorMode ErrorMod } } -func WithParserCollectionContextInferrer[S StatementsGetter, R any](contextInferrer ContextInferrer) ParserCollectionOption[S, R] { - return func(tp *ParserCollection[S, R]) error { - tp.contextInferrer = contextInferrer - return nil - } -} - -func WithParserCollectionContextRewriteLog[S StatementsGetter, R any](enabled bool) ParserCollectionOption[S, R] { +// EnableParserCollectionModifiedStatementLogging controls the statements modification logs. +// When enabled, it logs any statements modifications performed by the parsing operations, +// instructing users to rewrite the statements accordingly. +func EnableParserCollectionModifiedStatementLogging[S StatementsGetter, R any](enabled bool) ParserCollectionOption[S, R] { return func(tp *ParserCollection[S, R]) error { - tp.contextRewriteLogEnabled = enabled + tp.modifiedStatementLogging = enabled return nil } } +// ParseStatements parses the given statements into [R] using the configured context's ottl.Parser +// and subsequently calling the ParsedStatementConverter function. +// The statement's context is automatically inferred from the [Path.Context] values, choosing the +// highest priority context found. +// If no contexts are present in the statements, or if the inferred value is not supported by +// the [ParserCollection], it returns an error. +// If parsing the statements fails, it returns the underline [ottl.Parser.ParseStatements] error. func (pc *ParserCollection[S, R]) ParseStatements(statements S) (R, error) { - zero := *new(R) statementsValues := statements.GetStatements() - - inferredContext, err := pc.contextInferrer.Infer(statementsValues) + inferredContext, err := pc.contextInferrer.infer(statementsValues) if err != nil { - return zero, err + return *new(R), err } if inferredContext == "" { - return zero, fmt.Errorf("unable to infer context from statements [%v], path's first segment must be a valid context name", statementsValues) + return *new(R), fmt.Errorf("unable to infer context from statements [%v], path's first segment must be a valid context name", statementsValues) } return pc.ParseStatementsWithContext(inferredContext, statements, false) } -func (pc *ParserCollection[S, R]) ParseStatementsWithContext(context string, statements S, appendPathsContext bool) (R, error) { - zero := *new(R) +// ParseStatementsWithContext parses the given statements into [R] using the configured +// context's ottl.Parser and subsequently calling the ParsedStatementConverter function. +// Differently from ParseStatements, it uses the provided context and does not infer it +// automatically. The context valuer must be supported by the [ParserCollection], +// otherwise an error is returned. +// If the statement's Path does not provide their Path.Context value, the prependPathsContext +// argument should be set to true, so it rewrites the statements prepending the missing paths +// contexts. +// If parsing the statements fails, it returns the underline [ottl.Parser.ParseStatements] error. +func (pc *ParserCollection[S, R]) ParseStatementsWithContext(context string, statements S, prependPathsContext bool) (R, error) { contextParser, ok := pc.contextParsers[context] if !ok { - return zero, fmt.Errorf(`unknown context "%s" for stataments: %v`, context, statements.GetStatements()) + return *new(R), fmt.Errorf(`unknown context "%s" for stataments: %v`, context, statements.GetStatements()) } - var statementsValues []string - if appendPathsContext { + var err error + var parsingStatements []string + if prependPathsContext { originalStatements := statements.GetStatements() - for _, s := range originalStatements { - ctxStatement, err := contextParser.ottlParser.AppendStatementPathsContext(context, s) - if err != nil { - return zero, err - } - statementsValues = append(statementsValues, ctxStatement) + parsingStatements, err = contextParser.ottlParser.prependContextToStatementsPaths(context, originalStatements) + if err != nil { + return *new(R), err } - if pc.contextRewriteLogEnabled { - pc.logRewrittenStatements(originalStatements, statementsValues) + if pc.modifiedStatementLogging { + pc.logModifiedStatements(originalStatements, parsingStatements) } } else { - statementsValues = statements.GetStatements() + parsingStatements = statements.GetStatements() } - parsedStatements, err := contextParser.ottlParser.ParseStatements(statementsValues) + parsedStatements, err := contextParser.ottlParser.parseStatements(parsingStatements) if err != nil { - return zero, err + return *new(R), err } - scr := contextParser.statementsConverter.Call([]reflect.Value{ + convertedStatements, err := contextParser.statementsConverter.call( reflect.ValueOf(pc), - contextParser.ottlParser.Value, - reflect.ValueOf(context), - reflect.ValueOf(statements), + contextParser.ottlParser, + context, + statements, parsedStatements, - }) + ) - result := scr[0] - converterErr := scr[1] - if !converterErr.IsNil() { - return zero, converterErr.Interface().(error) + if err != nil { + return *new(R), err } - return result.Interface().(R), nil + return convertedStatements.Interface().(R), nil } -func (pc *ParserCollection[S, R]) logRewrittenStatements(originalStatements, rewrittenStatements []string) { +func (pc *ParserCollection[S, R]) logModifiedStatements(originalStatements, modifiedStatements []string) { var fields []zap.Field for i, original := range originalStatements { - if rewrittenStatements[i] != original { + if modifiedStatements[i] != original { statementKey := fmt.Sprintf("[%v]", i) fields = append(fields, zap.Dict( statementKey, zap.String("original", original), - zap.String("modified", rewrittenStatements[i])), + zap.String("modified", modifiedStatements[i])), ) } } diff --git a/pkg/ottl/parser_collection_test.go b/pkg/ottl/parser_collection_test.go index a33bec98cf36..b5d293f68a77 100644 --- a/pkg/ottl/parser_collection_test.go +++ b/pkg/ottl/parser_collection_test.go @@ -6,6 +6,7 @@ package ottl import ( "context" "errors" + "fmt" "reflect" "testing" @@ -13,6 +14,8 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/component/componenttest" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" ) type mockStatementsGetter struct { @@ -27,10 +30,18 @@ type mockFailingContextInferrer struct { err error } -func (r *mockFailingContextInferrer) Infer(_ []string) (string, error) { +func (r *mockFailingContextInferrer) infer(_ []string) (string, error) { return "", r.err } +type mockStaticContextInferrer struct { + value string +} + +func (r *mockStaticContextInferrer) infer(_ []string) (string, error) { + return r.value, nil +} + type mockSetArguments[K any] struct { Target Setter[K] Value Getter[K] @@ -57,11 +68,10 @@ func Test_NewParserCollection_OptionError(t *testing.T) { require.Error(t, err, "option error") } -func Test_WithContextParser(t *testing.T) { - parser := mockParser(t) - converter := NewNopParsedStatementConverter[any, mockStatementsGetter]() - - option := WithContextParser("testContext", parser, converter) +func Test_WithParserCollectionContext(t *testing.T) { + ps := mockParser(t, WithPathContextNames[any]([]string{"testContext"})) + conv := newNopParsedStatementConverter[any, mockStatementsGetter]() + option := WithParserCollectionContext("testContext", ps, conv) pc, err := NewParserCollection[mockStatementsGetter, any](componenttest.NewNopTelemetrySettings(), option) require.NoError(t, err) @@ -69,8 +79,18 @@ func Test_WithContextParser(t *testing.T) { pw, exists := pc.contextParsers["testContext"] assert.True(t, exists) assert.NotNil(t, pw) - assert.Equal(t, reflect.ValueOf(parser), pw.ottlParser.Value) - assert.Equal(t, reflect.ValueOf(converter), reflect.Value(pw.statementsConverter)) + assert.Equal(t, reflect.ValueOf(ps), pw.ottlParser.parser) + assert.Equal(t, reflect.ValueOf(conv), reflect.Value(pw.statementsConverter)) +} + +func Test_WithParserCollectionContext_UnsupportedContext(t *testing.T) { + ps := mockParser(t, WithPathContextNames[any]([]string{"foo"})) + conv := newNopParsedStatementConverter[any, mockStatementsGetter]() + option := WithParserCollectionContext("bar", ps, conv) + + _, err := NewParserCollection[mockStatementsGetter, any](componenttest.NewNopTelemetrySettings(), option) + + require.ErrorContains(t, err, `context "bar" must be a valid "*ottl.Parser[interface {}]" path context name`) } func Test_WithParserCollectionErrorMode(t *testing.T) { @@ -84,23 +104,67 @@ func Test_WithParserCollectionErrorMode(t *testing.T) { require.Equal(t, PropagateError, pc.ErrorMode) } -func Test_WithParserCollectionContextInferrer(t *testing.T) { - inferrer := NewStaticContextInferrer("foo") +func Test_EnableParserCollectionModifiedStatementLogging_True(t *testing.T) { + ps := mockParser(t, WithPathContextNames[any]([]string{"dummy"})) + core, observedLogs := observer.New(zap.InfoLevel) + telemetrySettings := componenttest.NewNopTelemetrySettings() + telemetrySettings.Logger = zap.New(core) - pc, err := NewParserCollection[mockStatementsGetter, any]( - componenttest.NewNopTelemetrySettings(), - WithParserCollectionContextInferrer[mockStatementsGetter, any](inferrer), + pc, err := NewParserCollection( + telemetrySettings, + WithParserCollectionContext("dummy", ps, newNopParsedStatementConverter[any, mockStatementsGetter]()), + EnableParserCollectionModifiedStatementLogging[mockStatementsGetter, any](true), ) + require.NoError(t, err) + originalStatements := []string{ + `set(attributes["foo"], "foo")`, + `set(attributes["bar"], "bar")`, + } + + _, err = pc.ParseStatementsWithContext("dummy", mockStatementsGetter{originalStatements}, true) require.NoError(t, err) - require.NotNil(t, pc) - assert.Equal(t, inferrer, pc.contextInferrer) + + logEntries := observedLogs.TakeAll() + require.Len(t, logEntries, 1) + logEntry := logEntries[0] + require.Equal(t, zap.InfoLevel, logEntry.Level) + require.Contains(t, logEntry.Message, "one or more statements were modified") + logEntryStatements := logEntry.ContextMap()["statements"] + require.NotNil(t, logEntryStatements) + + for i, originalStatement := range originalStatements { + k := fmt.Sprintf("[%d]", i) + logEntryStatementContext := logEntryStatements.(map[string]any)[k] + require.Equal(t, logEntryStatementContext.(map[string]any)["original"], originalStatement) + modifiedStatement, err := ps.prependContextToStatementPaths("dummy", originalStatement) + require.NoError(t, err) + require.Equal(t, logEntryStatementContext.(map[string]any)["modified"], modifiedStatement) + } +} + +func Test_EnableParserCollectionModifiedStatementLogging_False(t *testing.T) { + ps := mockParser(t, WithPathContextNames[any]([]string{"dummy"})) + core, observedLogs := observer.New(zap.InfoLevel) + telemetrySettings := componenttest.NewNopTelemetrySettings() + telemetrySettings.Logger = zap.New(core) + + pc, err := NewParserCollection( + telemetrySettings, + WithParserCollectionContext("dummy", ps, newNopParsedStatementConverter[any, mockStatementsGetter]()), + EnableParserCollectionModifiedStatementLogging[mockStatementsGetter, any](false), + ) + require.NoError(t, err) + + _, err = pc.ParseStatementsWithContext("dummy", mockStatementsGetter{[]string{`set(attributes["foo"], "foo")`}}, true) + require.NoError(t, err) + require.Empty(t, observedLogs.TakeAll()) } func Test_NopParsedStatementConverter(t *testing.T) { type dummyContext struct{} - noop := NewNopParsedStatementConverter[dummyContext, mockStatementsGetter]() + noop := newNopParsedStatementConverter[dummyContext, mockStatementsGetter]() parsedStatements := []*Statement[dummyContext]{{}} convertedStatements, err := noop(nil, nil, "", mockStatementsGetter{values: []string{}}, parsedStatements) @@ -109,7 +173,7 @@ func Test_NopParsedStatementConverter(t *testing.T) { assert.Equal(t, parsedStatements, convertedStatements) } -func Test_NewParserCollection_DefaultInferrer(t *testing.T) { +func Test_NewParserCollection_DefaultContextInferrer(t *testing.T) { pc, err := NewParserCollection[mockStatementsGetter, any](componenttest.NewNopTelemetrySettings()) require.NoError(t, err) require.NotNil(t, pc) @@ -117,15 +181,14 @@ func Test_NewParserCollection_DefaultInferrer(t *testing.T) { } func Test_ParseStatements_Success(t *testing.T) { - parser := mockParser(t) + ps := mockParser(t, WithPathContextNames[any]([]string{"foo"})) pc, err := NewParserCollection( component.TelemetrySettings{}, - WithContextParser("foo", parser, NewNopParsedStatementConverter[any, mockStatementsGetter]()), - WithParserCollectionContextInferrer[mockStatementsGetter, any](NewStaticContextInferrer("foo")), + WithParserCollectionContext("foo", ps, newNopParsedStatementConverter[any, mockStatementsGetter]()), ) - require.NoError(t, err) + pc.contextInferrer = &mockStaticContextInferrer{"foo"} statements := mockStatementsGetter{values: []string{`set(foo.attributes["bar"], "foo")`, `set(foo.attributes["bar"], "bar")`}} result, err := pc.ParseStatements(statements) @@ -136,18 +199,25 @@ func Test_ParseStatements_Success(t *testing.T) { assert.NotNil(t, result) } -func Test_ParseStatements_MultipleParsers_Success(t *testing.T) { - fooParser := mockParser(t) - barParser := mockParser(t) +func Test_ParseStatements_MultipleContexts_Success(t *testing.T) { + fooParser := mockParser(t, WithPathContextNames[any]([]string{"foo"})) + barParser := mockParser(t, WithPathContextNames[any]([]string{"bar"})) + failingConverter := func( + _ *ParserCollection[mockStatementsGetter, any], + _ *Parser[any], + _ string, + _ mockStatementsGetter, + _ []*Statement[any]) (any, error) { + return nil, errors.New("failing converter") + } pc, err := NewParserCollection( component.TelemetrySettings{}, - WithContextParser("foo", fooParser, newFailingConverter()), - WithContextParser("bar", barParser, NewNopParsedStatementConverter[any, mockStatementsGetter]()), - WithParserCollectionContextInferrer[mockStatementsGetter, any](NewStaticContextInferrer("bar")), + WithParserCollectionContext("foo", fooParser, failingConverter), + WithParserCollectionContext("bar", barParser, newNopParsedStatementConverter[any, mockStatementsGetter]()), ) - require.NoError(t, err) + pc.contextInferrer = &mockStaticContextInferrer{"bar"} statements := mockStatementsGetter{values: []string{`set(bar.attributes["bar"], "foo")`, `set(bar.attributes["bar"], "bar")`}} result, err := pc.ParseStatements(statements) @@ -158,11 +228,10 @@ func Test_ParseStatements_MultipleParsers_Success(t *testing.T) { assert.NotNil(t, result) } -func Test_ParseStatements_EmptyContextInferenceError(t *testing.T) { - pc, err := NewParserCollection[mockStatementsGetter, any](component.TelemetrySettings{}, - WithParserCollectionContextInferrer[mockStatementsGetter, any](NewStaticContextInferrer("")), - ) +func Test_ParseStatements_NoContextInferredError(t *testing.T) { + pc, err := NewParserCollection[mockStatementsGetter, any](component.TelemetrySettings{}) require.NoError(t, err) + pc.contextInferrer = &mockStaticContextInferrer{""} statements := mockStatementsGetter{values: []string{`set(bar.attributes["bar"], "foo")`}} _, err = pc.ParseStatements(statements) @@ -171,10 +240,9 @@ func Test_ParseStatements_EmptyContextInferenceError(t *testing.T) { } func Test_ParseStatements_ContextInferenceError(t *testing.T) { - pc, err := NewParserCollection[mockStatementsGetter, any](component.TelemetrySettings{}, - WithParserCollectionContextInferrer[mockStatementsGetter, any](&mockFailingContextInferrer{err: errors.New("inference error")}), - ) + pc, err := NewParserCollection[mockStatementsGetter, any](component.TelemetrySettings{}) require.NoError(t, err) + pc.contextInferrer = &mockFailingContextInferrer{err: errors.New("inference error")} statements := mockStatementsGetter{values: []string{`set(bar.attributes["bar"], "foo")`}} _, err = pc.ParseStatements(statements) @@ -183,10 +251,9 @@ func Test_ParseStatements_ContextInferenceError(t *testing.T) { } func Test_ParseStatements_UnknownContextError(t *testing.T) { - pc, err := NewParserCollection[mockStatementsGetter, any](component.TelemetrySettings{}, - WithParserCollectionContextInferrer[mockStatementsGetter, any](NewStaticContextInferrer("foo")), - ) + pc, err := NewParserCollection[mockStatementsGetter, any](component.TelemetrySettings{}) require.NoError(t, err) + pc.contextInferrer = &mockStaticContextInferrer{"foo"} statements := mockStatementsGetter{values: []string{`set(foo.attributes["bar"], "foo")`}} _, err = pc.ParseStatements(statements) @@ -195,18 +262,17 @@ func Test_ParseStatements_UnknownContextError(t *testing.T) { } func Test_ParseStatements_ParseStatementsError(t *testing.T) { - parser := mockParser(t) - parser.pathParser = func(_ Path[any]) (GetSetter[any], error) { + ps := mockParser(t, WithPathContextNames[any]([]string{"foo"})) + ps.pathParser = func(_ Path[any]) (GetSetter[any], error) { return nil, errors.New("parse statements error") } pc, err := NewParserCollection( component.TelemetrySettings{}, - WithContextParser("foo", parser, NewNopParsedStatementConverter[any, mockStatementsGetter]()), - WithParserCollectionContextInferrer[mockStatementsGetter, any](NewStaticContextInferrer("foo")), + WithParserCollectionContext("foo", ps, newNopParsedStatementConverter[any, mockStatementsGetter]()), ) - require.NoError(t, err) + pc.contextInferrer = &mockStaticContextInferrer{"foo"} statements := mockStatementsGetter{values: []string{`set(foo.attributes["bar"], "foo")`}} _, err = pc.ParseStatements(statements) @@ -214,18 +280,17 @@ func Test_ParseStatements_ParseStatementsError(t *testing.T) { } func Test_ParseStatements_ConverterError(t *testing.T) { - parser := mockParser(t) - converter := func(_ *ParserCollection[mockStatementsGetter, any], _ *Parser[any], _ string, _ mockStatementsGetter, _ []*Statement[any]) (any, error) { + ps := mockParser(t, WithPathContextNames[any]([]string{"dummy"})) + conv := func(_ *ParserCollection[mockStatementsGetter, any], _ *Parser[any], _ string, _ mockStatementsGetter, _ []*Statement[any]) (any, error) { return nil, errors.New("converter error") } pc, err := NewParserCollection( component.TelemetrySettings{}, - WithContextParser("dummy", parser, converter), - WithParserCollectionContextInferrer[mockStatementsGetter, any](NewStaticContextInferrer("dummy")), + WithParserCollectionContext("dummy", ps, conv), ) - require.NoError(t, err) + pc.contextInferrer = &mockStaticContextInferrer{"dummy"} statements := mockStatementsGetter{values: []string{`set(dummy.attributes["bar"], "foo")`}} _, err = pc.ParseStatements(statements) @@ -233,7 +298,41 @@ func Test_ParseStatements_ConverterError(t *testing.T) { assert.EqualError(t, err, "converter error") } -func mockParser(t *testing.T) *Parser[any] { +func Test_ParseStatementsWithContext_UnknownContextError(t *testing.T) { + pc, err := NewParserCollection[mockStatementsGetter, any](component.TelemetrySettings{}) + require.NoError(t, err) + + statements := mockStatementsGetter{[]string{`set(attributes["bar"], "bar")`}} + _, err = pc.ParseStatementsWithContext("bar", statements, false) + + assert.ErrorContains(t, err, `unknown context "bar"`) +} + +func Test_ParseStatementsWithContext_PrependPathContext(t *testing.T) { + ps := mockParser(t, WithPathContextNames[any]([]string{"dummy"})) + pc, err := NewParserCollection( + component.TelemetrySettings{}, + WithParserCollectionContext("dummy", ps, newNopParsedStatementConverter[any, mockStatementsGetter]()), + ) + require.NoError(t, err) + + result, err := pc.ParseStatementsWithContext( + "dummy", + mockStatementsGetter{[]string{ + `set(attributes["foo"], "foo")`, + `set(attributes["bar"], "bar")`, + }}, + true, + ) + + require.NoError(t, err) + require.Len(t, result, 2) + parsedStatements := result.([]*Statement[any]) + assert.Equal(t, `set(dummy.attributes["foo"], "foo")`, parsedStatements[0].origText) + assert.Equal(t, `set(dummy.attributes["bar"], "bar")`, parsedStatements[1].origText) +} + +func mockParser(t *testing.T, options ...Option[any]) *Parser[any] { mockSetFactory := NewFactory("set", &mockSetArguments[any]{}, func(_ FunctionContext, _ Arguments) (ExprFunc[any], error) { return func(_ context.Context, _ any) (any, error) { @@ -241,19 +340,15 @@ func mockParser(t *testing.T) *Parser[any] { }, nil }) - parser, err := NewParser( + ps, err := NewParser( CreateFactoryMap[any](mockSetFactory), testParsePath[any], componenttest.NewNopTelemetrySettings(), - WithEnumParser[any](testParseEnum), + append([]Option[any]{ + WithEnumParser[any](testParseEnum), + }, options...)..., ) require.NoError(t, err) - return &parser -} - -func newFailingConverter() ParsedStatementConverter[any, mockStatementsGetter, any] { - return func(_ *ParserCollection[mockStatementsGetter, any], _ *Parser[any], _ string, _ mockStatementsGetter, _ []*Statement[any]) (any, error) { - return nil, errors.New("failing converter") - } + return &ps } diff --git a/processor/transformprocessor/internal/common/logs.go b/processor/transformprocessor/internal/common/logs.go index 9571bc7473bd..1e9d636b3168 100644 --- a/processor/transformprocessor/internal/common/logs.go +++ b/processor/transformprocessor/internal/common/logs.go @@ -63,7 +63,7 @@ func WithLogParser(functions map[string]ottl.Factory[ottllog.TransformContext]) if err != nil { return err } - return ottl.WithContextParser(ottllog.PathContextName, &logParser, convertLogStatements)(pc) + return ottl.WithParserCollectionContext(ottllog.PathContextName, &logParser, convertLogStatements)(pc) } } @@ -83,7 +83,7 @@ func WithLogErrorMode(errorMode ottl.ErrorMode) LogParserCollectionOption { func NewLogParserCollection(settings component.TelemetrySettings, options ...LogParserCollectionOption) (*LogParserCollection, error) { pcOptions := []ottl.ParserCollectionOption[ContextStatements, consumer.Logs]{ withCommonContextParsers[consumer.Logs](), - ottl.WithParserCollectionContextRewriteLog[ContextStatements, consumer.Logs](true), + ottl.EnableParserCollectionModifiedStatementLogging[ContextStatements, consumer.Logs](true), } for _, option := range options { diff --git a/processor/transformprocessor/internal/common/metrics.go b/processor/transformprocessor/internal/common/metrics.go index d6729005b8d5..9d5695dafa36 100644 --- a/processor/transformprocessor/internal/common/metrics.go +++ b/processor/transformprocessor/internal/common/metrics.go @@ -177,7 +177,7 @@ func WithMetricParser(functions map[string]ottl.Factory[ottlmetric.TransformCont if err != nil { return err } - opt := ottl.WithContextParser(ottlmetric.PathContextName, &metricParser, convertMetricStatements) + opt := ottl.WithParserCollectionContext(ottlmetric.PathContextName, &metricParser, convertMetricStatements) return opt(pc) } } @@ -197,7 +197,7 @@ func WithDataPointParser(functions map[string]ottl.Factory[ottldatapoint.Transfo if err != nil { return err } - return ottl.WithContextParser(ottldatapoint.PathContextName, &dataPointParser, convertDataPointStatements)(pc) + return ottl.WithParserCollectionContext(ottldatapoint.PathContextName, &dataPointParser, convertDataPointStatements)(pc) } } @@ -217,7 +217,7 @@ func WithMetricErrorMode(errorMode ottl.ErrorMode) MetricParserCollectionOption func NewMetricParserCollection(settings component.TelemetrySettings, options ...MetricParserCollectionOption) (*MetricParserCollection, error) { pcOptions := []ottl.ParserCollectionOption[ContextStatements, consumer.Metrics]{ withCommonContextParsers[consumer.Metrics](), - ottl.WithParserCollectionContextRewriteLog[ContextStatements, consumer.Metrics](true), + ottl.EnableParserCollectionModifiedStatementLogging[ContextStatements, consumer.Metrics](true), } for _, option := range options { diff --git a/processor/transformprocessor/internal/common/processor.go b/processor/transformprocessor/internal/common/processor.go index b32f19d142a3..bf7857590859 100644 --- a/processor/transformprocessor/internal/common/processor.go +++ b/processor/transformprocessor/internal/common/processor.go @@ -185,12 +185,12 @@ func withCommonContextParsers[R any]() ottl.ParserCollectionOption[ContextStatem return err } - err = ottl.WithContextParser[ottlresource.TransformContext, ContextStatements, R](ottlresource.PathContextName, &rp, parseResourceContextStatements)(pc) + err = ottl.WithParserCollectionContext[ottlresource.TransformContext, ContextStatements, R](ottlresource.PathContextName, &rp, parseResourceContextStatements)(pc) if err != nil { return err } - err = ottl.WithContextParser[ottlscope.TransformContext, ContextStatements, R](ottlscope.PathContextName, &sp, parseScopeContextStatements)(pc) + err = ottl.WithParserCollectionContext[ottlscope.TransformContext, ContextStatements, R](ottlscope.PathContextName, &sp, parseScopeContextStatements)(pc) if err != nil { return err } diff --git a/processor/transformprocessor/internal/common/traces.go b/processor/transformprocessor/internal/common/traces.go index f92274328f3a..f2f396ccb7ce 100644 --- a/processor/transformprocessor/internal/common/traces.go +++ b/processor/transformprocessor/internal/common/traces.go @@ -105,7 +105,7 @@ func WithSpanParser(functions map[string]ottl.Factory[ottlspan.TransformContext] if err != nil { return err } - return ottl.WithContextParser(ottlspan.PathContextName, &parser, convertSpanStatements)(pc) + return ottl.WithParserCollectionContext(ottlspan.PathContextName, &parser, convertSpanStatements)(pc) } } @@ -124,7 +124,7 @@ func WithSpanEventParser(functions map[string]ottl.Factory[ottlspanevent.Transfo if err != nil { return err } - return ottl.WithContextParser(ottlspanevent.PathContextName, &parser, convertSpanEventStatements)(pc) + return ottl.WithParserCollectionContext(ottlspanevent.PathContextName, &parser, convertSpanEventStatements)(pc) } } @@ -144,7 +144,7 @@ func WithTraceErrorMode(errorMode ottl.ErrorMode) TraceParserCollectionOption { func NewTraceParserCollection(settings component.TelemetrySettings, options ...TraceParserCollectionOption) (*TraceParserCollection, error) { pcOptions := []ottl.ParserCollectionOption[ContextStatements, consumer.Traces]{ withCommonContextParsers[consumer.Traces](), - ottl.WithParserCollectionContextRewriteLog[ContextStatements, consumer.Traces](true), + ottl.EnableParserCollectionModifiedStatementLogging[ContextStatements, consumer.Traces](true), } for _, option := range options {