github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/roundtrip.go (about) 1 package queryrange 2 3 import ( 4 "flag" 5 "net/http" 6 "strings" 7 "time" 8 9 "github.com/go-kit/log" 10 "github.com/prometheus/client_golang/prometheus" 11 "github.com/prometheus/prometheus/model/labels" 12 "github.com/weaveworks/common/httpgrpc" 13 14 "github.com/grafana/dskit/tenant" 15 16 "github.com/grafana/loki/pkg/loghttp" 17 "github.com/grafana/loki/pkg/logql/syntax" 18 "github.com/grafana/loki/pkg/logqlmodel/stats" 19 "github.com/grafana/loki/pkg/querier/queryrange/queryrangebase" 20 "github.com/grafana/loki/pkg/storage/chunk/cache" 21 "github.com/grafana/loki/pkg/storage/config" 22 "github.com/grafana/loki/pkg/util/validation" 23 ) 24 25 // Config is the configuration for the queryrange tripperware 26 type Config struct { 27 queryrangebase.Config `yaml:",inline"` 28 } 29 30 // RegisterFlags adds the flags required to configure this flag set. 31 func (cfg *Config) RegisterFlags(f *flag.FlagSet) { 32 cfg.Config.RegisterFlags(f) 33 } 34 35 // Stopper gracefully shutdown resources created 36 type Stopper interface { 37 Stop() 38 } 39 40 // NewTripperware returns a Tripperware configured with middlewares to align, split and cache requests. 41 func NewTripperware( 42 cfg Config, 43 log log.Logger, 44 limits Limits, 45 schema config.SchemaConfig, 46 cacheGenNumLoader queryrangebase.CacheGenNumberLoader, 47 registerer prometheus.Registerer, 48 ) (queryrangebase.Tripperware, Stopper, error) { 49 metrics := NewMetrics(registerer) 50 51 var ( 52 c cache.Cache 53 err error 54 ) 55 56 if cfg.CacheResults { 57 c, err = cache.New(cfg.CacheConfig, registerer, log, stats.ResultCache) 58 if err != nil { 59 return nil, nil, err 60 } 61 if cfg.Compression == "snappy" { 62 c = cache.NewSnappy(c, log) 63 } 64 } 65 66 metricsTripperware, err := NewMetricTripperware(cfg, log, limits, schema, LokiCodec, c, 67 cacheGenNumLoader, PrometheusExtractor{}, metrics, registerer) 68 if err != nil { 69 return nil, nil, err 70 } 71 72 // NOTE: When we would start caching response from non-metric queries we would have to consider cache gen headers as well in 73 // MergeResponse implementation for Loki codecs same as it is done in Cortex at https://github.com/cortexproject/cortex/blob/21bad57b346c730d684d6d0205efef133422ab28/pkg/querier/queryrange/query_range.go#L170 74 logFilterTripperware, err := NewLogFilterTripperware(cfg, log, limits, schema, LokiCodec, c, metrics) 75 if err != nil { 76 return nil, nil, err 77 } 78 79 seriesTripperware, err := NewSeriesTripperware(cfg, log, limits, LokiCodec, metrics, schema) 80 if err != nil { 81 return nil, nil, err 82 } 83 84 labelsTripperware, err := NewLabelsTripperware(cfg, log, limits, LokiCodec, metrics) 85 if err != nil { 86 return nil, nil, err 87 } 88 89 instantMetricTripperware, err := NewInstantMetricTripperware(cfg, log, limits, schema, LokiCodec, metrics) 90 if err != nil { 91 return nil, nil, err 92 } 93 return func(next http.RoundTripper) http.RoundTripper { 94 metricRT := metricsTripperware(next) 95 logFilterRT := logFilterTripperware(next) 96 seriesRT := seriesTripperware(next) 97 labelsRT := labelsTripperware(next) 98 instantRT := instantMetricTripperware(next) 99 return newRoundTripper(next, logFilterRT, metricRT, seriesRT, labelsRT, instantRT, limits) 100 }, c, nil 101 } 102 103 type roundTripper struct { 104 next, log, metric, series, labels, instantMetric http.RoundTripper 105 106 limits Limits 107 } 108 109 // newRoundTripper creates a new queryrange roundtripper 110 func newRoundTripper(next, log, metric, series, labels, instantMetric http.RoundTripper, limits Limits) roundTripper { 111 return roundTripper{ 112 log: log, 113 limits: limits, 114 metric: metric, 115 series: series, 116 labels: labels, 117 instantMetric: instantMetric, 118 next: next, 119 } 120 } 121 122 func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 123 err := req.ParseForm() 124 if err != nil { 125 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 126 } 127 128 switch op := getOperation(req.URL.Path); op { 129 case QueryRangeOp: 130 rangeQuery, err := loghttp.ParseRangeQuery(req) 131 if err != nil { 132 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 133 } 134 expr, err := syntax.ParseExpr(rangeQuery.Query) 135 if err != nil { 136 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 137 } 138 switch e := expr.(type) { 139 case syntax.SampleExpr: 140 return r.metric.RoundTrip(req) 141 case syntax.LogSelectorExpr: 142 expr, err := transformRegexQuery(req, e) 143 if err != nil { 144 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 145 } 146 if err := validateLimits(req, rangeQuery.Limit, r.limits); err != nil { 147 return nil, err 148 } 149 // Only filter expressions are query sharded 150 if !expr.HasFilter() { 151 return r.next.RoundTrip(req) 152 } 153 return r.log.RoundTrip(req) 154 155 default: 156 return r.next.RoundTrip(req) 157 } 158 case SeriesOp: 159 _, err := loghttp.ParseAndValidateSeriesQuery(req) 160 if err != nil { 161 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 162 } 163 return r.series.RoundTrip(req) 164 case LabelNamesOp: 165 _, err := loghttp.ParseLabelQuery(req) 166 if err != nil { 167 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 168 } 169 return r.labels.RoundTrip(req) 170 case InstantQueryOp: 171 instantQuery, err := loghttp.ParseInstantQuery(req) 172 if err != nil { 173 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 174 } 175 expr, err := syntax.ParseExpr(instantQuery.Query) 176 if err != nil { 177 return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 178 } 179 switch expr.(type) { 180 case syntax.SampleExpr: 181 return r.instantMetric.RoundTrip(req) 182 default: 183 return r.next.RoundTrip(req) 184 } 185 default: 186 return r.next.RoundTrip(req) 187 } 188 } 189 190 // transformRegexQuery backport the old regexp params into the v1 query format 191 func transformRegexQuery(req *http.Request, expr syntax.LogSelectorExpr) (syntax.LogSelectorExpr, error) { 192 regexp := req.Form.Get("regexp") 193 if regexp != "" { 194 filterExpr, err := syntax.AddFilterExpr(expr, labels.MatchRegexp, "", regexp) 195 if err != nil { 196 return nil, err 197 } 198 params := req.URL.Query() 199 params.Set("query", filterExpr.String()) 200 req.URL.RawQuery = params.Encode() 201 // force the form and query to be parsed again. 202 req.Form = nil 203 req.PostForm = nil 204 return filterExpr, nil 205 } 206 return expr, nil 207 } 208 209 // validates log entries limits 210 func validateLimits(req *http.Request, reqLimit uint32, limits Limits) error { 211 tenantIDs, err := tenant.TenantIDs(req.Context()) 212 if err != nil { 213 return httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 214 } 215 216 maxEntriesLimit := validation.SmallestPositiveNonZeroIntPerTenant(tenantIDs, limits.MaxEntriesLimitPerQuery) 217 if int(reqLimit) > maxEntriesLimit && maxEntriesLimit != 0 { 218 return httpgrpc.Errorf(http.StatusBadRequest, 219 "max entries limit per query exceeded, limit > max_entries_limit (%d > %d)", reqLimit, maxEntriesLimit) 220 } 221 return nil 222 } 223 224 const ( 225 InstantQueryOp = "instant_query" 226 QueryRangeOp = "query_range" 227 SeriesOp = "series" 228 LabelNamesOp = "labels" 229 IndexStatsOp = "index_stats" 230 ) 231 232 func getOperation(path string) string { 233 switch { 234 case strings.HasSuffix(path, "/query_range") || strings.HasSuffix(path, "/prom/query"): 235 return QueryRangeOp 236 case strings.HasSuffix(path, "/series"): 237 return SeriesOp 238 case strings.HasSuffix(path, "/labels") || strings.HasSuffix(path, "/label") || strings.HasSuffix(path, "/values"): 239 return LabelNamesOp 240 case strings.HasSuffix(path, "/v1/query"): 241 return InstantQueryOp 242 case path == "/loki/api/v1/index/stats": 243 return IndexStatsOp 244 default: 245 return "" 246 } 247 } 248 249 // NewLogFilterTripperware creates a new frontend tripperware responsible for handling log requests with regex. 250 func NewLogFilterTripperware( 251 cfg Config, 252 log log.Logger, 253 limits Limits, 254 schema config.SchemaConfig, 255 codec queryrangebase.Codec, 256 c cache.Cache, 257 metrics *Metrics, 258 ) (queryrangebase.Tripperware, error) { 259 queryRangeMiddleware := []queryrangebase.Middleware{ 260 StatsCollectorMiddleware(), 261 NewLimitsMiddleware(limits), 262 queryrangebase.InstrumentMiddleware("split_by_interval", metrics.InstrumentMiddlewareMetrics), 263 SplitByIntervalMiddleware(limits, codec, splitByTime, metrics.SplitByMetrics), 264 } 265 266 if cfg.CacheResults { 267 queryCacheMiddleware := NewLogResultCache( 268 log, 269 limits, 270 c, 271 func(r queryrangebase.Request) bool { 272 return !r.GetCachingOptions().Disabled 273 }, 274 metrics.LogResultCacheMetrics, 275 ) 276 queryRangeMiddleware = append( 277 queryRangeMiddleware, 278 queryrangebase.InstrumentMiddleware("log_results_cache", metrics.InstrumentMiddlewareMetrics), 279 queryCacheMiddleware, 280 ) 281 } 282 283 if cfg.ShardedQueries { 284 queryRangeMiddleware = append(queryRangeMiddleware, 285 NewQueryShardMiddleware( 286 log, 287 schema.Configs, 288 metrics.InstrumentMiddlewareMetrics, // instrumentation is included in the sharding middleware 289 metrics.MiddlewareMapperMetrics.shardMapper, 290 limits, 291 ), 292 ) 293 } 294 295 if cfg.MaxRetries > 0 { 296 queryRangeMiddleware = append( 297 queryRangeMiddleware, queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics), 298 queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics), 299 ) 300 } 301 302 return func(next http.RoundTripper) http.RoundTripper { 303 if len(queryRangeMiddleware) > 0 { 304 return NewLimitedRoundTripper(next, codec, limits, queryRangeMiddleware...) 305 } 306 return next 307 }, nil 308 } 309 310 // NewSeriesTripperware creates a new frontend tripperware responsible for handling series requests 311 func NewSeriesTripperware( 312 cfg Config, 313 log log.Logger, 314 limits Limits, 315 codec queryrangebase.Codec, 316 metrics *Metrics, 317 schema config.SchemaConfig, 318 ) (queryrangebase.Tripperware, error) { 319 queryRangeMiddleware := []queryrangebase.Middleware{ 320 StatsCollectorMiddleware(), 321 NewLimitsMiddleware(limits), 322 queryrangebase.InstrumentMiddleware("split_by_interval", metrics.InstrumentMiddlewareMetrics), 323 // The Series API needs to pull one chunk per series to extract the label set, which is much cheaper than iterating through all matching chunks. 324 // Force a 24 hours split by for series API, this will be more efficient with our static daily bucket storage. 325 // This would avoid queriers downloading chunks for same series over and over again for serving smaller queries. 326 SplitByIntervalMiddleware(WithSplitByLimits(limits, 24*time.Hour), codec, splitByTime, metrics.SplitByMetrics), 327 } 328 329 if cfg.MaxRetries > 0 { 330 queryRangeMiddleware = append(queryRangeMiddleware, 331 queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics), 332 queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics), 333 ) 334 } 335 336 if cfg.ShardedQueries { 337 queryRangeMiddleware = append(queryRangeMiddleware, 338 NewSeriesQueryShardMiddleware( 339 log, 340 schema.Configs, 341 metrics.InstrumentMiddlewareMetrics, 342 metrics.MiddlewareMapperMetrics.shardMapper, 343 limits, 344 codec, 345 ), 346 ) 347 } 348 349 return func(next http.RoundTripper) http.RoundTripper { 350 if len(queryRangeMiddleware) > 0 { 351 return NewLimitedRoundTripper(next, codec, limits, queryRangeMiddleware...) 352 } 353 return next 354 }, nil 355 } 356 357 // NewLabelsTripperware creates a new frontend tripperware responsible for handling labels requests. 358 func NewLabelsTripperware( 359 cfg Config, 360 log log.Logger, 361 limits Limits, 362 codec queryrangebase.Codec, 363 metrics *Metrics, 364 ) (queryrangebase.Tripperware, error) { 365 queryRangeMiddleware := []queryrangebase.Middleware{ 366 StatsCollectorMiddleware(), 367 NewLimitsMiddleware(limits), 368 queryrangebase.InstrumentMiddleware("split_by_interval", metrics.InstrumentMiddlewareMetrics), 369 // Force a 24 hours split by for labels API, this will be more efficient with our static daily bucket storage. 370 // This is because the labels API is an index-only operation. 371 SplitByIntervalMiddleware(WithSplitByLimits(limits, 24*time.Hour), codec, splitByTime, metrics.SplitByMetrics), 372 } 373 374 if cfg.MaxRetries > 0 { 375 queryRangeMiddleware = append(queryRangeMiddleware, 376 queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics), 377 queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics), 378 ) 379 } 380 381 return func(next http.RoundTripper) http.RoundTripper { 382 if len(queryRangeMiddleware) > 0 { 383 // Do not forward any request header. 384 return queryrangebase.NewRoundTripper(next, codec, nil, queryRangeMiddleware...) 385 } 386 return next 387 }, nil 388 } 389 390 // NewMetricTripperware creates a new frontend tripperware responsible for handling metric queries 391 func NewMetricTripperware( 392 cfg Config, 393 log log.Logger, 394 limits Limits, 395 schema config.SchemaConfig, 396 codec queryrangebase.Codec, 397 c cache.Cache, 398 cacheGenNumLoader queryrangebase.CacheGenNumberLoader, 399 extractor queryrangebase.Extractor, 400 metrics *Metrics, 401 registerer prometheus.Registerer, 402 ) (queryrangebase.Tripperware, error) { 403 queryRangeMiddleware := []queryrangebase.Middleware{StatsCollectorMiddleware(), NewLimitsMiddleware(limits)} 404 if cfg.AlignQueriesWithStep { 405 queryRangeMiddleware = append( 406 queryRangeMiddleware, 407 queryrangebase.InstrumentMiddleware("step_align", metrics.InstrumentMiddlewareMetrics), 408 queryrangebase.StepAlignMiddleware, 409 ) 410 } 411 412 queryRangeMiddleware = append( 413 queryRangeMiddleware, 414 queryrangebase.InstrumentMiddleware("split_by_interval", metrics.InstrumentMiddlewareMetrics), 415 SplitByIntervalMiddleware(limits, codec, splitMetricByTime, metrics.SplitByMetrics), 416 ) 417 418 if cfg.CacheResults { 419 queryCacheMiddleware, err := queryrangebase.NewResultsCacheMiddleware( 420 log, 421 c, 422 cacheKeyLimits{limits}, 423 limits, 424 codec, 425 extractor, 426 cacheGenNumLoader, 427 func(r queryrangebase.Request) bool { 428 return !r.GetCachingOptions().Disabled 429 }, 430 registerer, 431 ) 432 if err != nil { 433 return nil, err 434 } 435 queryRangeMiddleware = append( 436 queryRangeMiddleware, 437 queryrangebase.InstrumentMiddleware("results_cache", metrics.InstrumentMiddlewareMetrics), 438 queryCacheMiddleware, 439 ) 440 } 441 442 if cfg.ShardedQueries { 443 queryRangeMiddleware = append(queryRangeMiddleware, 444 NewQueryShardMiddleware( 445 log, 446 schema.Configs, 447 metrics.InstrumentMiddlewareMetrics, // instrumentation is included in the sharding middleware 448 metrics.MiddlewareMapperMetrics.shardMapper, 449 limits, 450 ), 451 ) 452 } 453 454 if cfg.MaxRetries > 0 { 455 queryRangeMiddleware = append( 456 queryRangeMiddleware, 457 queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics), 458 queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics), 459 ) 460 } 461 462 return func(next http.RoundTripper) http.RoundTripper { 463 // Finally, if the user selected any query range middleware, stitch it in. 464 if len(queryRangeMiddleware) > 0 { 465 rt := NewLimitedRoundTripper(next, codec, limits, queryRangeMiddleware...) 466 return queryrangebase.RoundTripFunc(func(r *http.Request) (*http.Response, error) { 467 if !strings.HasSuffix(r.URL.Path, "/query_range") { 468 return next.RoundTrip(r) 469 } 470 return rt.RoundTrip(r) 471 }) 472 } 473 return next 474 }, nil 475 } 476 477 // NewInstantMetricTripperware creates a new frontend tripperware responsible for handling metric queries 478 func NewInstantMetricTripperware( 479 cfg Config, 480 log log.Logger, 481 limits Limits, 482 schema config.SchemaConfig, 483 codec queryrangebase.Codec, 484 metrics *Metrics, 485 ) (queryrangebase.Tripperware, error) { 486 queryRangeMiddleware := []queryrangebase.Middleware{StatsCollectorMiddleware(), NewLimitsMiddleware(limits)} 487 488 if cfg.ShardedQueries { 489 queryRangeMiddleware = append(queryRangeMiddleware, 490 NewSplitByRangeMiddleware(log, limits, metrics.MiddlewareMapperMetrics.rangeMapper), 491 NewQueryShardMiddleware( 492 log, 493 schema.Configs, 494 metrics.InstrumentMiddlewareMetrics, // instrumentation is included in the sharding middleware 495 metrics.MiddlewareMapperMetrics.shardMapper, 496 limits, 497 ), 498 ) 499 } 500 501 if cfg.MaxRetries > 0 { 502 queryRangeMiddleware = append( 503 queryRangeMiddleware, 504 queryrangebase.InstrumentMiddleware("retry", metrics.InstrumentMiddlewareMetrics), 505 queryrangebase.NewRetryMiddleware(log, cfg.MaxRetries, metrics.RetryMiddlewareMetrics), 506 ) 507 } 508 509 return func(next http.RoundTripper) http.RoundTripper { 510 if len(queryRangeMiddleware) > 0 { 511 return NewLimitedRoundTripper(next, codec, limits, queryRangeMiddleware...) 512 } 513 return next 514 }, nil 515 }