diff --git a/util/httputil/context.go b/util/httputil/context.go new file mode 100644 index 000000000..617e2a9d3 --- /dev/null +++ b/util/httputil/context.go @@ -0,0 +1,50 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package httputil + +import ( + "context" + "net" + "net/http" + + "github.com/prometheus/prometheus/promql" +) + +type ctxParam int + +var pathParam ctxParam + +// ContextWithPath returns a new context with the given path to be used later +// when logging the query. +func ContextWithPath(ctx context.Context, path string) context.Context { + return context.WithValue(ctx, pathParam, path) +} + +// ContextFromRequest returns a new context from a requests with identifiers of +// the request to be used later when logging the query. +func ContextFromRequest(ctx context.Context, r *http.Request) (context.Context, error) { + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return ctx, err + } + var path string + if v := ctx.Value(pathParam); v != nil { + path = v.(string) + } + return promql.NewOriginContext(ctx, map[string]string{ + "clientIP": ip, + "method": r.Method, + "path": path, + }), nil +} diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 871296e89..4aad787d6 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -18,7 +18,6 @@ import ( "fmt" "math" "math/rand" - "net" "net/http" "net/url" "os" @@ -342,7 +341,7 @@ func (api *API) query(r *http.Request) apiFuncResult { return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil} } - ctx, err = contextFromRequest(ctx, r) + ctx, err = httputil.ContextFromRequest(ctx, r) if err != nil { return apiFuncResult{nil, returnAPIError(err), nil, nil} } @@ -417,7 +416,7 @@ func (api *API) queryRange(r *http.Request) apiFuncResult { return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil} } - ctx, err = contextFromRequest(ctx, r) + ctx, err = httputil.ContextFromRequest(ctx, r) if err != nil { return apiFuncResult{nil, returnAPIError(err), nil, nil} } @@ -1480,11 +1479,3 @@ func marshalPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { func marshalPointJSONIsEmpty(ptr unsafe.Pointer) bool { return false } - -func contextFromRequest(ctx context.Context, r *http.Request) (context.Context, error) { - ip, _, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { - return ctx, err - } - return promql.NewOriginContext(ctx, map[string]string{"clientIP": ip}), nil -} diff --git a/web/web.go b/web/web.go index 03fa38757..33001475a 100644 --- a/web/web.go +++ b/web/web.go @@ -253,7 +253,11 @@ func New(logger log.Logger, o *Options) *Handler { } m := newMetrics(o.Registerer) - router := route.New().WithInstrumentation(m.instrumentHandler) + router := route.New(). + WithInstrumentation(combineInstrumentations( + m.instrumentHandler, + setPathWithPrefix(""), + )) cwd, err := os.Getwd() if err != nil { @@ -542,13 +546,17 @@ func (h *Handler) Run(ctx context.Context) error { mux := http.NewServeMux() mux.Handle("/", h.router) - av1 := route.New().WithInstrumentation(h.metrics.instrumentHandlerWithPrefix("/api/v1")) - h.apiV1.Register(av1) apiPath := "/api" if h.options.RoutePrefix != "/" { apiPath = h.options.RoutePrefix + apiPath level.Info(h.logger).Log("msg", "router prefix", "prefix", h.options.RoutePrefix) } + av1 := route.New(). + WithInstrumentation(combineInstrumentations( + h.metrics.instrumentHandlerWithPrefix("/api/v1"), + setPathWithPrefix(apiPath+"/v1"), + )) + h.apiV1.Register(av1) mux.Handle(apiPath+"/v1/", http.StripPrefix(apiPath+"/v1", av1)) @@ -643,6 +651,12 @@ func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) { return } + ctx, err = httputil.ContextFromRequest(ctx, r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // Provide URL parameters as a map for easy use. Advanced users may have need for // parameters beyond the first, so provide RawParams. rawParams, err := url.ParseQuery(r.URL.RawQuery) @@ -685,7 +699,7 @@ func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) { } tmpl := template.NewTemplateExpander( - h.context, + ctx, strings.Join(append(defs, string(text)), ""), "__console_"+name, data, @@ -1103,3 +1117,20 @@ type AlertByStateCount struct { Pending int32 Firing int32 } + +func combineInstrumentations(fs ...func(handlerName string, handler http.HandlerFunc) http.HandlerFunc) func(string, http.HandlerFunc) http.HandlerFunc { + return func(handlerName string, handler http.HandlerFunc) http.HandlerFunc { + for _, f := range fs { + handler = f(handlerName, handler) + } + return handler + } +} + +func setPathWithPrefix(prefix string) func(handlerName string, handler http.HandlerFunc) http.HandlerFunc { + return func(handlerName string, handler http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + handler(w, r.WithContext(httputil.ContextWithPath(r.Context(), prefix+r.URL.Path))) + } + } +}