mirror of
https://github.com/grafana/loki.git
synced 2026-03-13 09:33:58 +08:00
145 lines
4.3 KiB
Go
145 lines
4.3 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/grafana/dskit/httpgrpc"
|
|
"github.com/grafana/dskit/user"
|
|
"github.com/prometheus/prometheus/promql"
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"github.com/gogo/googleapis/google/rpc"
|
|
"github.com/gogo/status"
|
|
|
|
"github.com/grafana/loki/v3/pkg/logqlmodel"
|
|
storage_errors "github.com/grafana/loki/v3/pkg/storage/errors"
|
|
"github.com/grafana/loki/v3/pkg/util"
|
|
)
|
|
|
|
// StatusClientClosedRequest is the status code for when a client request cancellation of an http request
|
|
const StatusClientClosedRequest = 499
|
|
|
|
const (
|
|
ErrClientCanceled = "the request was cancelled by the client"
|
|
ErrDeadlineExceeded = "request timed out, decrease the duration of the request or add more label matchers (prefer exact match over regex match) to reduce the amount of data processed"
|
|
)
|
|
|
|
type UserError string
|
|
|
|
func (e UserError) Error() string {
|
|
return string(e)
|
|
}
|
|
|
|
func ClientGrpcStatusAndError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
status, newErr := ClientHTTPStatusAndError(err)
|
|
return httpgrpc.Errorf(status, "%s", newErr.Error())
|
|
}
|
|
|
|
// WriteError write a go error with the correct status code.
|
|
func WriteError(err error, w http.ResponseWriter) {
|
|
status, cerr := ClientHTTPStatusAndError(err)
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.WriteHeader(status)
|
|
fmt.Fprint(w, cerr.Error())
|
|
}
|
|
|
|
// ClientHTTPStatusAndError returns error and http status that is "safe" to return to client without
|
|
// exposing any implementation details.
|
|
func ClientHTTPStatusAndError(err error) (int, error) {
|
|
if err == nil {
|
|
return http.StatusOK, nil
|
|
}
|
|
|
|
var (
|
|
queryErr storage_errors.QueryError
|
|
promErr promql.ErrStorage
|
|
userErr UserError
|
|
)
|
|
|
|
me, ok := err.(util.MultiError)
|
|
if ok && me.Is(context.Canceled) {
|
|
return StatusClientClosedRequest, errors.New(ErrClientCanceled)
|
|
}
|
|
if ok && me.IsDeadlineExceeded() {
|
|
return http.StatusGatewayTimeout, errors.New(ErrDeadlineExceeded)
|
|
}
|
|
|
|
// Return 400 if any of the errors in the MultiError are client errors (4xx)
|
|
if ok {
|
|
for _, e := range me {
|
|
if errors.As(e, &queryErr) ||
|
|
errors.Is(e, logqlmodel.ErrLimit) ||
|
|
errors.Is(e, logqlmodel.ErrParse) ||
|
|
errors.Is(e, logqlmodel.ErrPipeline) ||
|
|
errors.Is(e, logqlmodel.ErrBlocked) ||
|
|
errors.Is(e, logqlmodel.ErrParseMatchers) ||
|
|
errors.Is(e, logqlmodel.ErrUnsupportedSyntaxForInstantQuery) ||
|
|
errors.Is(e, user.ErrNoOrgID) ||
|
|
errors.As(e, &userErr) ||
|
|
errors.Is(e, logqlmodel.ErrVariantsDisabled) {
|
|
return http.StatusBadRequest, err
|
|
}
|
|
}
|
|
}
|
|
|
|
if s, isRPC := status.FromError(err); isRPC {
|
|
if s.Code() == codes.DeadlineExceeded {
|
|
return http.StatusGatewayTimeout, errors.New(ErrDeadlineExceeded)
|
|
} else if int(s.Code())/100 == 4 || int(s.Code())/100 == 5 {
|
|
return int(s.Code()), errors.New(s.Message())
|
|
}
|
|
return http.StatusInternalServerError, err
|
|
}
|
|
|
|
switch {
|
|
case errors.Is(err, context.Canceled) ||
|
|
(errors.As(err, &promErr) && errors.Is(promErr.Err, context.Canceled)):
|
|
return StatusClientClosedRequest, errors.New(ErrClientCanceled)
|
|
case errors.Is(err, context.DeadlineExceeded):
|
|
return http.StatusGatewayTimeout, errors.New(ErrDeadlineExceeded)
|
|
case errors.As(err, &queryErr):
|
|
return http.StatusBadRequest, err
|
|
case errors.Is(err, logqlmodel.ErrLimit) ||
|
|
errors.Is(err, logqlmodel.ErrIntervalLimit) ||
|
|
errors.Is(err, logqlmodel.ErrParse) ||
|
|
errors.Is(err, logqlmodel.ErrPipeline) ||
|
|
errors.Is(err, logqlmodel.ErrBlocked) ||
|
|
errors.Is(err, logqlmodel.ErrParseMatchers) ||
|
|
errors.Is(err, logqlmodel.ErrUnsupportedSyntaxForInstantQuery):
|
|
return http.StatusBadRequest, err
|
|
case errors.Is(err, user.ErrNoOrgID):
|
|
return http.StatusBadRequest, err
|
|
case errors.As(err, &userErr):
|
|
return http.StatusBadRequest, err
|
|
case errors.Is(err, logqlmodel.ErrVariantsDisabled):
|
|
return http.StatusBadRequest, err
|
|
default:
|
|
if grpcErr, ok := httpgrpc.HTTPResponseFromError(err); ok {
|
|
return int(grpcErr.Code), errors.New(string(grpcErr.Body))
|
|
}
|
|
return http.StatusInternalServerError, err
|
|
}
|
|
}
|
|
|
|
// WrapError wraps an error in a protobuf status.
|
|
func WrapError(err error) *rpc.Status {
|
|
if s, ok := status.FromError(err); ok {
|
|
return s.Proto()
|
|
}
|
|
|
|
code, err := ClientHTTPStatusAndError(err)
|
|
return status.New(codes.Code(code), err.Error()).Proto()
|
|
}
|
|
|
|
func UnwrapError(s *rpc.Status) error {
|
|
return status.ErrorProto(s)
|
|
}
|