Skip to content

Commit 9951382

Browse files
committed
exporter/metric: add WithMetricClient option
This commit allows callers to configure their own Cloud Monitoring client. Fixes #1032
1 parent 19c4db6 commit 9951382

File tree

3 files changed

+106
-20
lines changed

3 files changed

+106
-20
lines changed
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package metric
16+
17+
import (
18+
"fmt"
19+
"testing"
20+
21+
monitoring "cloud.google.com/go/monitoring/apiv3/v2"
22+
)
23+
24+
func TestNew(t *testing.T) {
25+
cl := &monitoring.MetricClient{}
26+
tcs := []struct {
27+
desc string
28+
opts []Option
29+
verifyExporterFunc func(*metricExporter) error
30+
}{
31+
{
32+
desc: "WithMetricClient sets the client",
33+
opts: []Option{WithMonitoringClient(cl)},
34+
verifyExporterFunc: func(e *metricExporter) error {
35+
if e.client != cl {
36+
return fmt.Errorf("client mismatch, got = %p, want = %p", e.client, cl)
37+
}
38+
return nil
39+
},
40+
},
41+
{
42+
desc: "WithMetricClient overrides WithMetricClientOptions",
43+
opts: []Option{WithMonitoringClient(cl), WithMonitoringClientOptions()},
44+
verifyExporterFunc: func(e *metricExporter) error {
45+
if e.client != cl {
46+
return fmt.Errorf("client mismatch, got = %p, want = %p", e.client, cl)
47+
}
48+
return nil
49+
},
50+
},
51+
}
52+
53+
for _, tc := range tcs {
54+
t.Run(tc.desc, func(t *testing.T) {
55+
opts := append(tc.opts, WithProjectID("some-project-id"))
56+
e, err := New(opts...)
57+
if err != nil {
58+
t.Fatalf("New(): %v", err)
59+
}
60+
exp, ok := e.(*metricExporter)
61+
if !ok {
62+
t.Fatal("unexpected type mismatch")
63+
}
64+
if err := tc.verifyExporterFunc(exp); err != nil {
65+
t.Fatalf("failed to verify exporter: %v", err)
66+
}
67+
})
68+
}
69+
}

exporter/metric/metric.go

+21-18
Original file line numberDiff line numberDiff line change
@@ -106,25 +106,28 @@ func newMetricExporter(o *options) (*metricExporter, error) {
106106
return nil, errBlankProjectID
107107
}
108108

109-
clientOpts := append([]option.ClientOption{option.WithGRPCDialOption(grpc.WithUserAgent(userAgent))}, o.monitoringClientOptions...)
110-
ctx := o.context
111-
if ctx == nil {
112-
ctx = context.Background()
113-
}
114-
client, err := monitoring.NewMetricClient(ctx, clientOpts...)
115-
if err != nil {
116-
return nil, err
117-
}
109+
client := o.monitoringClient
110+
if client == nil {
111+
clientOpts := append([]option.ClientOption{option.WithGRPCDialOption(grpc.WithUserAgent(userAgent))}, o.monitoringClientOptions...)
112+
ctx := o.context
113+
if ctx == nil {
114+
ctx = context.Background()
115+
}
116+
client, err := monitoring.NewMetricClient(ctx, clientOpts...)
117+
if err != nil {
118+
return nil, err
119+
}
118120

119-
if o.compression == "gzip" {
120-
client.CallOptions.GetMetricDescriptor = append(client.CallOptions.GetMetricDescriptor,
121-
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
122-
client.CallOptions.CreateMetricDescriptor = append(client.CallOptions.CreateMetricDescriptor,
123-
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
124-
client.CallOptions.CreateTimeSeries = append(client.CallOptions.CreateTimeSeries,
125-
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
126-
client.CallOptions.CreateServiceTimeSeries = append(client.CallOptions.CreateServiceTimeSeries,
127-
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
121+
if o.compression == "gzip" {
122+
client.CallOptions.GetMetricDescriptor = append(client.CallOptions.GetMetricDescriptor,
123+
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
124+
client.CallOptions.CreateMetricDescriptor = append(client.CallOptions.CreateMetricDescriptor,
125+
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
126+
client.CallOptions.CreateTimeSeries = append(client.CallOptions.CreateTimeSeries,
127+
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
128+
client.CallOptions.CreateServiceTimeSeries = append(client.CallOptions.CreateServiceTimeSeries,
129+
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
130+
}
128131
}
129132

130133
cache := map[key]*googlemetricpb.MetricDescriptor{}

exporter/metric/option.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ import (
2121
"go.opentelemetry.io/otel"
2222
"go.opentelemetry.io/otel/attribute"
2323
"go.opentelemetry.io/otel/sdk/metric/metricdata"
24-
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
2524

25+
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
26+
monitoring "cloud.google.com/go/monitoring/apiv3/v2"
2627
apioption "google.golang.org/api/option"
2728
)
2829

@@ -69,8 +70,11 @@ type options struct {
6970
projectID string
7071
// compression enables gzip compression on gRPC calls.
7172
compression string
73+
// monitoringClient is used as the default client when not nil. If
74+
// monitoringClient is nil, a client is created instead.
75+
monitoringClient *monitoring.MetricClient
7276
// monitoringClientOptions are additional options to be passed
73-
// to the underlying Stackdriver Monitoring API client.
77+
// to the underlying Cloud Monitoring API client.
7478
// Optional.
7579
monitoringClientOptions []apioption.ClientOption
7680
// destinationProjectQuota sets whether the request should use quota from
@@ -108,6 +112,16 @@ func WithDestinationProjectQuota() func(o *options) {
108112
}
109113
}
110114

115+
// WithMonitoringClient configures the client used by the exporter to write
116+
// metrics to Cloud Monitoring. This option is mutually exclusive with
117+
// WithMonitoringClientOptions. If both options are provided,
118+
// WithMonitoringClient is used and WithMonitoringClientOptions is ignored.
119+
func WithMonitoringClient(cl *monitoring.MetricClient) func(o *options) {
120+
return func(o *options) {
121+
o.monitoringClient = cl
122+
}
123+
}
124+
111125
// WithMonitoringClientOptions add the options for Cloud Monitoring client instance.
112126
// Available options are defined in.
113127
func WithMonitoringClientOptions(opts ...apioption.ClientOption) func(o *options) {

0 commit comments

Comments
 (0)