Skip to content

Commit

Permalink
Add usage instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
easyCZ committed Oct 23, 2022
1 parent 7c98705 commit 7d1e813
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 19 deletions.
117 changes: 116 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,117 @@
# connect-go-prometheus
Prometheus interceptors for connect-go
[Prometheus](https://prometheus.io/) monitoring for [connect-go](https://github.com/bufbuild/connect-go).

## Interceptors
This library defines [interceptors](https://connect.build/docs/go/interceptors) to observe both client-side and server-side calls.

## Install
```bash
go get -u github.com/easyCZ/connect-go-prometheus
```

## Usage
```golang
import (
"github.com/easyCZ/connect-go-prometheus"
)

// Construct the interceptor. The same intereceptor is used for both client-side and server-side.
interceptor := connect_go_prometheus.NewInterceptor()

// Use the interceptor when constructing a new connect-go handler
_, _ := your_connect_package.NewServiceHandler(handler, connect.WithInterceptors(interceptor))

// Or with a client
client := your_connect_package.NewServiceClient(http.DefaultClient, serverURL, connect.WithInterceptors(interceptor))
```
For configuration, and more advanced use cases see [Configuration](#Configuration)

## Metrics

Metrics exposed use the following labels:
* `type` - one of `unary`, `client_stream`, `server_stream` or `bidi`
* `service` - name of the service, for example `myservice.greet.v1`
* `method` - name of the method, for example `SayHello`
* `code` - the resulting outcome of the RPC. The codes match [connect-go Error Codes](https://connect.build/docs/protocol#error-codes) with the addition of `ok` for succesful RPCs.


### Server-side metrics
* Counter `connect_server_started_total` with `(type, service, method)` labels
* Counter `connect_server_handled_total` with `(type, service, method, code)` labels
* (optionally) Histogram `connect_server_handled_seconds` with `(type, service, method, code)` labels

### Client-side metrics
* Counter `connect_client_started_total` with `(type, service, method)` labels
* Counter `connect_client_handled_total` with `(type, service, method, code)` labels
* (optionally) Histogram `connect_client_handled_seconds` with `(type, service, method, code)` labels

## Configuration

### Customizing client/server metrics reported
```golang
import (
"github.com/easyCZ/connect-go-prometheus"
prom "github.com/prometheus/client_golang/prometheus"
)

options := []connect_go_prometheus.MetricOption{
connect_go_prometheus.WithHistogram(true),
connect_go_prometheus.WithNamespace("namespace"),
connect_go_prometheus.WithSubsystem("subsystem"),
connect_go_prometheus.WithConstLabels(prom.Labels{"component": "foo"}),
connect_go_prometheus.WithHistogramBuckets([]float64{1, 5}),
}

// Construct client metrics
clientMetrics := connect_go_prometheus.NewClientMetrics(options...)

// Construct server metrics
serverMetrics := connect_go_prometheus.NewServerMetrics(options...)

// When you construct either client/server metrics with options, you must also register the metrics with your Prometheus Registry
prom.MustRegister(clientMetrics, serverMetrics)

// Construct the interceptor with our configured metrics
interceptor := connect_go_prometheus.NewInterceptor(
connect_go_prometheus.WithClientMetrics(clientMetrics),
connect_go_prometheus.WithServerMetrics(serverMetrics),
)
```

### Registering metrics against a Registry
You may want to register metrics against a [Prometheus Registry](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Registry). You can do this with the following:
```golang
import (
"github.com/easyCZ/connect-go-prometheus"
prom "github.com/prometheus/client_golang/prometheus"
)

clientMetrics := connect_go_prometheus.NewClientMetrics()
serverMetrics := connect_go_prometheus.NewServerMetrics()

registry := prom.NewRegistry()
registry.MustRegister(clientMetrics, serverMetrics)

interceptor := connect_go_prometheus.NewInterceptor(
connect_go_prometheus.WithClientMetrics(clientMetrics),
connect_go_prometheus.WithServerMetrics(serverMetrics),
)
```

### Disabling client/server metrics reporting
To disable reporting of either client or server metrics, pass `nil` as an option.
```golang
import (
"github.com/easyCZ/connect-go-prometheus"
)

// Disable client-side metrics
interceptor := connect_go_prometheus.NewInterceptor(
connect_go_prometheus.WithClientMetrics(nil),
)

// Disable server-side metrics
interceptor := connect_go_prometheus.NewInterceptor(
connect_go_prometheus.WithServerMetrics(nil),
)
```
5 changes: 5 additions & 0 deletions interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ type Interceptor struct {

func (i *Interceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc {
return connect.UnaryFunc(func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
// Short-circuit, not configured to report for either client or server.
if i.client == nil && i.server == nil {
return next(ctx, req)
}

now := time.Now()
callType := steamTypeString(req.Spec().StreamType)
callPackage, callMethod := procedureToPackageAndMethod(req.Spec().Procedure)
Expand Down
46 changes: 28 additions & 18 deletions interceptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ func TestInterceptor_WithClient_WithServer_Histogram(t *testing.T) {

reg.MustRegister(clientMetrics, serverMetrics)

intereceptor := NewInterceptor(WithClientMetrics(clientMetrics), WithServerMetrics(serverMetrics))
interceptor := NewInterceptor(WithClientMetrics(clientMetrics), WithServerMetrics(serverMetrics))

_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(intereceptor))
_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(interceptor))
srv := httptest.NewServer(handler)

client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(intereceptor))
client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(interceptor))
_, err := client.Greet(context.Background(), connect.NewRequest(&greet.GreetRequest{
Name: "elza",
}))
Expand All @@ -59,12 +59,12 @@ func TestInterceptor_WithClient_WithServer_Histogram(t *testing.T) {
}

func TestInterceptor_Default(t *testing.T) {
intereceptor := NewInterceptor()
interceptor := NewInterceptor()

_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(intereceptor))
_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(interceptor))
srv := httptest.NewServer(handler)

client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(intereceptor))
client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(interceptor))
_, err := client.Greet(context.Background(), connect.NewRequest(&greet.GreetRequest{
Name: "elza",
}))
Expand All @@ -88,49 +88,59 @@ func TestInterceptor_WithClientMetrics(t *testing.T) {
clientMetrics := NewClientMetrics(testMetricOptions...)
require.NoError(t, reg.Register(clientMetrics))

intereceptor := NewInterceptor(WithClientMetrics(clientMetrics))
interceptor := NewInterceptor(WithClientMetrics(clientMetrics), WithServerMetrics(nil))

_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(intereceptor))
_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(interceptor))
srv := httptest.NewServer(handler)

client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(intereceptor))
client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(interceptor))
_, err := client.Greet(context.Background(), connect.NewRequest(&greet.GreetRequest{
Name: "elza",
}))
require.Error(t, err)
require.Equal(t, connect.CodeOf(err), connect.CodeUnimplemented)

