Skip to content

Commit

Permalink
HTTP client Prometheus metrics (#354)
Browse files Browse the repository at this point in the history
Signed-off-by: George Christou <[email protected]>
  • Loading branch information
christgf authored Apr 5, 2021
1 parent 8e1f756 commit 3211cd4
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 59 deletions.
41 changes: 34 additions & 7 deletions client/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,42 @@ import (
"context"
"io"
"net/http"
"strconv"
"time"

"github.com/opentracing-contrib/go-stdlib/nethttp"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/prometheus/client_golang/prometheus"

"github.com/beatlabs/patron/correlation"
"github.com/beatlabs/patron/encoding"
"github.com/beatlabs/patron/log"
"github.com/beatlabs/patron/reliability/circuitbreaker"
"github.com/beatlabs/patron/trace"
"github.com/opentracing-contrib/go-stdlib/nethttp"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)

const (
clientComponent = "http-client"
)

var (
reqDurationMetrics *prometheus.HistogramVec
)

func init() {
reqDurationMetrics = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "client",
Subsystem: "http",
Name: "request_duration_seconds",
Help: "HTTP requests completed by the client.",
},
[]string{"method", "url", "status_code"},
)
prometheus.MustRegister(reqDurationMetrics)
}

// Client interface of a HTTP client.
type Client interface {
Do(ctx context.Context, req *http.Request) (*http.Response, error)
Expand Down Expand Up @@ -64,15 +84,22 @@ func (tc *TracedClient) Do(ctx context.Context, req *http.Request) (*http.Respon

req.Header.Set(correlation.HeaderID, correlation.IDFromContext(ctx))

start := time.Now()

rsp, err := tc.do(req)

ext.HTTPMethod.Set(ht.Span(), req.Method)
ext.HTTPUrl.Set(ht.Span(), req.URL.String())

if err != nil {
ext.Error.Set(ht.Span(), true)
} else {
ext.HTTPStatusCode.Set(ht.Span(), uint16(rsp.StatusCode))
return rsp, err
}

ext.HTTPMethod.Set(ht.Span(), req.Method)
ext.HTTPUrl.Set(ht.Span(), req.URL.String())
ext.HTTPStatusCode.Set(ht.Span(), uint16(rsp.StatusCode))
reqDurationMetrics.
WithLabelValues(req.Method, req.URL.Host, strconv.Itoa(rsp.StatusCode)).
Observe(time.Since(start).Seconds())

if hdr := req.Header.Get(encoding.AcceptEncodingHeader); hdr != "" {
rsp.Body = decompress(hdr, rsp)
Expand Down
31 changes: 19 additions & 12 deletions client/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import (
"testing"
"time"

"github.com/beatlabs/patron/encoding"
"github.com/beatlabs/patron/reliability/circuitbreaker"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"

"github.com/beatlabs/patron/encoding"
"github.com/beatlabs/patron/reliability/circuitbreaker"
)

func TestTracedClient_Do(t *testing.T) {
Expand All @@ -40,6 +42,7 @@ func TestTracedClient_Do(t *testing.T) {
assert.NoError(t, err)
reqErr, err := http.NewRequest("GET", "", nil)
assert.NoError(t, err)
reqErr.Header.Set(encoding.AcceptEncodingHeader, "gzip")
opName := opName("GET", ts.URL)
opNameError := "HTTP GET"

Expand All @@ -48,16 +51,17 @@ func TestTracedClient_Do(t *testing.T) {
req *http.Request
}
tests := []struct {
name string
args args
wantErr bool
wantOpName string
name string
args args
wantErr bool
wantOpName string
wantCounter int
}{
{name: "respose", args: args{c: c, req: req}, wantErr: false, wantOpName: opName},
{name: "response with circuit breaker", args: args{c: cb, req: req}, wantErr: false, wantOpName: opName},
{name: "respose with custom transport", args: args{c: ct, req: req}, wantErr: false, wantOpName: opName},
{name: "error", args: args{c: cb, req: reqErr}, wantErr: true, wantOpName: opNameError},
{name: "error with circuit breaker", args: args{c: cb, req: reqErr}, wantErr: true, wantOpName: opNameError},
{name: "response", args: args{c: c, req: req}, wantErr: false, wantOpName: opName, wantCounter: 1},
{name: "response with circuit breaker", args: args{c: cb, req: req}, wantErr: false, wantOpName: opName, wantCounter: 1},
{name: "response with custom transport", args: args{c: ct, req: req}, wantErr: false, wantOpName: opName, wantCounter: 1},
{name: "error", args: args{c: cb, req: reqErr}, wantErr: true, wantOpName: opNameError, wantCounter: 0},
{name: "error with circuit breaker", args: args{c: cb, req: reqErr}, wantErr: true, wantOpName: opNameError, wantCounter: 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -73,6 +77,9 @@ func TestTracedClient_Do(t *testing.T) {
assert.NotNil(t, sp)
assert.Equal(t, tt.wantOpName, sp.OperationName)
mtr.Reset()
// Test counters.
assert.Equal(t, tt.wantCounter, testutil.CollectAndCount(reqDurationMetrics))
reqDurationMetrics.Reset()
})
}
}
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/beatlabs/patron
require (
github.com/Shopify/sarama v1.28.0
github.com/aws/aws-sdk-go v1.36.15
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
github.com/containerd/continuity v0.0.0-20200228182428-0f16d7a0959c // indirect
github.com/elastic/go-elasticsearch/v8 v8.0.0-20190731061900-ea052088db25
Expand Down
Loading

0 comments on commit 3211cd4

Please sign in to comment.