@ -20,6 +20,7 @@ import (
"github.com/grafana/loki/v3/pkg/logql"
logqllog "github.com/grafana/loki/v3/pkg/logql/log"
"github.com/grafana/loki/v3/pkg/logql/syntax"
"github.com/grafana/loki/v3/pkg/logqlmodel"
"github.com/grafana/loki/v3/pkg/logqlmodel/stats"
"github.com/grafana/loki/v3/pkg/querier/queryrange/queryrangebase"
base "github.com/grafana/loki/v3/pkg/querier/queryrange/queryrangebase"
@ -257,6 +258,27 @@ func NewMiddleware(
if err != nil {
return nil , nil , err
}
variantsTripperware , err := NewVariantsTripperware (
cfg ,
engineOpts ,
log ,
limits ,
schema ,
codec ,
iqo ,
resultsCache ,
cacheGenNumLoader ,
retentionEnabled ,
PrometheusExtractor { } ,
metrics ,
indexStatsTripperware ,
metricsNamespace ,
)
if err != nil {
return nil , nil , err
}
return base . MiddlewareFunc ( func ( next base . Handler ) base . Handler {
var (
metricRT = metricsTripperware . Wrap ( next )
@ -269,9 +291,25 @@ func NewMiddleware(
seriesVolumeRT = seriesVolumeTripperware . Wrap ( next )
detectedFieldsRT = detectedFieldsTripperware . Wrap ( next )
detectedLabelsRT = detectedLabelsTripperware . Wrap ( next )
variantsRT = variantsTripperware . Wrap ( next )
)
return newRoundTripper ( log , next , limitedRT , logFilterRT , metricRT , seriesRT , labelsRT , instantRT , statsRT , seriesVolumeRT , detectedFieldsRT , detectedLabelsRT , limits )
return newRoundTripper (
log ,
next ,
limitedRT ,
logFilterRT ,
metricRT ,
seriesRT ,
labelsRT ,
instantRT ,
statsRT ,
seriesVolumeRT ,
detectedFieldsRT ,
detectedLabelsRT ,
variantsRT ,
limits ,
)
} ) , StopperWrapper { resultsCache , statsCache , volumeCache } , nil
}
@ -325,13 +363,17 @@ func NewDetectedLabelsCardinalityFilter(rt queryrangebase.Handler) queryrangebas
type roundTripper struct {
logger log . Logger
next , limited , log , metric , series , labels , instantMetric , indexStats , seriesVolume , detectedFields , detectedLabels base . Handler
next , limited , log , metric , series , labels , instantMetric , indexStats , seriesVolume , detectedFields , detectedLabels , variants base . Handler
limits Limits
}
// newRoundTripper creates a new queryrange roundtripper
func newRoundTripper ( logger log . Logger , next , limited , log , metric , series , labels , instantMetric , indexStats , seriesVolume , detectedFields , detectedLabels base . Handler , limits Limits ) roundTripper {
func newRoundTripper (
logger log . Logger ,
next , limited , log , metric , series , labels , instantMetric , indexStats , seriesVolume , detectedFields , detectedLabels , variants base . Handler ,
limits Limits ,
) roundTripper {
return roundTripper {
logger : logger ,
limited : limited ,
@ -345,6 +387,7 @@ func newRoundTripper(logger log.Logger, next, limited, log, metric, series, labe
seriesVolume : seriesVolume ,
detectedFields : detectedFields ,
detectedLabels : detectedLabels ,
variants : variants ,
next : next ,
}
}
@ -401,7 +444,31 @@ func (r roundTripper) Do(ctx context.Context, req base.Request) (base.Response,
return r . limited . Do ( ctx , req )
}
return r . log . Do ( ctx , req )
case syntax . VariantsExpr :
if err := validateMaxEntriesLimits ( ctx , op . Limit , r . limits ) ; err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , "%s" , err . Error ( ) )
}
matchers := e . Matchers ( )
if err := validateMatchers ( ctx , r . limits , matchers ) ; err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , "%s" , err . Error ( ) )
}
for _ , v := range e . Variants ( ) {
groups , err := v . MatcherGroups ( )
if err != nil {
level . Warn ( logger ) . Log ( "msg" , "unexpected matcher groups error in roundtripper" , "err" , err )
}
for _ , g := range groups {
if err := validateMatchers ( ctx , r . limits , g . Matchers ) ; err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , "%s" , err . Error ( ) )
}
}
}
return r . variants . Do ( ctx , req )
default :
return r . next . Do ( ctx , req )
}
@ -1231,3 +1298,34 @@ func NewDetectedFieldsTripperware(
return NewDetectedFieldsHandler ( limitedHandler , logHandler , limits )
} ) , nil
}
// NewVariantsTripperware creates a new frontend tripperware responsible for handling queries with multiple variants for a single
// selector.
func NewVariantsTripperware (
_ Config ,
_ logql . EngineOpts ,
_ log . Logger ,
_ Limits ,
_ config . SchemaConfig ,
_ base . Merger ,
_ util . IngesterQueryOptions ,
_ cache . Cache ,
_ base . CacheGenNumberLoader ,
_ bool ,
_ base . Extractor ,
_ * Metrics ,
_ base . Middleware ,
_ string ,
) ( base . Middleware , error ) {
return base . MiddlewareFunc ( func ( next base . Handler ) base . Handler {
return base . HandlerFunc (
func ( ctx context . Context , r base . Request ) ( base . Response , error ) {
if _ , ok := r . ( * LokiRequest ) ; ! ok {
return next . Do ( ctx , r )
}
return nil , logqlmodel . ErrVariantsDisabled
} ,
)
} ) , nil
}