Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/querier/queryrange/roundtrip.go

180 lines
5.7 KiB

package queryrange
import (
"flag"
"net/http"
"strings"
"github.com/cortexproject/cortex/pkg/chunk/cache"
"github.com/cortexproject/cortex/pkg/querier/frontend"
"github.com/cortexproject/cortex/pkg/querier/queryrange"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/weaveworks/common/httpgrpc"
"github.com/grafana/loki/pkg/logql"
)
// Config is the configuration for the queryrange tripperware
type Config struct {
queryrange.Config `yaml:",inline"`
}
// RegisterFlags adds the flags required to configure this flag set.
func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
cfg.Config.RegisterFlags(f)
}
// Stopper gracefully shutdown resources created
type Stopper interface {
Stop()
}
// NewTripperware returns a Tripperware configured with middlewares to align, split and cache requests.
func NewTripperware(cfg Config, log log.Logger, limits Limits) (frontend.Tripperware, Stopper, error) {
// Ensure that QuerySplitDuration uses configuration defaults.
// This avoids divide by zero errors when determining cache keys where user specific overrides don't exist.
limits = WithDefaultLimits(limits, cfg.Config)
metricsTripperware, cache, err := NewMetricTripperware(cfg, log, limits, lokiCodec, prometheusResponseExtractor)
if err != nil {
return nil, nil, err
}
logFilterTripperware, err := NewLogFilterTripperware(cfg, log, limits, lokiCodec)
if err != nil {
return nil, nil, err
}
return frontend.Tripperware(func(next http.RoundTripper) http.RoundTripper {
metricRT := metricsTripperware(next)
logFilterRT := logFilterTripperware(next)
return frontend.RoundTripFunc(func(req *http.Request) (*http.Response, error) {
if !strings.HasSuffix(req.URL.Path, "/query_range") && !strings.HasSuffix(req.URL.Path, "/prom/query") {
return next.RoundTrip(req)
}
params := req.URL.Query()
query := params.Get("query")
expr, err := logql.ParseExpr(query)
if err != nil {
// weavework server uses httpgrpc errors for status code.
return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
}
if _, ok := expr.(logql.SampleExpr); ok {
return metricRT.RoundTrip(req)
}
if logSelector, ok := expr.(logql.LogSelectorExpr); ok {
// backport the old regexp params into the query params
regexp := params.Get("regexp")
if regexp != "" {
logSelector = logql.NewFilterExpr(logSelector, labels.MatchRegexp, regexp)
params.Set("query", logSelector.String())
req.URL.RawQuery = params.Encode()
}
filter, err := logSelector.Filter()
if err != nil {
return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
}
if filter != nil {
return logFilterRT.RoundTrip(req)
}
}
return next.RoundTrip(req)
})
}), cache, nil
}
// NewLogFilterTripperware creates a new frontend tripperware responsible for handling log requests with regex.
func NewLogFilterTripperware(
cfg Config,
log log.Logger,
limits Limits,
codec queryrange.Codec,
) (frontend.Tripperware, error) {
queryRangeMiddleware := []queryrange.Middleware{StatsMiddleware(), queryrange.LimitsMiddleware(limits)}
if cfg.SplitQueriesByInterval != 0 {
queryRangeMiddleware = append(queryRangeMiddleware, queryrange.InstrumentMiddleware("split_by_interval"), SplitByIntervalMiddleware(limits, codec))
}
if cfg.MaxRetries > 0 {
queryRangeMiddleware = append(queryRangeMiddleware, queryrange.InstrumentMiddleware("retry"), queryrange.NewRetryMiddleware(log, cfg.MaxRetries))
}
return frontend.Tripperware(func(next http.RoundTripper) http.RoundTripper {
if len(queryRangeMiddleware) > 0 {
return queryrange.NewRoundTripper(next, codec, queryRangeMiddleware...)
}
return next
}), nil
}
// NewMetricTripperware creates a new frontend tripperware responsible for handling metric queries
func NewMetricTripperware(
cfg Config,
log log.Logger,
limits Limits,
codec queryrange.Codec,
extractor queryrange.Extractor,
) (frontend.Tripperware, Stopper, error) {
queryRangeMiddleware := []queryrange.Middleware{StatsMiddleware(), queryrange.LimitsMiddleware(limits)}
if cfg.AlignQueriesWithStep {
queryRangeMiddleware = append(
queryRangeMiddleware,
queryrange.InstrumentMiddleware("step_align"),
queryrange.StepAlignMiddleware,
)
}
// SplitQueriesByDay is deprecated use SplitQueriesByInterval.
if cfg.SplitQueriesByDay {
level.Warn(log).Log("msg", "flag querier.split-queries-by-day (or config split_queries_by_day) is deprecated, use querier.split-queries-by-interval instead.")
}
queryRangeMiddleware = append(
queryRangeMiddleware,
queryrange.InstrumentMiddleware("split_by_interval"),
SplitByIntervalMiddleware(limits, codec),
)
var c cache.Cache
if cfg.CacheResults {
queryCacheMiddleware, cache, err := queryrange.NewResultsCacheMiddleware(
log,
cfg.ResultsCacheConfig,
cacheKeyLimits{limits},
limits,
codec,
extractor,
)
if err != nil {
return nil, nil, err
}
c = cache
queryRangeMiddleware = append(
queryRangeMiddleware,
queryrange.InstrumentMiddleware("results_cache"),
queryCacheMiddleware,
)
}
if cfg.MaxRetries > 0 {
queryRangeMiddleware = append(
queryRangeMiddleware,
queryrange.InstrumentMiddleware("retry"),
queryrange.NewRetryMiddleware(log, cfg.MaxRetries),
)
}
return frontend.Tripperware(func(next http.RoundTripper) http.RoundTripper {
// Finally, if the user selected any query range middleware, stitch it in.
if len(queryRangeMiddleware) > 0 {
rt := queryrange.NewRoundTripper(next, codec, queryRangeMiddleware...)
return frontend.RoundTripFunc(func(r *http.Request) (*http.Response, error) {
if !strings.HasSuffix(r.URL.Path, "/query_range") {
return next.RoundTrip(r)
}
return rt.RoundTrip(r)
})
}
return next
}), c, nil
}