@ -2,239 +2,44 @@ package querier
import (
"context"
"fmt"
"math"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/grafana/loki/pkg/loghttp"
loghttp_legacy "github.com/grafana/loki/pkg/loghttp/legacy"
"github.com/grafana/loki/pkg/logql"
"github.com/grafana/loki/pkg/logql/marshal"
marshal_legacy "github.com/grafana/loki/pkg/logql/marshal/legacy"
"github.com/cortexproject/cortex/pkg/util"
"github.com/go-kit/kit/log/level"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/promql"
"github.com/weaveworks/common/httpgrpc"
"github.com/weaveworks/common/httpgrpc/server"
)
const (
defaultQueryLimit = 100
defaultSince = 1 * time . Hour
wsPingPeriod = 1 * time . Second
maxDelayForInTailing = 5
)
// nolint
func intParam ( values url . Values , name string , def int ) ( int , error ) {
value := values . Get ( name )
if value == "" {
return def , nil
}
return strconv . Atoi ( value )
}
func unixNanoTimeParam ( values url . Values , name string , def time . Time ) ( time . Time , error ) {
value := values . Get ( name )
if value == "" {
return def , nil
}
if strings . Contains ( value , "." ) {
if t , err := strconv . ParseFloat ( value , 64 ) ; err == nil {
s , ns := math . Modf ( t )
ns = math . Round ( ns * 1000 ) / 1000
return time . Unix ( int64 ( s ) , int64 ( ns * float64 ( time . Second ) ) ) , nil
}
}
nanos , err := strconv . ParseInt ( value , 10 , 64 )
if err != nil {
if ts , err := time . Parse ( time . RFC3339Nano , value ) ; err == nil {
return ts , nil
}
return time . Time { } , err
}
if len ( value ) <= 10 {
return time . Unix ( nanos , 0 ) , nil
}
return time . Unix ( 0 , nanos ) , nil
}
// nolint
func directionParam ( values url . Values , name string , def logproto . Direction ) ( logproto . Direction , error ) {
value := values . Get ( name )
if value == "" {
return def , nil
}
d , ok := logproto . Direction_value [ strings . ToUpper ( value ) ]
if ! ok {
return logproto . FORWARD , fmt . Errorf ( "invalid direction '%s'" , value )
}
return logproto . Direction ( d ) , nil
}
// defaultQueryRangeStep returns the default step used in the query range API,
// which is dinamically calculated based on the time range
func defaultQueryRangeStep ( start time . Time , end time . Time ) int {
return int ( math . Max ( math . Floor ( end . Sub ( start ) . Seconds ( ) / 250 ) , 1 ) )
}
func httpRequestToInstantQueryRequest ( httpRequest * http . Request ) ( * instantQueryRequest , error ) {
params := httpRequest . URL . Query ( )
queryRequest := instantQueryRequest {
query : params . Get ( "query" ) ,
}
limit , err := intParam ( params , "limit" , defaultQueryLimit )
if err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
queryRequest . limit = uint32 ( limit )
queryRequest . ts , err = unixNanoTimeParam ( params , "time" , time . Now ( ) )
if err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
queryRequest . direction , err = directionParam ( params , "direction" , logproto . BACKWARD )
if err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
return & queryRequest , nil
}
func httpRequestToRangeQueryRequest ( httpRequest * http . Request ) ( * rangeQueryRequest , error ) {
var err error
params := httpRequest . URL . Query ( )
queryRequest := rangeQueryRequest {
query : params . Get ( "query" ) ,
}
queryRequest . limit , queryRequest . start , queryRequest . end , err = httpRequestToLookback ( httpRequest )
if err != nil {
return nil , err
}
step , err := intParam ( params , "step" , defaultQueryRangeStep ( queryRequest . start , queryRequest . end ) )
if err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
queryRequest . step = time . Duration ( step ) * time . Second
queryRequest . direction , err = directionParam ( params , "direction" , logproto . BACKWARD )
if err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
return & queryRequest , nil
}
func httpRequestToTailRequest ( httpRequest * http . Request ) ( * logproto . TailRequest , error ) {
params := httpRequest . URL . Query ( )
tailRequest := logproto . TailRequest {
Query : params . Get ( "query" ) ,
}
var err error
tailRequest . Limit , tailRequest . Start , _ , err = httpRequestToLookback ( httpRequest )
if err != nil {
return nil , err
}
// delay_for is used to allow server to let slow loggers catch up.
// Entries would be accumulated in a heap until they become older than now()-<delay_for>
delayFor , err := intParam ( params , "delay_for" , 0 )
if err != nil {
return nil , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
tailRequest . DelayFor = uint32 ( delayFor )
return & tailRequest , nil
}
func httpRequestToLookback ( httpRequest * http . Request ) ( limit uint32 , start , end time . Time , err error ) {
params := httpRequest . URL . Query ( )
now := time . Now ( )
lim , err := intParam ( params , "limit" , defaultQueryLimit )
if err != nil {
return 0 , time . Now ( ) , time . Now ( ) , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
limit = uint32 ( lim )
start , err = unixNanoTimeParam ( params , "start" , now . Add ( - defaultSince ) )
if err != nil {
return 0 , time . Now ( ) , time . Now ( ) , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
end , err = unixNanoTimeParam ( params , "end" , now )
if err != nil {
return 0 , time . Now ( ) , time . Now ( ) , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) )
}
return
}
// parseRegexQuery parses regex and query querystring from httpRequest and returns the combined LogQL query.
// This is used only to keep regexp query string support until it gets fully deprecated.
func parseRegexQuery ( httpRequest * http . Request ) ( string , error ) {
params := httpRequest . URL . Query ( )
query := params . Get ( "query" )
regexp := params . Get ( "regexp" )
if regexp != "" {
expr , err := logql . ParseLogSelector ( query )
if err != nil {
return "" , err
}
query = logql . NewFilterExpr ( expr , labels . MatchRegexp , regexp ) . String ( )
}
return query , nil
}
type QueryResponse struct {
ResultType promql . ValueType ` json:"resultType" `
Result promql . Value ` json:"result" `
}
type rangeQueryRequest struct {
query string
start , end time . Time
step time . Duration
limit uint32
direction logproto . Direction
}
type instantQueryRequest struct {
query string
ts time . Time
limit uint32
direction logproto . Direction
}
// RangeQueryHandler is a http.HandlerFunc for range queries.
func ( q * Querier ) RangeQueryHandler ( w http . ResponseWriter , r * http . Request ) {
// Enforce the query timeout while querying backends
ctx , cancel := context . WithDeadline ( r . Context ( ) , time . Now ( ) . Add ( q . cfg . QueryTimeout ) )
defer cancel ( )
request , err := httpRequestToRangeQueryRequest ( r )
request , err := loghttp . ParseRangeQuery ( r )
if err != nil {
server . WriteError ( w , err )
http . Error ( w , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) ) . Error ( ) , http . StatusBadRequest )
return
}
query := q . engine . NewRangeQuery ( q , request . q uery, request . s tart, request . e nd, request . s tep, request . d irection, request . l imit)
query := q . engine . NewRangeQuery ( q , request . Query , request . Start , request . End , request . Step , request . Direction , request . Limit )
result , err := query . Exec ( ctx )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
@ -253,12 +58,12 @@ func (q *Querier) InstantQueryHandler(w http.ResponseWriter, r *http.Request) {
ctx , cancel := context . WithDeadline ( r . Context ( ) , time . Now ( ) . Add ( q . cfg . QueryTimeout ) )
defer cancel ( )
request , err := httpRequestToInstantQueryRequest ( r )
request , err := loghttp . ParseInstantQuery ( r )
if err != nil {
server . WriteError ( w , err )
http . Error ( w , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) ) . Error ( ) , http . StatusBadRequest )
return
}
query := q . engine . NewInstantQuery ( q , request . q uery, request . t s, request . d irection, request . l imit)
query := q . engine . NewInstantQuery ( q , request . Q uery, request . T s, request . D irection, request . L imit)
result , err := query . Exec ( ctx )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
@ -277,18 +82,18 @@ func (q *Querier) LogQueryHandler(w http.ResponseWriter, r *http.Request) {
ctx , cancel := context . WithDeadline ( r . Context ( ) , time . Now ( ) . Add ( q . cfg . QueryTimeout ) )
defer cancel ( )
request , err := httpRequestToRangeQueryRequest ( r )
request , err := loghttp . ParseRangeQuery ( r )
if err != nil {
server . WriteError ( w , err )
http . Error ( w , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) ) . Error ( ) , http . StatusBadRequest )
return
}
request . q uery, err = parseRegexQuery ( r )
request . Q uery, err = parseRegexQuery ( r )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
http . Error ( w , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) ) . Error ( ) , http . StatusBadRequest )
return
}
query := q . engine . NewRangeQuery ( q , request . q uery, request . s tart, request . e nd, request . s tep, request . d irection, request . l imit)
query := q . engine . NewRangeQuery ( q , request . Q uery, request . S tart, request . E nd, request . S tep, request . D irection, request . L imit)
result , err := query . Exec ( ctx )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
@ -303,27 +108,11 @@ func (q *Querier) LogQueryHandler(w http.ResponseWriter, r *http.Request) {
// LabelHandler is a http.HandlerFunc for handling label queries.
func ( q * Querier ) LabelHandler ( w http . ResponseWriter , r * http . Request ) {
name , ok := mux . Vars ( r ) [ "name" ]
params := r . URL . Query ( )
now := time . Now ( )
req := & logproto . LabelRequest {
Values : ok ,
Name : name ,
}
end , err := unixNanoTimeParam ( params , "end" , now )
req , err := loghttp . ParseLabelQuery ( r )
if err != nil {
http . Error ( w , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) ) . Error ( ) , http . StatusBadRequest )
return
}
req . End = & end
start , err := unixNanoTimeParam ( params , "start" , end . Add ( - 6 * time . Hour ) )
if err != nil {
http . Error ( w , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) ) . Error ( ) , http . StatusBadRequest )
return
}
req . Start = & start
resp , err := q . Label ( r . Context ( ) , req )
if err != nil {
@ -348,24 +137,18 @@ func (q *Querier) TailHandler(w http.ResponseWriter, r *http.Request) {
CheckOrigin : func ( r * http . Request ) bool { return true } ,
}
tailRequestPtr , err := httpRequestToTailRequest ( r )
req , err := loghttp . ParseTailQuery ( r )
if err != nil {
server . WriteError ( w , err )
http . Error ( w , httpgrpc . Errorf ( http . StatusBadRequest , err . Error ( ) ) . Error ( ) , http . StatusBadRequest )
return
}
tailRequestPtr . Query , err = parseRegexQuery ( r )
req . Query , err = parseRegexQuery ( r )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
if tailRequestPtr . DelayFor > maxDelayForInTailing {
server . WriteError ( w , fmt . Errorf ( "delay_for can't be greater than %d" , maxDelayForInTailing ) )
level . Error ( util . Logger ) . Log ( "msg" , "Error in upgrading websocket" , "err" , err )
return
}
conn , err := upgrader . Upgrade ( w , r , nil )
if err != nil {
level . Error ( util . Logger ) . Log ( "msg" , "Error in upgrading websocket" , "err" , err )
@ -378,11 +161,7 @@ func (q *Querier) TailHandler(w http.ResponseWriter, r *http.Request) {
}
} ( )
// response from httpRequestToQueryRequest is a ptr, if we keep passing pointer down the call then it would stay on
// heap until connection to websocket stays open
tailRequest := * tailRequestPtr
tailer , err := q . Tail ( r . Context ( ) , & tailRequest )
tailer , err := q . Tail ( r . Context ( ) , req )
if err != nil {
if err := conn . WriteMessage ( websocket . CloseMessage , websocket . FormatCloseMessage ( websocket . CloseInternalServerErr , err . Error ( ) ) ) ; err != nil {
level . Error ( util . Logger ) . Log ( "msg" , "Error connecting to ingesters for tailing" , "err" , err )
@ -437,3 +216,19 @@ func (q *Querier) TailHandler(w http.ResponseWriter, r *http.Request) {
}
}
}
// parseRegexQuery parses regex and query querystring from httpRequest and returns the combined LogQL query.
// This is used only to keep regexp query string support until it gets fully deprecated.
func parseRegexQuery ( httpRequest * http . Request ) ( string , error ) {
params := httpRequest . URL . Query ( )
query := params . Get ( "query" )
regexp := params . Get ( "regexp" )
if regexp != "" {
expr , err := logql . ParseLogSelector ( query )
if err != nil {
return "" , err
}
query = logql . NewFilterExpr ( expr , labels . MatchRegexp , regexp ) . String ( )
}
return query , nil
}