@@ -31,6 +31,8 @@ package gax
3131
3232import (
3333 "context"
34+ "log/slog"
35+ "sync"
3436
3537 "go.opentelemetry.io/otel"
3638 "go.opentelemetry.io/otel/attribute"
@@ -136,112 +138,158 @@ var defaultHistogramBoundaries = []float64{
136138// for a specific generated Google Cloud client library.
137139// There should be exactly one ClientMetrics instance instantiated per generated client.
138140type ClientMetrics struct {
141+ get func () clientMetricsData
142+ }
143+
144+ type clientMetricsData struct {
139145 duration metric.Float64Histogram
140146 attr []attribute.KeyValue
141147}
142148
143- type clientMetricsOptions struct {
149+ type telemetryOptions struct {
144150 provider metric.MeterProvider
145151 attributes map [string ]string
146152 explicitBucketBoundaries []float64
153+ logger * slog.Logger
147154}
148155
149- // ClientMetricsOption is an option to configure a ClientMetrics instance.
150- // ClientMetricsOption works by modifying relevant fields of clientMetricsOptions .
151- type ClientMetricsOption interface {
156+ // TelemetryOption is an option to configure a ClientMetrics instance.
157+ // TelemetryOption works by modifying relevant fields of telemetryOptions .
158+ type TelemetryOption interface {
152159 // Resolve applies the option by modifying opts.
153- Resolve (opts * clientMetricsOptions )
160+ Resolve (opts * telemetryOptions )
154161}
155162
156163type providerOpt struct {
157164 p metric.MeterProvider
158165}
159166
160- func (p providerOpt ) Resolve (opts * clientMetricsOptions ) {
167+ func (p providerOpt ) Resolve (opts * telemetryOptions ) {
161168 opts .provider = p .p
162169}
163170
164171// WithMeterProvider specifies the metric.MeterProvider to use for instruments.
165- func WithMeterProvider (p metric.MeterProvider ) ClientMetricsOption {
172+ func WithMeterProvider (p metric.MeterProvider ) TelemetryOption {
166173 return & providerOpt {p : p }
167174}
168175
169176type attrOpt struct {
170177 attrs map [string ]string
171178}
172179
173- func (a attrOpt ) Resolve (opts * clientMetricsOptions ) {
180+ func (a attrOpt ) Resolve (opts * telemetryOptions ) {
174181 opts .attributes = a .attrs
175182}
176183
177- // WithClientMetricsAttributes specifies the static attributes attachments.
178- func WithClientMetricsAttributes (attr map [string ]string ) ClientMetricsOption {
184+ // WithTelemetryAttributes specifies the static attributes attachments.
185+ func WithTelemetryAttributes (attr map [string ]string ) TelemetryOption {
179186 return & attrOpt {attrs : attr }
180187}
181188
182189type boundariesOpt struct {
183190 boundaries []float64
184191}
185192
186- func (b boundariesOpt ) Resolve (opts * clientMetricsOptions ) {
193+ func (b boundariesOpt ) Resolve (opts * telemetryOptions ) {
187194 opts .explicitBucketBoundaries = b .boundaries
188195}
189196
190197// WithExplicitBucketBoundaries overrides the default histogram bucket boundaries.
191- func WithExplicitBucketBoundaries (boundaries []float64 ) ClientMetricsOption {
198+ func WithExplicitBucketBoundaries (boundaries []float64 ) TelemetryOption {
192199 return & boundariesOpt {boundaries : boundaries }
193200}
194201
195- // NewClientMetrics initializes and returns a new ClientMetrics instance.
196- // It is intended to be called once per generated client during initialization.
197- func NewClientMetrics (opts ... ClientMetricsOption ) * ClientMetrics {
198- var config clientMetricsOptions
199- for _ , opt := range opts {
200- opt .Resolve (& config )
201- }
202+ type loggerOpt struct {
203+ l * slog.Logger
204+ }
202205
203- provider := config .provider
204- if provider == nil {
205- provider = otel .GetMeterProvider ()
206- }
206+ func (l loggerOpt ) Resolve (opts * telemetryOptions ) {
207+ opts .logger = l .l
208+ }
207209
208- var meterAttrs []attribute. KeyValue
209- if val , ok := config . attributes [ ClientService ]; ok {
210- meterAttrs = append ( meterAttrs , attribute. KeyValue { Key : attribute . Key ( keyGCPClientService ), Value : attribute . StringValue ( val )})
211- }
210+ // WithTelemetryLogger specifies a logger to record internal telemetry errors.
211+ func WithTelemetryLogger ( l * slog. Logger ) TelemetryOption {
212+ return & loggerOpt { l : l }
213+ }
212214
213- meterOpts := []metric.MeterOption {
214- metric .WithInstrumentationVersion (config .attributes [ClientVersion ]),
215- metric .WithSchemaURL (schemaURL ),
216- }
217- if len (meterAttrs ) > 0 {
218- meterOpts = append (meterOpts , metric .WithInstrumentationAttributes (meterAttrs ... ))
215+ func (config * telemetryOptions ) meterProvider () metric.MeterProvider {
216+ if config .provider != nil {
217+ return config .provider
219218 }
219+ return otel .GetMeterProvider ()
220+ }
220221
221- meter := provider .Meter (config .attributes [ClientArtifact ], meterOpts ... )
222-
223- boundaries := config .explicitBucketBoundaries
224- if len (boundaries ) == 0 {
225- boundaries = defaultHistogramBoundaries
222+ func (config * telemetryOptions ) bucketBoundaries () []float64 {
223+ if len (config .explicitBucketBoundaries ) > 0 {
224+ return config .explicitBucketBoundaries
226225 }
226+ return defaultHistogramBoundaries
227+ }
227228
228- duration , _ := meter .Float64Histogram (
229- metricName ,
230- metric .WithDescription (metricDescription ),
231- metric .WithUnit ("s" ),
232- metric .WithExplicitBucketBoundaries (boundaries ... ),
233- )
229+ // NewClientMetrics initializes and returns a new ClientMetrics instance.
230+ // It is intended to be called once per generated client during initialization.
231+ func NewClientMetrics (opts ... TelemetryOption ) * ClientMetrics {
232+ var config telemetryOptions
233+ for _ , opt := range opts {
234+ opt .Resolve (& config )
235+ }
234236
235- var attr []attribute.KeyValue
236- if val , ok := config .attributes [URLDomain ]; ok {
237- attr = append (attr , attribute.KeyValue {Key : attribute .Key (keyURLDomain ), Value : attribute .StringValue (val )})
237+ return & ClientMetrics {
238+ get : sync .OnceValue (func () clientMetricsData {
239+ provider := config .meterProvider ()
240+
241+ var meterAttrs []attribute.KeyValue
242+ if val , ok := config .attributes [ClientService ]; ok {
243+ meterAttrs = append (meterAttrs , attribute.KeyValue {Key : attribute .Key (keyGCPClientService ), Value : attribute .StringValue (val )})
244+ }
245+
246+ meterOpts := []metric.MeterOption {
247+ metric .WithInstrumentationVersion (config .attributes [ClientVersion ]),
248+ metric .WithSchemaURL (schemaURL ),
249+ }
250+ if len (meterAttrs ) > 0 {
251+ meterOpts = append (meterOpts , metric .WithInstrumentationAttributes (meterAttrs ... ))
252+ }
253+
254+ meter := provider .Meter (config .attributes [ClientArtifact ], meterOpts ... )
255+
256+ boundaries := config .bucketBoundaries ()
257+
258+ duration , err := meter .Float64Histogram (
259+ metricName ,
260+ metric .WithDescription (metricDescription ),
261+ metric .WithUnit ("s" ),
262+ metric .WithExplicitBucketBoundaries (boundaries ... ),
263+ )
264+ if err != nil && config .logger != nil {
265+ config .logger .Warn ("failed to initialize OTel duration histogram" , "error" , err )
266+ }
267+
268+ var attr []attribute.KeyValue
269+ if val , ok := config .attributes [URLDomain ]; ok {
270+ attr = append (attr , attribute.KeyValue {Key : attribute .Key (keyURLDomain ), Value : attribute .StringValue (val )})
271+ }
272+ if val , ok := config .attributes [RPCSystem ]; ok {
273+ attr = append (attr , attribute.KeyValue {Key : attribute .Key (keyRPCSystemName ), Value : attribute .StringValue (val )})
274+ }
275+ return clientMetricsData {
276+ duration : duration ,
277+ attr : attr ,
278+ }
279+ }),
238280 }
239- if val , ok := config .attributes [RPCSystem ]; ok {
240- attr = append (attr , attribute.KeyValue {Key : attribute .Key (keyRPCSystemName ), Value : attribute .StringValue (val )})
281+ }
282+
283+ func (cm * ClientMetrics ) durationHistogram () metric.Float64Histogram {
284+ if cm == nil || cm .get == nil {
285+ return nil
241286 }
287+ return cm .get ().duration
288+ }
242289
243- return & ClientMetrics {
244- duration : duration ,
245- attr : attr ,
290+ func ( cm * ClientMetrics ) attributes () []attribute. KeyValue {
291+ if cm == nil || cm . get == nil {
292+ return nil
246293 }
294+ return cm .get ().attr
247295}
0 commit comments