expectedMetrics := []string{
possibleMetrics := []string{
"namespace_subsystem_connect_client_handled_seconds",
"namespace_subsystem_connect_client_handled_total",
"namespace_subsystem_connect_client_started_total",

"namespace_subsystem_connect_server_handled_seconds",
"namespace_subsystem_connect_server_handled_total",
"namespace_subsystem_connect_server_started_total",
}
count, err := testutil.GatherAndCount(reg, expectedMetrics...)
count, err := testutil.GatherAndCount(reg, possibleMetrics...)
require.NoError(t, err)
require.Equal(t, len(expectedMetrics), count)
require.Equal(t, 3, count, "must report only 3 metrics, as server side is disabled")
}

func TestInterceptor_WithServerMetrics(t *testing.T) {
reg := prom.NewRegistry()
serverMetrics := NewServerMetrics(testMetricOptions...)
require.NoError(t, reg.Register(serverMetrics))

intereceptor := NewInterceptor(WithServerMetrics(serverMetrics))
interceptor := NewInterceptor(WithServerMetrics(serverMetrics), WithClientMetrics(nil))

_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(intereceptor))
_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(interceptor))
srv := httptest.NewServer(handler)

client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(intereceptor))
client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(interceptor))
_, err := client.Greet(context.Background(), connect.NewRequest(&greet.GreetRequest{
Name: "elza",
}))
require.Error(t, err)
require.Equal(t, connect.CodeOf(err), connect.CodeUnimplemented)

expectedMetrics := []string{
possibleMetrics := []string{
"namespace_subsystem_connect_client_handled_seconds",
"namespace_subsystem_connect_client_handled_total",
"namespace_subsystem_connect_client_started_total",

"namespace_subsystem_connect_server_handled_seconds",
"namespace_subsystem_connect_server_handled_total",
"namespace_subsystem_connect_server_started_total",
}
count, err := testutil.GatherAndCount(reg, expectedMetrics...)
count, err := testutil.GatherAndCount(reg, possibleMetrics...)
require.NoError(t, err)
require.Equal(t, len(expectedMetrics), count)
require.Equal(t, 3, count, "must report only server side metrics, client-side is disabled")
}

0 comments on commit 7d1e813

Please sign in to comment.