diff --git a/client.go b/client.go index c2ec8b0..8093034 100644 --- a/client.go +++ b/client.go @@ -11,6 +11,10 @@ import ( "github.com/prometheus/client_golang/prometheus" ) +const ( + BogusStatusCode = 999 +) + // Transport is a http.RoundTripper that collects Prometheus metrics of every // request it processes. It allows to be configured with callbacks that process // request path and query into a suitable label value. @@ -23,6 +27,7 @@ type Transport struct { type Callbacks struct { PathProcessor func(string) string QueryProcessor func(string) string + CodeProcessor func(int) string } const ( @@ -52,6 +57,15 @@ var ( parts := strings.Split(path, "/") return parts[len(parts)-1] } + // IntToStringProcessor converts an integer value to its string representation. + IntToStringProcessor = func(input int) string { return fmt.Sprintf("%d", input) } + // ServerErrorCodeProcessor exports all failed responses (5xx, timeouts, ...) as status=failure + ServerErrorCodeProcessor = func(code int) string { + if code >= http.StatusInternalServerError { + return "failure" + } + return "success" + } ) // init registers the Prometheus metric globally when the package is loaded. @@ -62,7 +76,7 @@ func init() { // RoundTrip implements http.RoundTripper. It forwards the request to the // next RoundTripper and measures the time it took in Prometheus summary. func (it *Transport) RoundTrip(req *http.Request) (*http.Response, error) { - var statusCode int + statusCode := BogusStatusCode // Remember the current time. now := time.Now() @@ -80,7 +94,7 @@ func (it *Transport) RoundTrip(req *http.Request) (*http.Response, error) { it.cbs.PathProcessor(req.URL.Path), it.cbs.QueryProcessor(req.URL.RawQuery), req.Method, - fmt.Sprintf("%d", statusCode), + it.cbs.CodeProcessor(statusCode), ).Observe(time.Since(now).Seconds()) // return the response and error reported from the next RoundTripper. @@ -123,6 +137,10 @@ func NewTransport(next http.RoundTripper, cbs *Callbacks) http.RoundTripper { if cbs.QueryProcessor == nil { cbs.QueryProcessor = EliminatingProcessor } + // By default, status code is set as is. + if cbs.CodeProcessor == nil { + cbs.CodeProcessor = IntToStringProcessor + } return &Transport{next: next, cbs: cbs} } diff --git a/examples/status-code/main.go b/examples/status-code/main.go new file mode 100644 index 0000000..1acf970 --- /dev/null +++ b/examples/status-code/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "time" + + "github.com/linki/instrumented_http" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +func main() { + go func() { + http.Handle("/metrics", promhttp.Handler()) + log.Fatal(http.ListenAndServe("127.0.0.1:9099", nil)) + }() + + client := instrumented_http.NewClient(nil, &instrumented_http.Callbacks{ + PathProcessor: instrumented_http.IdentityProcessor, + QueryProcessor: instrumented_http.IdentityProcessor, + CodeProcessor: func(code int) string { + // export all status codes >= 400 as label status=failure instead of their individual values. + if code >= http.StatusBadRequest { + return "failure" + } + return "success" + }, + }) + + for { + func() { + resp, err := client.Get("https://kubernetes.io/docs/search/?q=pods") + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + fmt.Printf("%d\n", resp.StatusCode) + }() + + time.Sleep(10 * time.Second) + } +} + +// expected result: +// +// $ curl -Ss 127.0.0.1:9099/metrics | grep http +// +// http_request_duration_seconds{handler="instrumented_http",host="kubernetes.io",method="GET",path="/docs/search/",query="q=pods",scheme="https",status="success",quantile="0.5"} 0.662351 +// http_request_duration_seconds{handler="instrumented_http",host="kubernetes.io",method="GET",path="/docs/search/",query="q=pods",scheme="https",status="success",quantile="0.9"} 1.000437 +// http_request_duration_seconds{handler="instrumented_http",host="kubernetes.io",method="GET",path="/docs/search/",query="q=pods",scheme="https",status="success",quantile="0.99"} 1.000437 +// http_request_duration_seconds_sum{handler="instrumented_http",host="kubernetes.io",method="GET",path="/docs/search/",query="q=pods",scheme="https",status="success"} 1.662788442 +// http_request_duration_seconds_count{handler="instrumented_http",host="kubernetes.io",method="GET",path="/docs/search/",query="q=pods",scheme="https",status="success"} 2