github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/operator/builtin/transformer/router.go (about)

     1  package transformer
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/antonmedv/expr"
     8  	"github.com/antonmedv/expr/vm"
     9  	"github.com/observiq/carbon/entry"
    10  	"github.com/observiq/carbon/operator"
    11  	"github.com/observiq/carbon/operator/helper"
    12  	"go.uber.org/zap"
    13  )
    14  
    15  func init() {
    16  	operator.Register("router", func() operator.Builder { return NewRouterOperatorConfig("") })
    17  }
    18  
    19  func NewRouterOperatorConfig(operatorID string) *RouterOperatorConfig {
    20  	return &RouterOperatorConfig{
    21  		BasicConfig: helper.NewBasicConfig(operatorID, "router"),
    22  	}
    23  }
    24  
    25  // RouterOperatorConfig is the configuration of a router operator
    26  type RouterOperatorConfig struct {
    27  	helper.BasicConfig `yaml:",inline"`
    28  	Routes             []*RouterOperatorRouteConfig `json:"routes" yaml:"routes"`
    29  }
    30  
    31  // RouterOperatorRouteConfig is the configuration of a route on a router operator
    32  type RouterOperatorRouteConfig struct {
    33  	helper.LabelerConfig `yaml:",inline"`
    34  	Expression           string           `json:"expr"   yaml:"expr"`
    35  	OutputIDs            helper.OutputIDs `json:"output" yaml:"output"`
    36  }
    37  
    38  // Build will build a router operator from the supplied configuration
    39  func (c RouterOperatorConfig) Build(context operator.BuildContext) (operator.Operator, error) {
    40  	basicOperator, err := c.BasicConfig.Build(context)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	routes := make([]*RouterOperatorRoute, 0, len(c.Routes))
    46  	for _, routeConfig := range c.Routes {
    47  		compiled, err := expr.Compile(routeConfig.Expression, expr.AsBool(), expr.AllowUndefinedVariables())
    48  		if err != nil {
    49  			return nil, fmt.Errorf("failed to compile expression '%s': %w", routeConfig.Expression, err)
    50  		}
    51  
    52  		labeler, err := routeConfig.LabelerConfig.Build()
    53  		if err != nil {
    54  			return nil, fmt.Errorf("failed to build labeler for route '%s': %w", routeConfig.Expression, err)
    55  		}
    56  
    57  		route := RouterOperatorRoute{
    58  			Labeler:    labeler,
    59  			Expression: compiled,
    60  			OutputIDs:  routeConfig.OutputIDs,
    61  		}
    62  		routes = append(routes, &route)
    63  	}
    64  
    65  	routerOperator := &RouterOperator{
    66  		BasicOperator: basicOperator,
    67  		routes:        routes,
    68  	}
    69  
    70  	return routerOperator, nil
    71  }
    72  
    73  // SetNamespace will namespace the router operator and the outputs contained in its routes
    74  func (c *RouterOperatorConfig) SetNamespace(namespace string, exclusions ...string) {
    75  	c.BasicConfig.SetNamespace(namespace, exclusions...)
    76  	for _, route := range c.Routes {
    77  		for i, outputID := range route.OutputIDs {
    78  			if helper.CanNamespace(outputID, exclusions) {
    79  				route.OutputIDs[i] = helper.AddNamespace(outputID, namespace)
    80  			}
    81  		}
    82  	}
    83  }
    84  
    85  // RouterOperator is an operator that routes entries based on matching expressions
    86  type RouterOperator struct {
    87  	helper.BasicOperator
    88  	routes []*RouterOperatorRoute
    89  }
    90  
    91  // RouterOperatorRoute is a route on a router operator
    92  type RouterOperatorRoute struct {
    93  	helper.Labeler
    94  	Expression      *vm.Program
    95  	OutputIDs       helper.OutputIDs
    96  	OutputOperators []operator.Operator
    97  }
    98  
    99  // CanProcess will always return true for a router operator
   100  func (p *RouterOperator) CanProcess() bool {
   101  	return true
   102  }
   103  
   104  // Process will route incoming entries based on matching expressions
   105  func (p *RouterOperator) Process(ctx context.Context, entry *entry.Entry) error {
   106  	env := helper.GetExprEnv(entry)
   107  	defer helper.PutExprEnv(env)
   108  
   109  	for _, route := range p.routes {
   110  		matches, err := vm.Run(route.Expression, env)
   111  		if err != nil {
   112  			p.Warnw("Running expression returned an error", zap.Error(err))
   113  			continue
   114  		}
   115  
   116  		// we compile the expression with "AsBool", so this should be safe
   117  		if matches.(bool) {
   118  			if err := route.Label(entry); err != nil {
   119  				p.Errorf("Failed to label entry: %s", err)
   120  				return err
   121  			}
   122  
   123  			for _, output := range route.OutputOperators {
   124  				_ = output.Process(ctx, entry)
   125  			}
   126  			break
   127  		}
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  // CanOutput will always return true for a router operator
   134  func (p *RouterOperator) CanOutput() bool {
   135  	return true
   136  }
   137  
   138  // Outputs will return all connected operators.
   139  func (p *RouterOperator) Outputs() []operator.Operator {
   140  	outputs := make([]operator.Operator, 0, len(p.routes))
   141  	for _, route := range p.routes {
   142  		outputs = append(outputs, route.OutputOperators...)
   143  	}
   144  	return outputs
   145  }
   146  
   147  // SetOutputs will set the outputs of the router operator.
   148  func (p *RouterOperator) SetOutputs(operators []operator.Operator) error {
   149  	for _, route := range p.routes {
   150  		outputOperators, err := p.findOperators(operators, route.OutputIDs)
   151  		if err != nil {
   152  			return fmt.Errorf("failed to set outputs on route: %s", err)
   153  		}
   154  		route.OutputOperators = outputOperators
   155  	}
   156  	return nil
   157  }
   158  
   159  // findOperators will find a subset of operators from a collection.
   160  func (p *RouterOperator) findOperators(operators []operator.Operator, operatorIDs []string) ([]operator.Operator, error) {
   161  	result := make([]operator.Operator, 0)
   162  	for _, operatorID := range operatorIDs {
   163  		operator, err := p.findOperator(operators, operatorID)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  		result = append(result, operator)
   168  	}
   169  	return result, nil
   170  }
   171  
   172  // findOperator will find an operator from a collection.
   173  func (p *RouterOperator) findOperator(operators []operator.Operator, operatorID string) (operator.Operator, error) {
   174  	for _, operator := range operators {
   175  		if operator.ID() == operatorID {
   176  			return operator, nil
   177  		}
   178  	}
   179  	return nil, fmt.Errorf("operator %s does not exist", operatorID)
   180  }