mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Middleware: Add team metadata to HTTP handlers (#71010)
Signed-off-by: bergquist <carl.bergquist@gmail.com>
This commit is contained in:
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
@@ -70,7 +71,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// not logged in views
|
||||
r.Get("/logout", hs.Logout)
|
||||
r.Post("/login", quota(string(auth.QuotaTargetSrv)), routing.Wrap(hs.LoginPost))
|
||||
r.Post("/login", requestmeta.SetOwner(requestmeta.TeamAuth), quota(string(auth.QuotaTargetSrv)), routing.Wrap(hs.LoginPost))
|
||||
r.Get("/login/:name", quota(string(auth.QuotaTargetSrv)), hs.OAuthLogin)
|
||||
r.Get("/login", hs.LoginView)
|
||||
r.Get("/invite/:code", hs.Index)
|
||||
@@ -539,7 +540,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
alertsRoute.Get("/:alertId", hs.ValidateOrgAlert, routing.Wrap(hs.GetAlert))
|
||||
alertsRoute.Get("/", routing.Wrap(hs.GetAlerts))
|
||||
alertsRoute.Get("/states-for-dashboard", routing.Wrap(hs.GetAlertStatesForDashboard))
|
||||
})
|
||||
}, requestmeta.SetOwner(requestmeta.TeamAlerting))
|
||||
|
||||
var notifiersAuthHandler web.Handler
|
||||
if hs.Cfg.UnifiedAlerting.IsEnabled() {
|
||||
@@ -548,7 +549,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
notifiersAuthHandler = reqEditorRole
|
||||
}
|
||||
|
||||
apiRoute.Get("/alert-notifiers", notifiersAuthHandler, routing.Wrap(
|
||||
apiRoute.Get("/alert-notifiers", notifiersAuthHandler, requestmeta.SetOwner(requestmeta.TeamAlerting), routing.Wrap(
|
||||
hs.GetAlertNotifiers(hs.Cfg.UnifiedAlerting.IsEnabled())),
|
||||
)
|
||||
|
||||
@@ -562,12 +563,12 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
alertNotifications.Get("/uid/:uid", routing.Wrap(hs.GetAlertNotificationByUID))
|
||||
alertNotifications.Put("/uid/:uid", routing.Wrap(hs.UpdateAlertNotificationByUID))
|
||||
alertNotifications.Delete("/uid/:uid", routing.Wrap(hs.DeleteAlertNotificationByUID))
|
||||
}, reqEditorRole)
|
||||
}, reqEditorRole, requestmeta.SetOwner(requestmeta.TeamAlerting))
|
||||
|
||||
// alert notifications without requirement of user to be org editor
|
||||
apiRoute.Group("/alert-notifications", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Get("/lookup", routing.Wrap(hs.GetAlertNotificationLookup))
|
||||
})
|
||||
}, requestmeta.SetOwner(requestmeta.TeamAlerting))
|
||||
|
||||
apiRoute.Get("/annotations", authorize(ac.EvalPermission(ac.ActionAnnotationsRead)), routing.Wrap(hs.GetAnnotations))
|
||||
apiRoute.Post("/annotations/mass-delete", authorize(ac.EvalPermission(ac.ActionAnnotationsDelete)), routing.Wrap(hs.MassDeleteAnnotations))
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/middleware/csrf"
|
||||
"github.com/grafana/grafana/pkg/middleware/loggermw"
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||
"github.com/grafana/grafana/pkg/registry/corekind"
|
||||
@@ -203,6 +204,7 @@ type HTTPServer struct {
|
||||
statsService stats.Service
|
||||
authnService authn.Service
|
||||
starApi *starApi.API
|
||||
promRegister prometheus.Registerer
|
||||
}
|
||||
|
||||
type ServerOptions struct {
|
||||
@@ -244,7 +246,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
accesscontrolService accesscontrol.Service, navTreeService navtree.Service,
|
||||
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService,
|
||||
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service,
|
||||
starApi *starApi.API,
|
||||
starApi *starApi.API, promRegister prometheus.Registerer,
|
||||
|
||||
) (*HTTPServer, error) {
|
||||
web.Env = cfg.Env
|
||||
@@ -346,6 +348,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
authnService: authnService,
|
||||
pluginsCDNService: pluginsCDNService,
|
||||
starApi: starApi,
|
||||
promRegister: promRegister,
|
||||
}
|
||||
if hs.Listener != nil {
|
||||
hs.log.Debug("Using provided listener")
|
||||
@@ -568,8 +571,9 @@ func (hs *HTTPServer) applyRoutes() {
|
||||
func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
|
||||
m := hs.web
|
||||
|
||||
m.Use(requestmeta.SetupRequestMetadata())
|
||||
m.Use(middleware.RequestTracing(hs.tracer))
|
||||
m.Use(middleware.RequestMetrics(hs.Features))
|
||||
m.Use(middleware.RequestMetrics(hs.Features, hs.Cfg, hs.promRegister))
|
||||
|
||||
m.UseMiddleware(hs.LoggerMiddleware.Middleware())
|
||||
|
||||
|
||||
40
pkg/middleware/request_metadata_test.go
Normal file
40
pkg/middleware/request_metadata_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRequestMetaDefault(t *testing.T) {
|
||||
m := web.New()
|
||||
m.Use(requestmeta.SetupRequestMetadata())
|
||||
|
||||
m.Get("/", func(rw http.ResponseWriter, req *http.Request) {
|
||||
v := requestmeta.GetRequestMetaData(req.Context())
|
||||
assert.Equal(t, requestmeta.TeamCore, v.Team)
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
m.ServeHTTP(httptest.NewRecorder(), req)
|
||||
}
|
||||
|
||||
func TestRequestMetaNewTeam(t *testing.T) {
|
||||
m := web.New()
|
||||
m.Use(requestmeta.SetupRequestMetadata())
|
||||
|
||||
m.Get("/",
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting), // set new owner for this route.
|
||||
func(rw http.ResponseWriter, req *http.Request) {
|
||||
v := requestmeta.GetRequestMetaData(req.Context())
|
||||
assert.Equal(t, requestmeta.TeamAlerting, v.Team)
|
||||
})
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
assert.NoError(t, err)
|
||||
m.ServeHTTP(httptest.NewRecorder(), r)
|
||||
}
|
||||
@@ -11,21 +11,23 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
var (
|
||||
httpRequestsInFlight prometheus.Gauge
|
||||
httpRequestDurationHistogram *prometheus.HistogramVec
|
||||
|
||||
// DefBuckets are histogram buckets for the response time (in seconds)
|
||||
// of a network service, including one that is responding very slowly.
|
||||
defBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 25}
|
||||
)
|
||||
|
||||
func init() {
|
||||
httpRequestsInFlight = prometheus.NewGauge(
|
||||
// RequestMetrics is a middleware handler that instruments the request.
|
||||
func RequestMetrics(features featuremgmt.FeatureToggles, cfg *setting.Cfg, promRegister prometheus.Registerer) web.Middleware {
|
||||
log := log.New("middleware.request-metrics")
|
||||
|
||||
httpRequestsInFlight := prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "grafana",
|
||||
Name: "http_request_in_flight",
|
||||
@@ -33,22 +35,22 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
httpRequestDurationHistogram = prometheus.NewHistogramVec(
|
||||
histogramLabels := []string{"handler", "status_code", "method"}
|
||||
if cfg.MetricsIncludeTeamLabel {
|
||||
histogramLabels = append(histogramLabels, "team")
|
||||
}
|
||||
|
||||
httpRequestDurationHistogram := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "grafana",
|
||||
Name: "http_request_duration_seconds",
|
||||
Help: "Histogram of latencies for HTTP requests.",
|
||||
Buckets: defBuckets,
|
||||
},
|
||||
[]string{"handler", "status_code", "method"},
|
||||
histogramLabels,
|
||||
)
|
||||
|
||||
prometheus.MustRegister(httpRequestsInFlight, httpRequestDurationHistogram)
|
||||
}
|
||||
|
||||
// RequestMetrics is a middleware handler that instruments the request.
|
||||
func RequestMetrics(features featuremgmt.FeatureToggles) web.Middleware {
|
||||
log := log.New("middleware.request-metrics")
|
||||
promRegister.MustRegister(httpRequestsInFlight, httpRequestDurationHistogram)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -77,10 +79,17 @@ func RequestMetrics(features featuremgmt.FeatureToggles) web.Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
labelValues := []string{handler, code, r.Method}
|
||||
if cfg.MetricsIncludeTeamLabel {
|
||||
rmd := requestmeta.GetRequestMetaData(r.Context())
|
||||
labelValues = append(labelValues, rmd.Team)
|
||||
}
|
||||
|
||||
// avoiding the sanitize functions for in the new instrumentation
|
||||
// since they dont make much sense. We should remove them later.
|
||||
histogram := httpRequestDurationHistogram.
|
||||
WithLabelValues(handler, code, r.Method)
|
||||
WithLabelValues(labelValues...)
|
||||
|
||||
if traceID := tracing.TraceIDFromContext(r.Context(), true); traceID != "" {
|
||||
// Need to type-convert the Observer to an
|
||||
// ExemplarObserver. This will always work for a
|
||||
|
||||
75
pkg/middleware/requestmeta/request_metadata.go
Normal file
75
pkg/middleware/requestmeta/request_metadata.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package requestmeta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
const (
|
||||
TeamAlerting = "alerting"
|
||||
TeamAuth = "auth"
|
||||
TeamCore = "core"
|
||||
)
|
||||
|
||||
type rMDContextKey struct{}
|
||||
|
||||
type RequestMetaData struct {
|
||||
Team string
|
||||
}
|
||||
|
||||
var requestMetaDataContextKey = rMDContextKey{}
|
||||
|
||||
// SetupRequestMetadata injects defaul request metadata values
|
||||
// on the request context.
|
||||
func SetupRequestMetadata() web.Middleware {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rmd := defaultRequestMetadata()
|
||||
|
||||
ctx := context.WithValue(r.Context(), requestMetaDataContextKey, rmd)
|
||||
*r = *r.WithContext(ctx)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequestMetaData returns the request metadata for the context.
|
||||
// if request metadata is missing it will return the default values.
|
||||
func GetRequestMetaData(ctx context.Context) *RequestMetaData {
|
||||
val := ctx.Value(requestMetaDataContextKey)
|
||||
|
||||
value, ok := val.(*RequestMetaData)
|
||||
if ok {
|
||||
return value
|
||||
}
|
||||
|
||||
return defaultRequestMetadata()
|
||||
}
|
||||
|
||||
// SetRequestMetaData returns an `web.Handler` that overrides the request metadata
|
||||
// with the provided param.
|
||||
func SetRequestMetaData(rmd RequestMetaData) web.Handler {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
v := GetRequestMetaData(r.Context())
|
||||
if rmd.Team != "" {
|
||||
v.Team = rmd.Team
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetOwner returns an `web.Handler` that sets the team name for an request.
|
||||
func SetOwner(team string) web.Handler {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
v := GetRequestMetaData(r.Context())
|
||||
v.Team = team
|
||||
}
|
||||
}
|
||||
|
||||
func defaultRequestMetadata() *RequestMetaData {
|
||||
return &RequestMetaData{
|
||||
Team: TeamCore,
|
||||
}
|
||||
}
|
||||
@@ -256,6 +256,10 @@ type Cfg struct {
|
||||
MetricsEndpointBasicAuthUsername string
|
||||
MetricsEndpointBasicAuthPassword string
|
||||
MetricsEndpointDisableTotalStats bool
|
||||
// MetricsIncludeTeamLabel configures grafana to set a label for
|
||||
// the team responsible for the code at Grafana labs. We don't expect anyone else to
|
||||
// use this setting.
|
||||
MetricsIncludeTeamLabel bool
|
||||
MetricsTotalStatsIntervalSeconds int
|
||||
MetricsGrafanaEnvironmentInfo map[string]string
|
||||
|
||||
@@ -1088,6 +1092,7 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
|
||||
cfg.MetricsEndpointBasicAuthUsername = valueAsString(iniFile.Section("metrics"), "basic_auth_username", "")
|
||||
cfg.MetricsEndpointBasicAuthPassword = valueAsString(iniFile.Section("metrics"), "basic_auth_password", "")
|
||||
cfg.MetricsEndpointDisableTotalStats = iniFile.Section("metrics").Key("disable_total_stats").MustBool(false)
|
||||
cfg.MetricsIncludeTeamLabel = iniFile.Section("metrics").Key("include_team_label").MustBool(false)
|
||||
cfg.MetricsTotalStatsIntervalSeconds = iniFile.Section("metrics").Key("total_stats_collector_interval_seconds").MustInt(1800)
|
||||
|
||||
analytics := iniFile.Section("analytics")
|
||||
|
||||
Reference in New Issue
Block a user