123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- // Copyright The OpenTelemetry Authors
- // SPDX-License-Identifier: Apache-2.0
- package ottl // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
- import (
- "context"
- "errors"
- "fmt"
- "strings"
- "github.com/alecthomas/participle/v2"
- "go.opentelemetry.io/collector/component"
- "go.uber.org/zap"
- )
- type ErrorMode string
- const (
- IgnoreError ErrorMode = "ignore"
- PropagateError ErrorMode = "propagate"
- )
- func (e *ErrorMode) UnmarshalText(text []byte) error {
- str := ErrorMode(strings.ToLower(string(text)))
- switch str {
- case IgnoreError, PropagateError:
- *e = str
- return nil
- default:
- return fmt.Errorf("unknown error mode %v", str)
- }
- }
- // 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 {
- function Expr[K]
- condition BoolExpr[K]
- origText string
- }
- // Execute is a function that will execute the statement's function if the statement's condition is met.
- // Returns true if the function was run, returns false otherwise.
- // If the statement contains no condition, the function will run and true will be returned.
- // In addition, the functions return value is always returned.
- func (s *Statement[K]) Execute(ctx context.Context, tCtx K) (any, bool, error) {
- condition, err := s.condition.Eval(ctx, tCtx)
- if err != nil {
- return nil, false, err
- }
- var result any
- if condition {
- result, err = s.function.Eval(ctx, tCtx)
- if err != nil {
- return nil, true, err
- }
- }
- 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],
- settings component.TelemetrySettings,
- options ...Option[K],
- ) (Parser[K], error) {
- if settings.Logger == nil {
- return Parser[K]{}, fmt.Errorf("logger cannot be nil")
- }
- p := Parser[K]{
- functions: functions,
- pathParser: pathParser,
- enumParser: func(*EnumSymbol) (*Enum, error) {
- return nil, fmt.Errorf("enums aren't supported for the current context: %T", new(K))
- },
- telemetrySettings: settings,
- }
- for _, opt := range options {
- opt(&p)
- }
- return p, nil
- }
- type Option[K any] func(*Parser[K])
- func WithEnumParser[K any](parser EnumParser) Option[K] {
- return func(p *Parser[K]) {
- p.enumParser = parser
- }
- }
- // ParseStatements parses string statements into ottl.Statement objects ready for execution.
- // Returns a slice of statements and a nil error on successful parsing.
- // If parsing fails, returns nil and a joined error containing each error per failed statement.
- func (p *Parser[K]) ParseStatements(statements []string) ([]*Statement[K], error) {
- parsedStatements := make([]*Statement[K], 0, len(statements))
- var parseErrs []error
- for _, statement := range statements {
- ps, err := p.ParseStatement(statement)
- if err != nil {
- parseErrs = append(parseErrs, fmt.Errorf("unable to parse OTTL statement %q: %w", statement, err))
- continue
- }
- parsedStatements = append(parsedStatements, ps)
- }
- if len(parseErrs) > 0 {
- return nil, errors.Join(parseErrs...)
- }
- return parsedStatements, nil
- }
- // ParseStatement parses a single string statement into a Statement struct ready for execution.
- // Returns a Statement and a nil error on successful parsing.
- // If parsing fails, returns nil and an error.
- func (p *Parser[K]) ParseStatement(statement string) (*Statement[K], error) {
- parsed, err := parseStatement(statement)
- if err != nil {
- return nil, err
- }
- function, err := p.newFunctionCall(parsed.Editor)
- if err != nil {
- return nil, err
- }
- expression, err := p.newBoolExpr(parsed.WhereClause)
- if err != nil {
- return nil, err
- }
- return &Statement[K]{
- function: function,
- condition: expression,
- origText: statement,
- }, 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)
- if err != nil {
- return nil, fmt.Errorf("statement has invalid syntax: %w", err)
- }
- err = parsed.checkForCustomError()
- if err != nil {
- return nil, err
- }
- 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] {
- lex := buildLexer()
- parser, err := participle.Build[G](
- participle.Lexer(lex),
- participle.Unquote("String"),
- participle.Elide("whitespace"),
- participle.UseLookahead(participle.MaxLookahead), // Allows negative lookahead to work properly in 'value' for 'mathExprLiteral'.
- )
- if err != nil {
- panic("Unable to initialize parser; this is a programming error in OTTL:" + err.Error())
- }
- return parser
- }
- // Statements represents a list of statements that will be executed sequentially for a TransformContext.
- type Statements[K any] struct {
- statements []*Statement[K]
- errorMode ErrorMode
- telemetrySettings component.TelemetrySettings
- }
- type StatementsOption[K any] func(*Statements[K])
- func WithErrorMode[K any](errorMode ErrorMode) StatementsOption[K] {
- return func(s *Statements[K]) {
- s.errorMode = errorMode
- }
- }
- func NewStatements[K any](statements []*Statement[K], telemetrySettings component.TelemetrySettings, options ...StatementsOption[K]) Statements[K] {
- s := Statements[K]{
- statements: statements,
- telemetrySettings: telemetrySettings,
- }
- for _, op := range options {
- op(&s)
- }
- return s
- }
- // Execute is a function that will execute all the statements in the Statements list.
- func (s *Statements[K]) Execute(ctx context.Context, tCtx K) error {
- for _, statement := range s.statements {
- _, _, err := statement.Execute(ctx, tCtx)
- if err != nil {
- if s.errorMode == PropagateError {
- err = fmt.Errorf("failed to execute statement: %v, %w", statement.origText, err)
- return err
- }
- s.telemetrySettings.Logger.Warn("failed to execute statement", zap.Error(err), zap.String("statement", statement.origText))
- }
- }
- return nil
- }
- // Eval returns true if any statement's condition is true and returns false otherwise.
- // Does not execute the statement's function.
- // When errorMode is `propagate`, errors cause the evaluation to be false and an error is returned.
- // When errorMode is `ignore`, errors cause evaluation to continue to the next statement.
- func (s *Statements[K]) Eval(ctx context.Context, tCtx K) (bool, error) {
- for _, statement := range s.statements {
- match, err := statement.condition.Eval(ctx, tCtx)
- if err != nil {
- if s.errorMode == PropagateError {
- err = fmt.Errorf("failed to eval statement: %v, %w", statement.origText, err)
- return false, err
- }
- s.telemetrySettings.Logger.Warn("failed to eval statement", zap.Error(err), zap.String("statement", statement.origText))
- continue
- }
- if match {
- return true, nil
- }
- }
- return false, nil
- }
|