github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/http.go (about) 1 package querier 2 3 import ( 4 "context" 5 "net/http" 6 "strconv" 7 "time" 8 9 "github.com/go-kit/log" 10 "github.com/go-kit/log/level" 11 "github.com/gorilla/websocket" 12 "github.com/prometheus/client_golang/prometheus" 13 "github.com/prometheus/prometheus/model/labels" 14 "github.com/prometheus/prometheus/promql/parser" 15 "github.com/weaveworks/common/httpgrpc" 16 17 "github.com/grafana/dskit/tenant" 18 19 "github.com/grafana/loki/pkg/loghttp" 20 loghttp_legacy "github.com/grafana/loki/pkg/loghttp/legacy" 21 "github.com/grafana/loki/pkg/logql" 22 "github.com/grafana/loki/pkg/logql/syntax" 23 "github.com/grafana/loki/pkg/logqlmodel" 24 "github.com/grafana/loki/pkg/logqlmodel/stats" 25 index_stats "github.com/grafana/loki/pkg/storage/stores/index/stats" 26 "github.com/grafana/loki/pkg/util/httpreq" 27 util_log "github.com/grafana/loki/pkg/util/log" 28 "github.com/grafana/loki/pkg/util/marshal" 29 marshal_legacy "github.com/grafana/loki/pkg/util/marshal/legacy" 30 "github.com/grafana/loki/pkg/util/server" 31 serverutil "github.com/grafana/loki/pkg/util/server" 32 "github.com/grafana/loki/pkg/util/spanlogger" 33 util_validation "github.com/grafana/loki/pkg/util/validation" 34 "github.com/grafana/loki/pkg/validation" 35 ) 36 37 const ( 38 wsPingPeriod = 1 * time.Second 39 ) 40 41 type QueryResponse struct { 42 ResultType parser.ValueType `json:"resultType"` 43 Result parser.Value `json:"result"` 44 } 45 46 //nolint // QurierAPI defines HTTP handler functions for the querier. 47 type QuerierAPI struct { 48 querier Querier 49 cfg Config 50 limits *validation.Overrides 51 engine *logql.Engine 52 } 53 54 // NewQuerierAPI returns an instance of the QuerierAPI. 55 func NewQuerierAPI(cfg Config, querier Querier, limits *validation.Overrides, logger log.Logger) *QuerierAPI { 56 engine := logql.NewEngine(cfg.Engine, querier, limits, logger) 57 return &QuerierAPI{ 58 cfg: cfg, 59 limits: limits, 60 querier: querier, 61 engine: engine, 62 } 63 } 64 65 // RangeQueryHandler is a http.HandlerFunc for range queries. 66 func (q *QuerierAPI) RangeQueryHandler(w http.ResponseWriter, r *http.Request) { 67 // Enforce the query timeout while querying backends 68 ctx, cancel := context.WithDeadline(r.Context(), time.Now().Add(q.cfg.QueryTimeout)) 69 defer cancel() 70 71 request, err := loghttp.ParseRangeQuery(r) 72 if err != nil { 73 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 74 return 75 } 76 77 if err := q.validateEntriesLimits(ctx, request.Query, request.Limit); err != nil { 78 serverutil.WriteError(err, w) 79 return 80 } 81 82 params := logql.NewLiteralParams( 83 request.Query, 84 request.Start, 85 request.End, 86 request.Step, 87 request.Interval, 88 request.Direction, 89 request.Limit, 90 request.Shards, 91 ) 92 query := q.engine.Query(params) 93 result, err := query.Exec(ctx) 94 if err != nil { 95 serverutil.WriteError(err, w) 96 return 97 } 98 if err := marshal.WriteQueryResponseJSON(result, w); err != nil { 99 serverutil.WriteError(err, w) 100 return 101 } 102 } 103 104 // InstantQueryHandler is a http.HandlerFunc for instant queries. 105 func (q *QuerierAPI) InstantQueryHandler(w http.ResponseWriter, r *http.Request) { 106 // Enforce the query timeout while querying backends 107 ctx, cancel := context.WithDeadline(r.Context(), time.Now().Add(q.cfg.QueryTimeout)) 108 defer cancel() 109 110 request, err := loghttp.ParseInstantQuery(r) 111 if err != nil { 112 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 113 return 114 } 115 116 if err := q.validateEntriesLimits(ctx, request.Query, request.Limit); err != nil { 117 serverutil.WriteError(err, w) 118 return 119 } 120 121 params := logql.NewLiteralParams( 122 request.Query, 123 request.Ts, 124 request.Ts, 125 0, 126 0, 127 request.Direction, 128 request.Limit, 129 request.Shards, 130 ) 131 query := q.engine.Query(params) 132 result, err := query.Exec(ctx) 133 if err != nil { 134 serverutil.WriteError(err, w) 135 return 136 } 137 138 if err := marshal.WriteQueryResponseJSON(result, w); err != nil { 139 serverutil.WriteError(err, w) 140 return 141 } 142 } 143 144 // LogQueryHandler is a http.HandlerFunc for log only queries. 145 func (q *QuerierAPI) LogQueryHandler(w http.ResponseWriter, r *http.Request) { 146 // Enforce the query timeout while querying backends 147 ctx, cancel := context.WithDeadline(r.Context(), time.Now().Add(q.cfg.QueryTimeout)) 148 defer cancel() 149 150 request, err := loghttp.ParseRangeQuery(r) 151 if err != nil { 152 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 153 return 154 } 155 request.Query, err = parseRegexQuery(r) 156 if err != nil { 157 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 158 return 159 } 160 161 expr, err := syntax.ParseExpr(request.Query) 162 if err != nil { 163 serverutil.WriteError(err, w) 164 return 165 } 166 167 // short circuit metric queries 168 if _, ok := expr.(syntax.SampleExpr); ok { 169 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, "legacy endpoints only support %s result type", logqlmodel.ValueTypeStreams), w) 170 return 171 } 172 173 if err := q.validateEntriesLimits(ctx, request.Query, request.Limit); err != nil { 174 serverutil.WriteError(err, w) 175 return 176 } 177 178 params := logql.NewLiteralParams( 179 request.Query, 180 request.Start, 181 request.End, 182 request.Step, 183 request.Interval, 184 request.Direction, 185 request.Limit, 186 request.Shards, 187 ) 188 query := q.engine.Query(params) 189 190 result, err := query.Exec(ctx) 191 if err != nil { 192 serverutil.WriteError(err, w) 193 return 194 } 195 196 if err := marshal_legacy.WriteQueryResponseJSON(result, w); err != nil { 197 serverutil.WriteError(err, w) 198 return 199 } 200 } 201 202 // LabelHandler is a http.HandlerFunc for handling label queries. 203 func (q *QuerierAPI) LabelHandler(w http.ResponseWriter, r *http.Request) { 204 req, err := loghttp.ParseLabelQuery(r) 205 if err != nil { 206 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 207 return 208 } 209 210 log, ctx := spanlogger.New(r.Context(), "query.Label") 211 212 timer := prometheus.NewTimer(logql.QueryTime.WithLabelValues("labels")) 213 defer timer.ObserveDuration() 214 215 start := time.Now() 216 statsCtx, ctx := stats.NewContext(ctx) 217 218 resp, err := q.querier.Label(r.Context(), req) 219 queueTime, _ := ctx.Value(httpreq.QueryQueueTimeHTTPHeader).(time.Duration) 220 221 resLength := 0 222 if resp != nil { 223 resLength = len(resp.Values) 224 } 225 // record stats about the label query 226 statResult := statsCtx.Result(time.Since(start), queueTime, resLength) 227 statResult.Log(level.Debug(log)) 228 229 status := 200 230 if err != nil { 231 status, _ = server.ClientHTTPStatusAndError(err) 232 } 233 234 logql.RecordLabelQueryMetrics(ctx, log, *req.Start, *req.End, req.Name, strconv.Itoa(status), statResult) 235 236 if err != nil { 237 serverutil.WriteError(err, w) 238 return 239 } 240 241 if loghttp.GetVersion(r.RequestURI) == loghttp.VersionV1 { 242 err = marshal.WriteLabelResponseJSON(*resp, w) 243 } else { 244 err = marshal_legacy.WriteLabelResponseJSON(*resp, w) 245 } 246 if err != nil { 247 serverutil.WriteError(err, w) 248 return 249 } 250 } 251 252 // TailHandler is a http.HandlerFunc for handling tail queries. 253 func (q *QuerierAPI) TailHandler(w http.ResponseWriter, r *http.Request) { 254 upgrader := websocket.Upgrader{ 255 CheckOrigin: func(r *http.Request) bool { return true }, 256 } 257 logger := util_log.WithContext(r.Context(), util_log.Logger) 258 259 req, err := loghttp.ParseTailQuery(r) 260 if err != nil { 261 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 262 return 263 } 264 265 req.Query, err = parseRegexQuery(r) 266 if err != nil { 267 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 268 return 269 } 270 271 tenantID, err := tenant.TenantID(r.Context()) 272 if err != nil { 273 level.Warn(logger).Log("msg", "error getting tenant id", "err", err) 274 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 275 return 276 } 277 278 conn, err := upgrader.Upgrade(w, r, nil) 279 if err != nil { 280 level.Error(logger).Log("msg", "Error in upgrading websocket", "err", err) 281 return 282 } 283 284 level.Info(logger).Log("msg", "starting to tail logs", "tenant", tenantID, "selectors", req.Query) 285 286 defer func() { 287 level.Info(logger).Log("msg", "ended tailing logs", "tenant", tenantID, "selectors", req.Query) 288 }() 289 290 defer func() { 291 if err := conn.Close(); err != nil { 292 level.Error(logger).Log("msg", "Error closing websocket", "err", err) 293 } 294 }() 295 296 tailer, err := q.querier.Tail(r.Context(), req) 297 if err != nil { 298 if err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, err.Error())); err != nil { 299 level.Error(logger).Log("msg", "Error connecting to ingesters for tailing", "err", err) 300 } 301 return 302 } 303 defer func() { 304 if err := tailer.close(); err != nil { 305 level.Error(logger).Log("msg", "Error closing Tailer", "err", err) 306 } 307 }() 308 309 ticker := time.NewTicker(wsPingPeriod) 310 defer ticker.Stop() 311 312 var response *loghttp_legacy.TailResponse 313 responseChan := tailer.getResponseChan() 314 closeErrChan := tailer.getCloseErrorChan() 315 316 doneChan := make(chan struct{}) 317 go func() { 318 for { 319 _, _, err := conn.ReadMessage() 320 if err != nil { 321 if closeErr, ok := err.(*websocket.CloseError); ok { 322 if closeErr.Code == websocket.CloseNormalClosure { 323 break 324 } 325 level.Error(logger).Log("msg", "Error from client", "err", err) 326 break 327 } else if tailer.stopped { 328 return 329 } else { 330 level.Error(logger).Log("msg", "Unexpected error from client", "err", err) 331 break 332 } 333 } 334 } 335 doneChan <- struct{}{} 336 }() 337 338 for { 339 select { 340 case response = <-responseChan: 341 var err error 342 if loghttp.GetVersion(r.RequestURI) == loghttp.VersionV1 { 343 err = marshal.WriteTailResponseJSON(*response, conn) 344 } else { 345 err = marshal_legacy.WriteTailResponseJSON(*response, conn) 346 } 347 if err != nil { 348 level.Error(logger).Log("msg", "Error writing to websocket", "err", err) 349 if err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, err.Error())); err != nil { 350 level.Error(logger).Log("msg", "Error writing close message to websocket", "err", err) 351 } 352 return 353 } 354 355 case err := <-closeErrChan: 356 level.Error(logger).Log("msg", "Error from iterator", "err", err) 357 if err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, err.Error())); err != nil { 358 level.Error(logger).Log("msg", "Error writing close message to websocket", "err", err) 359 } 360 return 361 case <-ticker.C: 362 // This is to periodically check whether connection is active, useful to clean up dead connections when there are no entries to send 363 if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil { 364 level.Error(logger).Log("msg", "Error writing ping message to websocket", "err", err) 365 if err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, err.Error())); err != nil { 366 level.Error(logger).Log("msg", "Error writing close message to websocket", "err", err) 367 } 368 return 369 } 370 case <-doneChan: 371 return 372 } 373 } 374 } 375 376 // SeriesHandler returns the list of time series that match a certain label set. 377 // See https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers 378 func (q *QuerierAPI) SeriesHandler(w http.ResponseWriter, r *http.Request) { 379 req, err := loghttp.ParseAndValidateSeriesQuery(r) 380 if err != nil { 381 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 382 return 383 } 384 385 log, ctx := spanlogger.New(r.Context(), "query.Series") 386 387 timer := prometheus.NewTimer(logql.QueryTime.WithLabelValues("series")) 388 defer timer.ObserveDuration() 389 390 start := time.Now() 391 statsCtx, ctx := stats.NewContext(ctx) 392 393 resp, err := q.querier.Series(r.Context(), req) 394 queueTime, _ := ctx.Value(httpreq.QueryQueueTimeHTTPHeader).(time.Duration) 395 396 resLength := 0 397 if resp != nil { 398 resLength = len(resp.Series) 399 } 400 401 // record stats about the label query 402 statResult := statsCtx.Result(time.Since(start), queueTime, resLength) 403 statResult.Log(level.Debug(log)) 404 405 status := 200 406 if err != nil { 407 status, _ = server.ClientHTTPStatusAndError(err) 408 } 409 410 logql.RecordSeriesQueryMetrics(ctx, log, req.Start, req.End, req.Groups, strconv.Itoa(status), statResult) 411 if err != nil { 412 serverutil.WriteError(err, w) 413 return 414 } 415 416 err = marshal.WriteSeriesResponseJSON(*resp, w) 417 if err != nil { 418 serverutil.WriteError(err, w) 419 return 420 } 421 } 422 423 // IndexStatsHandler queries the index for the data statistics related to a query 424 func (q *QuerierAPI) IndexStatsHandler(w http.ResponseWriter, r *http.Request) { 425 426 req, err := loghttp.ParseIndexStatsQuery(r) 427 if err != nil { 428 serverutil.WriteError(httpgrpc.Errorf(http.StatusBadRequest, err.Error()), w) 429 return 430 } 431 432 _, ctx := spanlogger.New(r.Context(), "query.IndexStats") 433 434 // TODO(owen-d): log metadata, record stats? 435 resp, err := q.querier.IndexStats(ctx, req) 436 if resp == nil { 437 // Some stores don't implement this 438 resp = &index_stats.Stats{} 439 } 440 441 if err != nil { 442 serverutil.WriteError(err, w) 443 return 444 } 445 446 err = marshal.WriteIndexStatsResponseJSON(resp, w) 447 if err != nil { 448 serverutil.WriteError(err, w) 449 return 450 } 451 } 452 453 // parseRegexQuery parses regex and query querystring from httpRequest and returns the combined LogQL query. 454 // This is used only to keep regexp query string support until it gets fully deprecated. 455 func parseRegexQuery(httpRequest *http.Request) (string, error) { 456 query := httpRequest.Form.Get("query") 457 regexp := httpRequest.Form.Get("regexp") 458 if regexp != "" { 459 expr, err := syntax.ParseLogSelector(query, true) 460 if err != nil { 461 return "", err 462 } 463 newExpr, err := syntax.AddFilterExpr(expr, labels.MatchRegexp, "", regexp) 464 if err != nil { 465 return "", err 466 } 467 query = newExpr.String() 468 } 469 return query, nil 470 } 471 472 func (q *QuerierAPI) validateEntriesLimits(ctx context.Context, query string, limit uint32) error { 473 tenantIDs, err := tenant.TenantIDs(ctx) 474 if err != nil { 475 return httpgrpc.Errorf(http.StatusBadRequest, err.Error()) 476 } 477 478 expr, err := syntax.ParseExpr(query) 479 if err != nil { 480 return err 481 } 482 483 // entry limit does not apply to metric queries. 484 if _, ok := expr.(syntax.SampleExpr); ok { 485 return nil 486 } 487 488 maxEntriesLimit := util_validation.SmallestPositiveNonZeroIntPerTenant(tenantIDs, q.limits.MaxEntriesLimitPerQuery) 489 if int(limit) > maxEntriesLimit && maxEntriesLimit != 0 { 490 return httpgrpc.Errorf(http.StatusBadRequest, 491 "max entries limit per query exceeded, limit > max_entries_limit (%d > %d)", limit, maxEntriesLimit) 492 } 493 return nil 494 }