Skip to content

Commit fe82db4

Browse files
authored
stats/opentelemetry: Add CSM Observability API (#7277)
1 parent f1aceb0 commit fe82db4

File tree

4 files changed

+97
-3
lines changed

4 files changed

+97
-3
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
*
3+
* Copyright 2024 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package csm
20+
21+
import (
22+
"context"
23+
"net/url"
24+
25+
"google.golang.org/grpc"
26+
"google.golang.org/grpc/internal"
27+
"google.golang.org/grpc/stats/opentelemetry"
28+
otelinternal "google.golang.org/grpc/stats/opentelemetry/internal"
29+
)
30+
31+
// EnableObservability sets up CSM Observability for the binary globally.
32+
//
33+
// The CSM Stats Plugin is instantiated with local labels and metadata exchange
34+
// labels pulled from the environment, and emits metadata exchange labels from
35+
// the peer and local labels. Context timeouts do not trigger an error, but set
36+
// certain labels to "unknown".
37+
//
38+
// This function is not thread safe, and should only be invoked once in main
39+
// before any channels or servers are created. Returns a cleanup function to be
40+
// deferred in main.
41+
func EnableObservability(ctx context.Context, options opentelemetry.Options) func() {
42+
csmPluginOption := newPluginOption(ctx)
43+
clientSideOTelWithCSM := dialOptionWithCSMPluginOption(options, csmPluginOption)
44+
clientSideOTel := opentelemetry.DialOption(options)
45+
internal.AddGlobalPerTargetDialOptions.(func(opt any))(perTargetDialOption{
46+
clientSideOTelWithCSM: clientSideOTelWithCSM,
47+
clientSideOTel: clientSideOTel,
48+
})
49+
50+
serverSideOTelWithCSM := serverOptionWithCSMPluginOption(options, csmPluginOption)
51+
internal.AddGlobalServerOptions.(func(opt ...grpc.ServerOption))(serverSideOTelWithCSM)
52+
53+
return func() {
54+
internal.ClearGlobalServerOptions()
55+
internal.ClearGlobalPerTargetDialOptions()
56+
}
57+
}
58+
59+
type perTargetDialOption struct {
60+
clientSideOTelWithCSM grpc.DialOption
61+
clientSideOTel grpc.DialOption
62+
}
63+
64+
func (o *perTargetDialOption) DialOptionForTarget(parsedTarget url.URL) grpc.DialOption {
65+
if determineTargetCSM(&parsedTarget) {
66+
return o.clientSideOTelWithCSM
67+
}
68+
return o.clientSideOTel
69+
}
70+
71+
func dialOptionWithCSMPluginOption(options opentelemetry.Options, po otelinternal.PluginOption) grpc.DialOption {
72+
options.MetricsOptions.OptionalLabels = []string{"csm.service_name", "csm.service_namespace_name"} // Attach the two xDS Optional Labels for this component to not filter out.
73+
otelinternal.SetPluginOption.(func(options *opentelemetry.Options, po otelinternal.PluginOption))(&options, po)
74+
return opentelemetry.DialOption(options)
75+
}
76+
77+
func serverOptionWithCSMPluginOption(options opentelemetry.Options, po otelinternal.PluginOption) grpc.ServerOption {
78+
otelinternal.SetPluginOption.(func(options *opentelemetry.Options, po otelinternal.PluginOption))(&options, po)
79+
return opentelemetry.ServerOption(options)
80+
}

stats/opentelemetry/csm/pluginoption.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,15 @@ var (
179179
getAttrSetFromResourceDetector = func(ctx context.Context) *attribute.Set {
180180
r, err := resource.New(ctx, resource.WithDetectors(gcp.NewDetector()))
181181
if err != nil {
182-
logger.Errorf("error reading OpenTelemetry resource: %v", err)
183-
return nil
182+
logger.Warningf("error reading OpenTelemetry resource: %v", err)
184183
}
185-
return r.Set()
184+
if r != nil {
185+
// It's possible for resource.New to return partial data alongside
186+
// an error. In this case, use partial data and set "unknown" for
187+
// labels missing.
188+
return r.Set()
189+
}
190+
return nil
186191
}
187192
)
188193

stats/opentelemetry/internal/pluginoption.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import (
2121
"google.golang.org/grpc/metadata"
2222
)
2323

24+
// SetPluginOption sets the plugin option on Options.
25+
var SetPluginOption any // func(*Options, PluginOption)
26+
2427
// PluginOption is the interface which represents a plugin option for the
2528
// OpenTelemetry instrumentation component. This plugin option emits labels from
2629
// metadata and also creates metadata containing labels. These labels are

stats/opentelemetry/opentelemetry.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ import (
3333
"go.opentelemetry.io/otel/metric/noop"
3434
)
3535

36+
func init() {
37+
otelinternal.SetPluginOption = func(o *Options, po otelinternal.PluginOption) {
38+
o.MetricsOptions.pluginOption = po
39+
}
40+
}
41+
3642
var logger = grpclog.Component("otel-plugin")
3743

3844
var canonicalString = internal.CanonicalString.(func(codes.Code) string)

0 commit comments

Comments
 (0)