diff --git a/pkg/logql/ast.go b/pkg/logql/ast.go index 43d64c248db9..efd01131c3c8 100644 --- a/pkg/logql/ast.go +++ b/pkg/logql/ast.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/loki/pkg/iter" "github.com/grafana/loki/pkg/logproto" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/promql" ) @@ -19,6 +20,7 @@ import ( // Expr is the root expression which can be a SampleExpr or LogSelectorExpr type Expr interface { logQLExpr() // ensure it's not implemented accidentally + fmt.Stringer } // SelectParams specifies parameters passed to data selections. @@ -53,7 +55,6 @@ type Filter func(line []byte) bool type LogSelectorExpr interface { Filter() (Filter, error) Matchers() []*labels.Matcher - fmt.Stringer Expr } @@ -188,6 +189,16 @@ type logRange struct { interval time.Duration } +// impls Stringer +func (r logRange) String() string { + var sb strings.Builder + sb.WriteString("(") + sb.WriteString(r.left.String()) + sb.WriteString(")") + sb.WriteString(fmt.Sprintf("[%v]", model.Duration(r.interval))) + return sb.String() +} + func newLogRange(left LogSelectorExpr, interval time.Duration) *logRange { return &logRange{ left: left, @@ -279,11 +290,34 @@ func (e *rangeAggregationExpr) Selector() LogSelectorExpr { // impl Expr func (e *rangeAggregationExpr) logQLExpr() {} +// impls Stringer +func (e *rangeAggregationExpr) String() string { + return formatOperation(e.operation, nil, e.left.String()) +} + type grouping struct { groups []string without bool } +// impls Stringer +func (g grouping) String() string { + var sb strings.Builder + if g.without { + sb.WriteString(" without") + } else if len(g.groups) > 0 { + sb.WriteString(" by") + } + + if len(g.groups) > 0 { + sb.WriteString("(") + sb.WriteString(strings.Join(g.groups, ",")) + sb.WriteString(")") + } + + return sb.String() +} + type vectorAggregationExpr struct { left SampleExpr @@ -325,3 +359,33 @@ func (e *vectorAggregationExpr) Selector() LogSelectorExpr { // impl Expr func (e *vectorAggregationExpr) logQLExpr() {} + +func (e *vectorAggregationExpr) String() string { + var params []string + if e.params != 0 { + params = []string{fmt.Sprintf("%d", e.params), e.left.String()} + } else { + params = []string{e.left.String()} + } + return formatOperation(e.operation, e.grouping, params...) +} + +// helper used to impl Stringer for vector and range aggregations +func formatOperation(op string, grouping *grouping, params ...string) string { + nonEmptyParams := make([]string, 0, len(params)) + for _, p := range params { + if p != "" { + nonEmptyParams = append(nonEmptyParams, p) + } + } + + var sb strings.Builder + sb.WriteString(op) + if grouping != nil { + sb.WriteString(grouping.String()) + } + sb.WriteString("(") + sb.WriteString(strings.Join(nonEmptyParams, ",")) + sb.WriteString(")") + return sb.String() +} diff --git a/pkg/logql/ast_test.go b/pkg/logql/ast_test.go index f26e6d77cbb7..303cf52986ea 100644 --- a/pkg/logql/ast_test.go +++ b/pkg/logql/ast_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/prometheus/prometheus/pkg/labels" ) @@ -33,6 +34,27 @@ func Test_logSelectorExpr_String(t *testing.T) { } } +func Test_SampleExpr_String(t *testing.T) { + t.Parallel() + for _, tc := range []string{ + `rate( ( {job="mysql"} |="error" !="timeout" ) [10s] )`, + `sum without(a) ( rate ( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )`, + `sum by(a) (rate( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )`, + `sum(count_over_time({job="mysql"}[5m]))`, + `topk(10,sum(rate({region="us-east1"}[5m])) by (name))`, + `avg( rate( ( {job="nginx"} |= "GET" ) [10s] ) ) by (region)`, + } { + t.Run(tc, func(t *testing.T) { + expr, err := ParseExpr(tc) + require.Nil(t, err) + + expr2, err := ParseExpr(expr.String()) + require.Nil(t, err) + require.Equal(t, expr, expr2) + }) + } +} + type linecheck struct { l string e bool