-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[pkg/ottl] Add the ottl.ParserCollection utility #36174
base: main
Are you sure you want to change the base?
[pkg/ottl] Add the ottl.ParserCollection utility #36174
Conversation
var _ ParsedStatementConverter[any, StatementsGetter, any] = func( | ||
_ *ParserCollection[StatementsGetter, any], | ||
_ *Parser[any], | ||
_ string, | ||
_ StatementsGetter, | ||
_ []*Statement[any]) (any, error) { | ||
return nil, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as #36174 (comment), but in this case it's not really necessary considering this interface is declared inside this same file.
We can definitely replace it by a comment, but IMO, I'd keep it as a safe-guard, considering changing this function signature could break the parser collection here, and in all its external usages. WDYT?
var _ interface { | ||
ParseStatements(statements []string) ([]*Statement[any], error) | ||
} = (*Parser[any])(nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this enforcing that we'll be able to make the reflective call to ParseStatements
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's an interface safe guard and is only being used to statically check the ParseStatements
signature at compile time. It fails to compile If for some reason the ottl.Parser.ParseStatements
changes (which could make the reflective call to fail).
type ottlParserWrapper[S StatementsGetter] struct { | ||
parser reflect.Value | ||
prependContextToStatementPaths func(context string, statement string) (string, error) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this type need a generic? I don't see where it uses it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! that's correct, it does not need to be generic.
|
||
// 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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is S StatementsGetter
required? Would specifying parameters of type StatementGetter
sufficient? Or even better can we get away with []string
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is S StatementsGetter required?
Not really, I think we can drop it from the ParserCollection
. It's only necessary for the ParsedStatementConverter
.
// 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of taking an interface can this take []string
like the parser's ParseStatements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'd possible, but the idea of making it an interface was giving theParsedStatementConverter
functions more flexibility to receive extra data, without the need of adding an extra argument for some custom data.
For example, the transformprocessor
builds the parser collection and them invokes the ParseContextStatements
function passing on a custom type ContextStatements
argument. Besides the Statements []string
property, that type has a Conditions []string
property used to convert (see) the parsed ottl.Statement[K]
into [R]
, which is only known after the parser collection is built.
In summary, having it as an interface allows the ParsedStatementConverter
functions to access customised ParseStatements
input arguments, avoiding extra parameters on the ParsedStatementConverter
and making the parser collection reusable.
WDYT?
// 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of taking an generic S
can this take []string like the parser's ParseStatements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not against the use of reflection and generics (OTTL uses both a lot already), but we've also taken steps in the ottl context packages to make it easier to use OTTL in a component without needing to know about OTTL's generics. ottllog.NewParser
exists so users don't have to do ottl.Parser[ottllog.TransformContext]
. I believe we'll want to do something similar with this new API - It needs to be generic enough for others to provide their own context collections (thanks reflection), but our contexts should be able to hide this feature.
I believe this solution supports this but I want to call it out. In my head I want to still be able to do ottllog.NewParser
and it takes care of using a ParserCollection
behind the scenes, enforcing the valid contexts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this solution supports this but I want to call it out. In my head I want to still be able to do ottllog.NewParser and it takes care of using a ParserCollection behind the scenes, enforcing the valid contexts.
That's interesting, but I think it would require another type of API (builder perhaps) considering a single OTTL context (ottllog.NewParser
) woudn't be able to build the whole parser collection. For example, if I want to parse OTTL log
and metric
contexts statements, I'd create a parser collection as:
logParser, _ := ottllog.NewParser(ottlfuncs.StandardFuncs[ottllog.TransformContext](), telemetrySettings, ottllog.WithPathContextNames())
metricParser, _ := ottlmetric.NewParser(ottlfuncs.StandardFuncs[ottlmetric.TransformContext](), telemetrySettings, ottlmetric.WithPathContextNames())
pc, err := ottl.NewParserCollection[any](
telemetrySettings,
ottl.WithParserCollectionContext("log", &logParser, {...}),
ottl.WithParserCollectionContext("metric", &metricsParser, {...}),
)
pc.ParseStatements(statements{[]string{`set(log.attributes["key"], "value")`}})
It indeed does not completely hides the generic type, but I think it's somehow similar to the NewParser
functions.
In my head I want to still be able to do ottllog.NewParser and it takes care of using a ParserCollection behind the scenes, enforcing the valid contexts.
The valid contexts are still being enforced by the underline parser, and not by the parser collection, for example, given the previous parser collection and the following log
(new flat structure) statements:
- set(log.attributes["key"], "value") # ok
- set(log.attributes["key"], attributes["value"]) # Parser fails and report a missing context for the `attributes["value"]` path
- set(log.attributes["key"], log.attributes["value"]) # ok
- set(log.attributes["key"], log.resource.attributes["value"]) # ok
- set(log.attributes["key"], log.instrumentation_scope.attributes["value"]) # ok
- set(log.attributes["key"], log.metric.attributes["value"]) # Parser fails as `metric` is not a valid path context name for the ottllog.NewParser (WithPathContextNames)
- set(trace.attributes["key"], trace.attributes["value"]) # ParserCollection fails as it does not support parsing `trace` context.
Please note that in this approach/initial version, the access to lower contexts would still be handled by the contexts themselves, for example logs, being the parser collection used only to parse top level contexts (inferred from the statements).
Description
This PR is part of #29017, and adds the parser collection utility into the
ottl
package.This utility is based on the existing
transformprocessor
'sParserCollection
concept and is used to group context's parsers, abstracting the logic to infer the context from the statements, and/or rewriting them adding their paths's context if necessary.In summary, the general idea is: given the configured contexts, parsers and statements converters, pick the right parser, and transform the parsed result (
ottl.Statements[K]
) into something common to the parser collection ([R]
). For more details, please have a look at the transformprocessor` refactoring on the draft using this utility.Given the
ottl.Parser[K]
's have different type restrictions/generics (e.g.TransformContext
), this utility heavily rely on the Go's reflection api, being the exposed interface type-safe, but manipulating untyped objects under the hood.Considering this code is only executed during the bootstrap, IMO, it should be fine using reflection. I couldn't think about any other solution that would make this utility as generic as it's without reflection. Another approach would be hard-code all the existing ottl contexts's parsers on the collection, having fixed
WithParserX
functions per context and generating a direct dependency on theottl/contexts
package.Link to tracking issue
#29017
Testing
Unit tests
Documentation
No changes