Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions prometheus/promhttp/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2022 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 promhttp

import "context"

type _PathKey string

var requestPathKey _PathKey

func WithRequestPath(ctx context.Context, path string) context.Context {
return context.WithValue(ctx, requestPathKey, path)
}

func pathFromContext(ctx context.Context) string {
if value, ok := ctx.Value(requestPathKey).(string); ok {
return value
}
return "*"
}
16 changes: 9 additions & 7 deletions prometheus/promhttp/instrument_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp
// InstrumentRoundTripperCounter is a middleware that wraps the provided
// http.RoundTripper to observe the request result with the provided CounterVec.
// The CounterVec must have zero, one, or two non-const non-curried labels. For
// those, the only allowed label names are "code" and "method". The function
// those, the only allowed label names are "code", "method" and "path". The function
// panics otherwise. For the "method" label a predefined default label value set
// is used to filter given values. Values besides predefined values will count
// as `unknown` method.`WithExtraMethods` can be used to add more
Expand All @@ -60,6 +60,7 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp
// is not incremented.
//
// Use with WithExemplarFromContext to instrument the exemplars on the counter of requests.
// Use with WithRequestPath to associate the request path with the context.
//
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
Expand All @@ -68,13 +69,13 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
o.apply(rtOpts)
}

code, method := checkLabels(counter)
code, method, path := checkLabels(counter)

return func(r *http.Request) (*http.Response, error) {
resp, err := next.RoundTrip(r)
if err == nil {
addWithExemplar(
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
counter.With(labels(code, method, path, r.Method, resp.StatusCode, rtOpts.getRequestPathFn(r), rtOpts.extraMethods...)),
1,
rtOpts.getExemplarFn(r.Context()),
)
Expand All @@ -86,8 +87,8 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
// InstrumentRoundTripperDuration is a middleware that wraps the provided
// http.RoundTripper to observe the request duration with the provided
// ObserverVec. The ObserverVec must have zero, one, or two non-const
// non-curried labels. For those, the only allowed label names are "code" and
// "method". The function panics otherwise. For the "method" label a predefined
// non-curried labels. For those, the only allowed label names are "code", "method"
// and "path". The function panics otherwise. For the "method" label a predefined
// default label value set is used to filter given values. Values besides
// predefined values will count as `unknown` method. `WithExtraMethods`
// can be used to add more methods to the set. The Observe method of the Observer
Expand All @@ -101,6 +102,7 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
// reported.
//
// Use with WithExemplarFromContext to instrument the exemplars on the duration histograms.
// Use with WithRequestPath to associate the request path with the context.
//
// Note that this method is only guaranteed to never observe negative durations
// if used with Go1.9+.
Expand All @@ -110,14 +112,14 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT
o.apply(rtOpts)
}

code, method := checkLabels(obs)
code, method, path := checkLabels(obs)

return func(r *http.Request) (*http.Response, error) {
start := time.Now()
resp, err := next.RoundTrip(r)
if err == nil {
observeWithExemplar(
obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
obs.With(labels(code, method, path, r.Method, resp.StatusCode, rtOpts.getRequestPathFn(r), rtOpts.extraMethods...)),
time.Since(start).Seconds(),
rtOpts.getExemplarFn(r.Context()),
)
Expand Down
16 changes: 11 additions & 5 deletions prometheus/promhttp/instrument_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func makeInstrumentedClient(opts ...Option) (*http.Client, *prometheus.Registry)
Name: "client_api_requests_total",
Help: "A counter for requests from the wrapped client.",
},
[]string{"code", "method"},
[]string{"code", "method", "path"},
)

dnsLatencyVec := prometheus.NewHistogramVec(
Expand Down Expand Up @@ -228,8 +228,9 @@ func TestClientMiddlewareAPI_WithRequestContext(t *testing.T) {
t.Fatalf("%v", err)
}

ctx := WithRequestPath(context.Background(), "/home")
// Set a context with a long timeout.
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel()
req = req.WithContext(ctx)

Expand All @@ -256,7 +257,7 @@ func TestClientMiddlewareAPI_WithRequestContext(t *testing.T) {
expected := `
# HELP client_api_requests_total A counter for requests from the wrapped client.
# TYPE client_api_requests_total counter
client_api_requests_total{code="200",method="get"} 1
client_api_requests_total{code="200",method="get",path="/home"} 1
`

if err := testutil.GatherAndCompare(reg, strings.NewReader(expected),
Expand Down Expand Up @@ -310,7 +311,7 @@ func ExampleInstrumentRoundTripperDuration() {
Name: "client_api_requests_total",
Help: "A counter for requests from the wrapped client.",
},
[]string{"code", "method"},
[]string{"code", "method", "path"},
)

// dnsLatencyVec uses custom buckets based on expected dns durations.
Expand Down Expand Up @@ -381,7 +382,12 @@ func ExampleInstrumentRoundTripperDuration() {
// Set the RoundTripper on our client.
client.Transport = roundTripper

resp, err := client.Get("http://google.com")
ctx := WithRequestPath(context.Background(), "/")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://google.com", nil)
if err != nil {
log.Printf("error: %v", err)
}
resp, err := client.Do(req)
if err != nil {
log.Printf("error: %v", err)
}
Expand Down
Loading