Skip to content

Commit 7d1e813

Browse files
committed
Add usage instructions
1 parent 7c98705 commit 7d1e813

File tree

3 files changed

+149
-19
lines changed

3 files changed

+149
-19
lines changed

README.md

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,117 @@
11
# connect-go-prometheus
2-
Prometheus interceptors for connect-go
2+
[Prometheus](https://prometheus.io/) monitoring for [connect-go](https://github.com/bufbuild/connect-go).
3+
4+
## Interceptors
5+
This library defines [interceptors](https://connect.build/docs/go/interceptors) to observe both client-side and server-side calls.
6+
7+
## Install
8+
```bash
9+
go get -u github.com/easyCZ/connect-go-prometheus
10+
```
11+
12+
## Usage
13+
```golang
14+
import (
15+
"github.com/easyCZ/connect-go-prometheus"
16+
)
17+
18+
// Construct the interceptor. The same intereceptor is used for both client-side and server-side.
19+
interceptor := connect_go_prometheus.NewInterceptor()
20+
21+
// Use the interceptor when constructing a new connect-go handler
22+
_, _ := your_connect_package.NewServiceHandler(handler, connect.WithInterceptors(interceptor))
23+
24+
// Or with a client
25+
client := your_connect_package.NewServiceClient(http.DefaultClient, serverURL, connect.WithInterceptors(interceptor))
26+
```
27+
For configuration, and more advanced use cases see [Configuration](#Configuration)
28+
29+
## Metrics
30+
31+
Metrics exposed use the following labels:
32+
* `type` - one of `unary`, `client_stream`, `server_stream` or `bidi`
33+
* `service` - name of the service, for example `myservice.greet.v1`
34+
* `method` - name of the method, for example `SayHello`
35+
* `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.
36+
37+
38+
### Server-side metrics
39+
* Counter `connect_server_started_total` with `(type, service, method)` labels
40+
* Counter `connect_server_handled_total` with `(type, service, method, code)` labels
41+
* (optionally) Histogram `connect_server_handled_seconds` with `(type, service, method, code)` labels
42+
43+
### Client-side metrics
44+
* Counter `connect_client_started_total` with `(type, service, method)` labels
45+
* Counter `connect_client_handled_total` with `(type, service, method, code)` labels
46+
* (optionally) Histogram `connect_client_handled_seconds` with `(type, service, method, code)` labels
47+
48+
## Configuration
49+
50+
### Customizing client/server metrics reported
51+
```golang
52+
import (
53+
"github.com/easyCZ/connect-go-prometheus"
54+
prom "github.com/prometheus/client_golang/prometheus"
55+
)
56+
57+
options := []connect_go_prometheus.MetricOption{
58+
connect_go_prometheus.WithHistogram(true),
59+
connect_go_prometheus.WithNamespace("namespace"),
60+
connect_go_prometheus.WithSubsystem("subsystem"),
61+
connect_go_prometheus.WithConstLabels(prom.Labels{"component": "foo"}),
62+
connect_go_prometheus.WithHistogramBuckets([]float64{1, 5}),
63+
}
64+
65+
// Construct client metrics
66+
clientMetrics := connect_go_prometheus.NewClientMetrics(options...)
67+
68+
// Construct server metrics
69+
serverMetrics := connect_go_prometheus.NewServerMetrics(options...)
70+
71+
// When you construct either client/server metrics with options, you must also register the metrics with your Prometheus Registry
72+
prom.MustRegister(clientMetrics, serverMetrics)
73+
74+
// Construct the interceptor with our configured metrics
75+
interceptor := connect_go_prometheus.NewInterceptor(
76+
connect_go_prometheus.WithClientMetrics(clientMetrics),
77+
connect_go_prometheus.WithServerMetrics(serverMetrics),
78+
)
79+
```
80+
81+
### Registering metrics against a Registry
82+
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:
83+
```golang
84+
import (
85+
"github.com/easyCZ/connect-go-prometheus"
86+
prom "github.com/prometheus/client_golang/prometheus"
87+
)
88+
89+
clientMetrics := connect_go_prometheus.NewClientMetrics()
90+
serverMetrics := connect_go_prometheus.NewServerMetrics()
91+
92+
registry := prom.NewRegistry()
93+
registry.MustRegister(clientMetrics, serverMetrics)
94+
95+
interceptor := connect_go_prometheus.NewInterceptor(
96+
connect_go_prometheus.WithClientMetrics(clientMetrics),
97+
connect_go_prometheus.WithServerMetrics(serverMetrics),
98+
)
99+
```
100+
101+
### Disabling client/server metrics reporting
102+
To disable reporting of either client or server metrics, pass `nil` as an option.
103+
```golang
104+
import (
105+
"github.com/easyCZ/connect-go-prometheus"
106+
)
107+
108+
// Disable client-side metrics
109+
interceptor := connect_go_prometheus.NewInterceptor(
110+
connect_go_prometheus.WithClientMetrics(nil),
111+
)
112+
113+
// Disable server-side metrics
114+
interceptor := connect_go_prometheus.NewInterceptor(
115+
connect_go_prometheus.WithServerMetrics(nil),
116+
)
117+
```

interceptor.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ type Interceptor struct {
2929

3030
func (i *Interceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc {
3131
return connect.UnaryFunc(func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
32+
// Short-circuit, not configured to report for either client or server.
33+
if i.client == nil && i.server == nil {
34+
return next(ctx, req)
35+
}
36+
3237
now := time.Now()
3338
callType := steamTypeString(req.Spec().StreamType)
3439
callPackage, callMethod := procedureToPackageAndMethod(req.Spec().Procedure)

interceptor_test.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ func TestInterceptor_WithClient_WithServer_Histogram(t *testing.T) {
3232

3333
reg.MustRegister(clientMetrics, serverMetrics)
3434

35-
intereceptor := NewInterceptor(WithClientMetrics(clientMetrics), WithServerMetrics(serverMetrics))
35+
interceptor := NewInterceptor(WithClientMetrics(clientMetrics), WithServerMetrics(serverMetrics))
3636

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

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

6161
func TestInterceptor_Default(t *testing.T) {
62-
intereceptor := NewInterceptor()
62+
interceptor := NewInterceptor()
6363

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

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

91-
intereceptor := NewInterceptor(WithClientMetrics(clientMetrics))
91+
interceptor := NewInterceptor(WithClientMetrics(clientMetrics), WithServerMetrics(nil))
9292

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

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

103-
expectedMetrics := []string{
103+
possibleMetrics := []string{
104+
"namespace_subsystem_connect_client_handled_seconds",
104105
"namespace_subsystem_connect_client_handled_total",
105106
"namespace_subsystem_connect_client_started_total",
107+
108+
"namespace_subsystem_connect_server_handled_seconds",
109+
"namespace_subsystem_connect_server_handled_total",
110+
"namespace_subsystem_connect_server_started_total",
106111
}
107-
count, err := testutil.GatherAndCount(reg, expectedMetrics...)
112+
count, err := testutil.GatherAndCount(reg, possibleMetrics...)
108113
require.NoError(t, err)
109-
require.Equal(t, len(expectedMetrics), count)
114+
require.Equal(t, 3, count, "must report only 3 metrics, as server side is disabled")
110115
}
111116

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

117-
intereceptor := NewInterceptor(WithServerMetrics(serverMetrics))
122+
interceptor := NewInterceptor(WithServerMetrics(serverMetrics), WithClientMetrics(nil))
118123

119-
_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(intereceptor))
124+
_, handler := greetconnect.NewGreetServiceHandler(greetconnect.UnimplementedGreetServiceHandler{}, connect.WithInterceptors(interceptor))
120125
srv := httptest.NewServer(handler)
121126

122-
client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(intereceptor))
127+
client := greetconnect.NewGreetServiceClient(http.DefaultClient, srv.URL, connect.WithInterceptors(interceptor))
123128
_, err := client.Greet(context.Background(), connect.NewRequest(&greet.GreetRequest{
124129
Name: "elza",
125130
}))
126131
require.Error(t, err)
127132
require.Equal(t, connect.CodeOf(err), connect.CodeUnimplemented)
128133

129-
expectedMetrics := []string{
134+
possibleMetrics := []string{
135+
"namespace_subsystem_connect_client_handled_seconds",
136+
"namespace_subsystem_connect_client_handled_total",
137+
"namespace_subsystem_connect_client_started_total",
138+
139+
"namespace_subsystem_connect_server_handled_seconds",
130140
"namespace_subsystem_connect_server_handled_total",
131141
"namespace_subsystem_connect_server_started_total",
132142
}
133-
count, err := testutil.GatherAndCount(reg, expectedMetrics...)
143+
count, err := testutil.GatherAndCount(reg, possibleMetrics...)
134144
require.NoError(t, err)
135-
require.Equal(t, len(expectedMetrics), count)
145+
require.Equal(t, 3, count, "must report only server side metrics, client-side is disabled")
136146
}

0 commit comments

Comments
 (0)