Skip to content

Commit

Permalink
[pkg/ottl] Add ability to parse conditions (open-telemetry#29315)
Browse files Browse the repository at this point in the history
**Description:** 
This PR adds a new public API to support parsing and using conditions. 

This implementation opted to add ParseConditions directly to the
existing Parser instead of creating a second Parser struct. Since any
Parser needs the Context, Functions, PathExpressionParser, EnumParser,
and telemetry settings the 2 structs would be incredibly similar.

See these structs implemented in components here:
open-telemetry#29294

**Link to tracking Issue:** <Issue number if applicable>
Works towards
open-telemetry#13545

**Testing:** <Describe what testing was performed and which tests were
added.>
Added tests

**Documentation:** <Describe the documentation added.>
Added godoc strings
  • Loading branch information
TylerHelmuth authored and RoryCrispin committed Nov 24, 2023
1 parent ae47fb6 commit 4466565
Show file tree
Hide file tree
Showing 3 changed files with 370 additions and 8 deletions.
27 changes: 27 additions & 0 deletions .chloggen/ottl-condition-parser-add-interfaces.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add ability to independently parse OTTL conditions.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [29315]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
83 changes: 76 additions & 7 deletions pkg/ottl/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ func (e *ErrorMode) UnmarshalText(text []byte) error {
}
}

type Parser[K any] struct {
functions map[string]Factory[K]
pathParser PathExpressionParser[K]
enumParser EnumParser
telemetrySettings component.TelemetrySettings
}

// Statement holds a top level Statement for processing telemetry data. A Statement is a combination of a function
// invocation and the boolean expression to match telemetry for invoking the function.
type Statement[K any] struct {
Expand Down Expand Up @@ -66,6 +59,26 @@ func (s *Statement[K]) Execute(ctx context.Context, tCtx K) (any, bool, error) {
return result, condition, nil
}

// Condition holds a top level Condition. A Condition is a boolean expression to match telemetry.
type Condition[K any] struct {
condition BoolExpr[K]
origText string
}

// Eval returns true if the condition was met for the given TransformContext and false otherwise.
func (c *Condition[K]) Eval(ctx context.Context, tCtx K) (bool, error) {
return c.condition.Eval(ctx, tCtx)
}

// Parser provides the means to parse OTTL Statements and Conditions given a specific set of functions,
// a PathExpressionParser, and an EnumParser.
type Parser[K any] struct {
functions map[string]Factory[K]
pathParser PathExpressionParser[K]
enumParser EnumParser
telemetrySettings component.TelemetrySettings
}

func NewParser[K any](
functions map[string]Factory[K],
pathParser PathExpressionParser[K],
Expand Down Expand Up @@ -143,7 +156,49 @@ func (p *Parser[K]) ParseStatement(statement string) (*Statement[K], error) {
}, nil
}

// ParseConditions parses string conditions into a Condition slice ready for execution.
// Returns a slice of Condition and a nil error on successful parsing.
// If parsing fails, returns nil and an error containing each error per failed condition.
func (p *Parser[K]) ParseConditions(conditions []string) ([]*Condition[K], error) {
parsedConditions := make([]*Condition[K], 0, len(conditions))
var parseErrs []error

for _, condition := range conditions {
ps, err := p.ParseCondition(condition)
if err != nil {
parseErrs = append(parseErrs, fmt.Errorf("unable to parse OTTL condition %q: %w", condition, err))
continue
}
parsedConditions = append(parsedConditions, ps)
}

if len(parseErrs) > 0 {
return nil, errors.Join(parseErrs...)
}

return parsedConditions, nil
}

// ParseCondition parses a single string condition into a Condition objects ready for execution.
// Returns an Condition and a nil error on successful parsing.
// If parsing fails, returns nil and an error.
func (p *Parser[K]) ParseCondition(condition string) (*Condition[K], error) {
parsed, err := parseCondition(condition)
if err != nil {
return nil, err
}
expression, err := p.newBoolExpr(parsed)
if err != nil {
return nil, err
}
return &Condition[K]{
condition: expression,
origText: condition,
}, nil
}

var parser = newParser[parsedStatement]()
var conditionParser = newParser[booleanExpression]()

func parseStatement(raw string) (*parsedStatement, error) {
parsed, err := parser.ParseString("", raw)
Expand All @@ -159,6 +214,20 @@ func parseStatement(raw string) (*parsedStatement, error) {
return parsed, nil
}

func parseCondition(raw string) (*booleanExpression, error) {
parsed, err := conditionParser.ParseString("", raw)

if err != nil {
return nil, fmt.Errorf("condition has invalid syntax: %w", err)
}
err = parsed.checkForCustomError()
if err != nil {
return nil, err
}

return parsed, nil
}

// newParser returns a parser that can be used to read a string into a parsedStatement. An error will be returned if the string
// is not formatted for the DSL.
func newParser[G any]() *participle.Parser[G] {
Expand Down
Loading

0 comments on commit 4466565

Please sign in to comment.