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  }