actually fix expr-debugger to work with the new version (#2124)

This commit is contained in:
blotus 2023-03-16 15:20:48 +01:00 committed by GitHub
parent 94c7efdb5b
commit c77fe16943
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 165 additions and 33 deletions

View file

@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/antonmedv/expr/parser" "github.com/antonmedv/expr/parser"
"github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -19,27 +20,61 @@ Visitor is used to reconstruct variables with its property called in an expr fil
Thus, we can debug expr filter by displaying all variables contents present in the filter Thus, we can debug expr filter by displaying all variables contents present in the filter
*/ */
type visitor struct { type visitor struct {
newVar bool newVar bool
currentID string currentId string
properties []string vars map[string][]string
vars []string logger *log.Entry
} }
func (v *visitor) Visit(node *ast.Node) { func (v *visitor) Visit(node *ast.Node) {
if n, ok := (*node).(*ast.IdentifierNode); ok { switch n := (*node).(type) {
if !v.newVar { case *ast.IdentifierNode:
v.newVar = true v.newVar = true
v.currentID = n.Value uid, _ := uuid.NewUUID()
} else { v.currentId = uid.String()
fullVar := fmt.Sprintf("%s.%s", v.currentID, strings.Join(v.properties, ".")) v.vars[v.currentId] = []string{n.Value}
v.vars = append(v.vars, fullVar) case *ast.MemberNode:
v.properties = []string{} if n2, ok := n.Property.(*ast.StringNode); ok {
v.currentID = n.Value v.vars[v.currentId] = append(v.vars[v.currentId], n2.Value)
}
} else if n2, ok := (*node).(*ast.MemberNode); ok {
if ns, ok := (n2.Property).(*ast.StringNode); ok {
v.properties = append(v.properties, ns.Value)
} }
case *ast.StringNode: //Don't reset here, as any attribute of a member node is a string node (in evt.X, evt is member node, X is string node)
default:
v.newVar = false
v.currentId = ""
/*case *ast.IntegerNode:
v.logger.Infof("integer node found: %+v", n)
case *ast.FloatNode:
v.logger.Infof("float node found: %+v", n)
case *ast.BoolNode:
v.logger.Infof("boolean node found: %+v", n)
case *ast.ArrayNode:
v.logger.Infof("array node found: %+v", n)
case *ast.ConstantNode:
v.logger.Infof("constant node found: %+v", n)
case *ast.UnaryNode:
v.logger.Infof("unary node found: %+v", n)
case *ast.BinaryNode:
v.logger.Infof("binary node found: %+v", n)
case *ast.CallNode:
v.logger.Infof("call node found: %+v", n)
case *ast.BuiltinNode:
v.logger.Infof("builtin node found: %+v", n)
case *ast.ConditionalNode:
v.logger.Infof("conditional node found: %+v", n)
case *ast.ChainNode:
v.logger.Infof("chain node found: %+v", n)
case *ast.PairNode:
v.logger.Infof("pair node found: %+v", n)
case *ast.MapNode:
v.logger.Infof("map node found: %+v", n)
case *ast.SliceNode:
v.logger.Infof("slice node found: %+v", n)
case *ast.ClosureNode:
v.logger.Infof("closure node found: %+v", n)
case *ast.PointerNode:
v.logger.Infof("pointer node found: %+v", n)
default:
v.logger.Infof("unknown node found: %+v | type: %T", n, n)*/
} }
} }
@ -52,34 +87,30 @@ func (v *visitor) Build(filter string, exprEnv expr.Option) (*ExprDebugger, erro
filter: filter, filter: filter,
} }
if filter == "" { if filter == "" {
log.Debugf("unable to create expr debugger with empty filter") v.logger.Debugf("unable to create expr debugger with empty filter")
return &ExprDebugger{}, nil return &ExprDebugger{}, nil
} }
v.newVar = false v.newVar = false
v.vars = make(map[string][]string)
tree, err := parser.Parse(filter) tree, err := parser.Parse(filter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ast.Walk(&tree.Node, v) ast.Walk(&tree.Node, v)
if v.currentID != "" && len(v.properties) > 0 { // if its a variable with property (eg. evt.Line.Labels) log.Debugf("vars: %+v", v.vars)
fullVar := fmt.Sprintf("%s.%s", v.currentID, strings.Join(v.properties, "."))
v.vars = append(v.vars, fullVar)
} else if v.currentID != "" && len(v.properties) == 0 { // if it's a variable without property
fullVar := v.currentID
v.vars = append(v.vars, fullVar)
} else {
log.Debugf("no variable in filter : '%s'", filter)
}
v.properties = []string{}
v.currentID = ""
for _, variable := range v.vars { for _, variable := range v.vars {
debugFilter, err := expr.Compile(variable, exprEnv) if variable[0] != "evt" {
continue
}
toBuild := strings.Join(variable, ".")
v.logger.Debugf("compiling expression '%s'", toBuild)
debugFilter, err := expr.Compile(toBuild, exprEnv)
if err != nil { if err != nil {
return ret, fmt.Errorf("compilation of variable '%s' failed: %v", variable, err) return ret, fmt.Errorf("compilation of variable '%s' failed: %v", toBuild, err)
} }
tmpExpression := &expression{ tmpExpression := &expression{
variable, toBuild,
debugFilter, debugFilter,
} }
expressions = append(expressions, tmpExpression) expressions = append(expressions, tmpExpression)
@ -123,7 +154,8 @@ func (e *ExprDebugger) Run(logger *logrus.Entry, filterResult bool, exprEnv map[
// NewDebugger is the exported function that build the debuggers expressions // NewDebugger is the exported function that build the debuggers expressions
func NewDebugger(filter string, exprEnv expr.Option) (*ExprDebugger, error) { func NewDebugger(filter string, exprEnv expr.Option) (*ExprDebugger, error) {
visitor := &visitor{} logger := log.WithField("component", "expr-debugger")
visitor := &visitor{logger: logger}
exprDebugger, err := visitor.Build(filter, exprEnv) exprDebugger, err := visitor.Build(filter, exprEnv)
return exprDebugger, err return exprDebugger, err
} }

View file

@ -0,0 +1,100 @@
package exprhelpers
import (
"sort"
"testing"
"github.com/antonmedv/expr"
log "github.com/sirupsen/logrus"
)
func TestVisitorBuild(t *testing.T) {
tests := []struct {
name string
expr string
want []string
env map[string]interface{}
}{
{
name: "simple",
expr: "evt.X",
want: []string{"evt.X"},
env: map[string]interface{}{
"evt": map[string]interface{}{
"X": 1,
},
},
},
{
name: "two vars",
expr: "evt.X && evt.Y",
want: []string{"evt.X", "evt.Y"},
env: map[string]interface{}{
"evt": map[string]interface{}{
"X": 1,
"Y": 2,
},
},
},
{
name: "in",
expr: "evt.X in [1,2,3]",
want: []string{"evt.X"},
env: map[string]interface{}{
"evt": map[string]interface{}{
"X": 1,
},
},
},
{
name: "in complex",
expr: "evt.X in [1,2,3] && evt.Y in [1,2,3] || evt.Z in [1,2,3]",
want: []string{"evt.X", "evt.Y", "evt.Z"},
env: map[string]interface{}{
"evt": map[string]interface{}{
"X": 1,
"Y": 2,
"Z": 3,
},
},
},
{
name: "function call",
expr: "Foo(evt.X, 'ads')",
want: []string{"evt.X"},
env: map[string]interface{}{
"evt": map[string]interface{}{
"X": 1,
},
"Foo": func(x int, y string) int {
return x
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := &visitor{logger: log.NewEntry(log.New())}
ret, err := v.Build(tt.expr, expr.Env(tt.env))
if err != nil {
t.Errorf("visitor.Build() error = %v", err)
return
}
if len(ret.expression) != len(tt.want) {
t.Errorf("visitor.Build() = %v, want %v", ret.expression, tt.want)
}
//Sort both slices as the order is not guaranteed ??
sort.Slice(tt.want, func(i, j int) bool {
return tt.want[i] < tt.want[j]
})
sort.Slice(ret.expression, func(i, j int) bool {
return ret.expression[i].Str < ret.expression[j].Str
})
for idx, v := range ret.expression {
if v.Str != tt.want[idx] {
t.Errorf("visitor.Build() = %v, want %v", v.Str, tt.want[idx])
}
}
})
}
}