Files
Michael Mandrus 5626461b3c Caching: Refactor enterprise query caching middleware to a wire service (#65616)
* define initial service and add to wire

* update caching service interface

* add skipQueryCache header handler and update metrics query function to use it

* add caching service as a dependency to query service

* working caching impl

* propagate cache status to frontend in response

* beginning of improvements suggested by Lean - separate caching logic from query logic.

* more changes to simplify query function

* Decided to revert renaming of function

* Remove error status from cache request

* add extra documentation

* Move query caching duration metric to query package

* add a little bit of documentation

* wip: convert resource caching

* Change return type of query service QueryData to a QueryDataResponse with Headers

* update codeowners

* change X-Cache value to const

* use resource caching in endpoint handlers

* write resource headers to response even if it's not a cache hit

* fix panic caused by lack of nil check

* update unit test

* remove NONE header - shouldn't show up in OSS

* Convert everything to use the plugin middleware

* revert a few more things

* clean up unused vars

* start reverting resource caching, start to implement in plugin middleware

* revert more, fix typo

* Update caching interfaces - resource caching now has a separate cache method

* continue wiring up new resource caching conventions - still in progress

* add more safety to implementation

* remove some unused objects

* remove some code that I left in by accident

* add some comments, fix codeowners, fix duplicate registration

* fix source of panic in resource middleware

* Update client decorator test to provide an empty response object

* create tests for caching middleware

* fix unit test

* Update pkg/services/caching/service.go

Co-authored-by: Arati R. <33031346+suntala@users.noreply.github.com>

* improve error message in error log

* quick docs update

* Remove use of mockery. Update return signature to return an explicit hit/miss bool

* create unit test for empty request context

* rename caching metrics to make it clear they pertain to caching

* Update pkg/services/pluginsintegration/clientmiddleware/caching_middleware.go

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>

* Add clarifying comments to cache skip middleware func

* Add comment pointing to the resource cache update call

* fix unit tests (missing dependency)

* try to fix mystery syntax error

* fix a panic

* Caching: Introduce feature toggle to caching service refactor (#66323)

* introduce new feature toggle

* hide calls to new service behind a feature flag

* remove licensing flag from toggle (misunderstood what it was for)

* fix unit tests

* rerun toggle gen

---------

Co-authored-by: Arati R. <33031346+suntala@users.noreply.github.com>
Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2023-04-12 12:30:33 -04:00

178 lines
4.5 KiB
Go

package contextmodel
import (
"errors"
"net/http"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/grafana/grafana/pkg/web"
)
type ReqContext struct {
*web.Context
*user.SignedInUser
UserToken *usertoken.UserToken
IsSignedIn bool
IsRenderCall bool
AllowAnonymous bool
SkipDSCache bool
SkipQueryCache bool
Logger log.Logger
Error error
// RequestNonce is a cryptographic request identifier for use with Content Security Policy.
RequestNonce string
IsPublicDashboardView bool
PerfmonTimer prometheus.Summary
LookupTokenErr error
}
// Handle handles and logs error by given status.
func (ctx *ReqContext) Handle(cfg *setting.Cfg, status int, title string, err error) {
data := struct {
Title string
AppTitle string
AppSubUrl string
Theme string
ErrorMsg error
}{title, "Grafana", cfg.AppSubURL, "dark", nil}
if err != nil {
ctx.Logger.Error(title, "error", err)
if setting.Env != setting.Prod {
data.ErrorMsg = err
}
}
ctx.HTML(status, cfg.ErrTemplateName, data)
}
func (ctx *ReqContext) IsApiRequest() bool {
return strings.HasPrefix(ctx.Req.URL.Path, "/api")
}
func (ctx *ReqContext) JsonApiErr(status int, message string, err error) {
resp := make(map[string]interface{})
traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false)
if err != nil {
resp["traceID"] = traceID
if status == http.StatusInternalServerError {
ctx.Logger.Error(message, "error", err, "traceID", traceID)
} else {
ctx.Logger.Warn(message, "error", err, "traceID", traceID)
}
if setting.Env != setting.Prod {
resp["error"] = err.Error()
}
}
switch status {
case http.StatusNotFound:
resp["message"] = "Not Found"
case http.StatusInternalServerError:
resp["message"] = "Internal Server Error"
}
if message != "" {
resp["message"] = message
}
ctx.JSON(status, resp)
}
// WriteErr writes an error response based on errutil.Error.
// If provided error is not errutil.Error a 500 response is written.
func (ctx *ReqContext) WriteErr(err error) {
ctx.writeErrOrFallback(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), err)
}
// WriteErrOrFallback uses the information in an errutil.Error if available
// and otherwise falls back to the status and message provided as arguments.
func (ctx *ReqContext) WriteErrOrFallback(status int, message string, err error) {
ctx.writeErrOrFallback(status, message, err)
}
func (ctx *ReqContext) writeErrOrFallback(status int, message string, err error) {
data := make(map[string]interface{})
statusResponse := status
traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false)
if err != nil {
data["traceID"] = traceID
var logMessage string
logger := ctx.Logger.Warn
gfErr := errutil.Error{}
if errors.As(err, &gfErr) {
logger = gfErr.LogLevel.LogFunc(ctx.Logger)
publicErr := gfErr.Public()
// need to manually set these fields because we want to include the trace id
data["extra"] = publicErr.Extra
data["message"] = publicErr.Message
data["messageId"] = publicErr.MessageID
data["statusCode"] = publicErr.StatusCode
statusResponse = publicErr.StatusCode
} else {
if message != "" {
logMessage = message
} else {
logMessage = http.StatusText(status)
data["message"] = logMessage
}
if status == http.StatusInternalServerError {
logger = ctx.Logger.Error
}
}
if errutil.HasUnifiedLogging(ctx.Req.Context()) {
ctx.Error = err
} else {
logger(logMessage, "error", err, "remote_addr", ctx.RemoteAddr(), "traceID", traceID)
}
}
if _, ok := data["message"]; !ok && message != "" {
data["message"] = message
}
ctx.JSON(statusResponse, data)
}
func (ctx *ReqContext) HasUserRole(role org.RoleType) bool {
return ctx.OrgRole.Includes(role)
}
func (ctx *ReqContext) HasHelpFlag(flag user.HelpFlags1) bool {
return ctx.HelpFlags1.HasFlag(flag)
}
func (ctx *ReqContext) TimeRequest(timer prometheus.Summary) {
ctx.PerfmonTimer = timer
}
// QueryBoolWithDefault extracts a value from the request query params and applies a bool default if not present.
func (ctx *ReqContext) QueryBoolWithDefault(field string, d bool) bool {
f := ctx.Query(field)
if f == "" {
return d
}
return ctx.QueryBool(field)
}