Skip to content

Commit 4c776ec

Browse files
authored
Cherry-pick observability changes from master to v1.50.x and update version to 1.50.1 (#5722)
* Add binary logger option for client and server (#5675) * Add binary logger option for client and server * gcp/observability: implement public preview config syntax, logging schema, and exposed metrics (#5704) * Fix o11y typo (#5719) * o11y: Fixed o11y bug (#5720) * update version to 1.50.1
1 parent 6576007 commit 4c776ec

23 files changed

+2202
-2193
lines changed

default_dial_option_server_option_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (s) TestAddExtraDialOptions(t *testing.T) {
3838

3939
// Set and check the DialOptions
4040
opts := []DialOption{WithTransportCredentials(insecure.NewCredentials()), WithTransportCredentials(insecure.NewCredentials()), WithTransportCredentials(insecure.NewCredentials())}
41-
internal.AddExtraDialOptions.(func(opt ...DialOption))(opts...)
41+
internal.AddGlobalDialOptions.(func(opt ...DialOption))(opts...)
4242
for i, opt := range opts {
4343
if extraDialOptions[i] != opt {
4444
t.Fatalf("Unexpected extra dial option at index %d: %v != %v", i, extraDialOptions[i], opt)
@@ -52,7 +52,7 @@ func (s) TestAddExtraDialOptions(t *testing.T) {
5252
cc.Close()
5353
}
5454

55-
internal.ClearExtraDialOptions()
55+
internal.ClearGlobalDialOptions()
5656
if len(extraDialOptions) != 0 {
5757
t.Fatalf("Unexpected len of extraDialOptions: %d != 0", len(extraDialOptions))
5858
}
@@ -62,7 +62,7 @@ func (s) TestAddExtraServerOptions(t *testing.T) {
6262
const maxRecvSize = 998765
6363
// Set and check the ServerOptions
6464
opts := []ServerOption{Creds(insecure.NewCredentials()), MaxRecvMsgSize(maxRecvSize)}
65-
internal.AddExtraServerOptions.(func(opt ...ServerOption))(opts...)
65+
internal.AddGlobalServerOptions.(func(opt ...ServerOption))(opts...)
6666
for i, opt := range opts {
6767
if extraServerOptions[i] != opt {
6868
t.Fatalf("Unexpected extra server option at index %d: %v != %v", i, extraServerOptions[i], opt)
@@ -75,7 +75,7 @@ func (s) TestAddExtraServerOptions(t *testing.T) {
7575
t.Fatalf("Unexpected s.opts.maxReceiveMessageSize: %d != %d", s.opts.maxReceiveMessageSize, maxRecvSize)
7676
}
7777

78-
internal.ClearExtraServerOptions()
78+
internal.ClearGlobalServerOptions()
7979
if len(extraServerOptions) != 0 {
8080
t.Fatalf("Unexpected len of extraServerOptions: %d != 0", len(extraServerOptions))
8181
}

dialoptions.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,21 @@ import (
2929
"google.golang.org/grpc/credentials/insecure"
3030
"google.golang.org/grpc/internal"
3131
internalbackoff "google.golang.org/grpc/internal/backoff"
32+
"google.golang.org/grpc/internal/binarylog"
3233
"google.golang.org/grpc/internal/transport"
3334
"google.golang.org/grpc/keepalive"
3435
"google.golang.org/grpc/resolver"
3536
"google.golang.org/grpc/stats"
3637
)
3738

3839
func init() {
39-
internal.AddExtraDialOptions = func(opt ...DialOption) {
40+
internal.AddGlobalDialOptions = func(opt ...DialOption) {
4041
extraDialOptions = append(extraDialOptions, opt...)
4142
}
42-
internal.ClearExtraDialOptions = func() {
43+
internal.ClearGlobalDialOptions = func() {
4344
extraDialOptions = nil
4445
}
46+
internal.WithBinaryLogger = withBinaryLogger
4547
}
4648

4749
// dialOptions configure a Dial call. dialOptions are set by the DialOption
@@ -61,6 +63,7 @@ type dialOptions struct {
6163
timeout time.Duration
6264
scChan <-chan ServiceConfig
6365
authority string
66+
binaryLogger binarylog.Logger
6467
copts transport.ConnectOptions
6568
callOptions []CallOption
6669
channelzParentID *channelz.Identifier
@@ -401,6 +404,14 @@ func WithStatsHandler(h stats.Handler) DialOption {
401404
})
402405
}
403406

407+
// withBinaryLogger returns a DialOption that specifies the binary logger for
408+
// this ClientConn.
409+
func withBinaryLogger(bl binarylog.Logger) DialOption {
410+
return newFuncDialOption(func(o *dialOptions) {
411+
o.binaryLogger = bl
412+
})
413+
}
414+
404415
// FailOnNonTempDialError returns a DialOption that specifies if gRPC fails on
405416
// non-temporary dial errors. If f is true, and dialer returns a non-temporary
406417
// error, gRPC will fail the connection to the network address and won't try to

gcp/observability/config.go

Lines changed: 163 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -21,86 +21,23 @@ package observability
2121
import (
2222
"context"
2323
"encoding/json"
24+
"errors"
2425
"fmt"
2526
"io/ioutil"
2627
"os"
2728
"regexp"
2829

2930
gcplogging "cloud.google.com/go/logging"
3031
"golang.org/x/oauth2/google"
32+
"google.golang.org/grpc/internal/envconfig"
3133
)
3234

3335
const (
34-
envObservabilityConfig = "GRPC_CONFIG_OBSERVABILITY"
35-
envObservabilityConfigJSON = "GRPC_CONFIG_OBSERVABILITY_JSON"
36-
envProjectID = "GOOGLE_CLOUD_PROJECT"
37-
logFilterPatternRegexpStr = `^([\w./]+)/((?:\w+)|[*])$`
36+
envProjectID = "GOOGLE_CLOUD_PROJECT"
37+
methodStringRegexpStr = `^([\w./]+)/((?:\w+)|[*])$`
3838
)
3939

40-
var logFilterPatternRegexp = regexp.MustCompile(logFilterPatternRegexpStr)
41-
42-
// logFilter represents a method logging configuration.
43-
type logFilter struct {
44-
// Pattern is a string which can select a group of method names. By
45-
// default, the Pattern is an empty string, matching no methods.
46-
//
47-
// Only "*" Wildcard is accepted for Pattern. A Pattern is in the form
48-
// of <service>/<method> or just a character "*" .
49-
//
50-
// If the Pattern is "*", it specifies the defaults for all the
51-
// services; If the Pattern is <service>/*, it specifies the defaults
52-
// for all methods in the specified service <service>; If the Pattern is
53-
// */<method>, this is not supported.
54-
//
55-
// Examples:
56-
// - "Foo/Bar" selects only the method "Bar" from service "Foo"
57-
// - "Foo/*" selects all methods from service "Foo"
58-
// - "*" selects all methods from all services.
59-
Pattern string `json:"pattern,omitempty"`
60-
// HeaderBytes is the number of bytes of each header to log. If the size of
61-
// the header is greater than the defined limit, content past the limit will
62-
// be truncated. The default value is 0.
63-
HeaderBytes int32 `json:"header_bytes,omitempty"`
64-
// MessageBytes is the number of bytes of each message to log. If the size
65-
// of the message is greater than the defined limit, content pass the limit
66-
// will be truncated. The default value is 0.
67-
MessageBytes int32 `json:"message_bytes,omitempty"`
68-
}
69-
70-
// config is configuration for observability behaviors. By default, no
71-
// configuration is required for tracing/metrics/logging to function. This
72-
// config captures the most common knobs for gRPC users. It's always possible to
73-
// override with explicit config in code.
74-
type config struct {
75-
// EnableCloudTrace represents whether the tracing data upload to
76-
// CloudTrace should be enabled or not.
77-
EnableCloudTrace bool `json:"enable_cloud_trace,omitempty"`
78-
// EnableCloudMonitoring represents whether the metrics data upload to
79-
// CloudMonitoring should be enabled or not.
80-
EnableCloudMonitoring bool `json:"enable_cloud_monitoring,omitempty"`
81-
// EnableCloudLogging represents Whether the logging data upload to
82-
// CloudLogging should be enabled or not.
83-
EnableCloudLogging bool `json:"enable_cloud_logging,omitempty"`
84-
// DestinationProjectID is the destination GCP project identifier for the
85-
// uploading log entries. If empty, the gRPC Observability plugin will
86-
// attempt to fetch the project_id from the GCP environment variables, or
87-
// from the default credentials.
88-
DestinationProjectID string `json:"destination_project_id,omitempty"`
89-
// LogFilters is a list of method config. The order matters here - the first
90-
// Pattern which matches the current method will apply the associated config
91-
// options in the logFilter. Any other logFilter that also matches that
92-
// comes later will be ignored. So a logFilter of "*/*" should appear last
93-
// in this list.
94-
LogFilters []logFilter `json:"log_filters,omitempty"`
95-
// GlobalTraceSamplingRate is the global setting that controls the
96-
// probability of a RPC being traced. For example, 0.05 means there is a 5%
97-
// chance for a RPC to be traced, 1.0 means trace every call, 0 means don’t
98-
// start new traces.
99-
GlobalTraceSamplingRate float64 `json:"global_trace_sampling_rate,omitempty"`
100-
// CustomTags a list of custom tags that will be attached to every log
101-
// entry.
102-
CustomTags map[string]string `json:"custom_tags,omitempty"`
103-
}
40+
var methodStringRegexp = regexp.MustCompile(methodStringRegexpStr)
10441

10542
// fetchDefaultProjectID fetches the default GCP project id from environment.
10643
func fetchDefaultProjectID(ctx context.Context) string {
@@ -123,14 +60,34 @@ func fetchDefaultProjectID(ctx context.Context) string {
12360
return credentials.ProjectID
12461
}
12562

126-
func validateFilters(config *config) error {
127-
for _, filter := range config.LogFilters {
128-
if filter.Pattern == "*" {
63+
func validateLogEventMethod(methods []string, exclude bool) error {
64+
for _, method := range methods {
65+
if method == "*" {
66+
if exclude {
67+
return errors.New("cannot have exclude and a '*' wildcard")
68+
}
12969
continue
13070
}
131-
match := logFilterPatternRegexp.FindStringSubmatch(filter.Pattern)
71+
match := methodStringRegexp.FindStringSubmatch(method)
13272
if match == nil {
133-
return fmt.Errorf("invalid log filter Pattern: %v", filter.Pattern)
73+
return fmt.Errorf("invalid method string: %v", method)
74+
}
75+
}
76+
return nil
77+
}
78+
79+
func validateLoggingEvents(config *config) error {
80+
if config.CloudLogging == nil {
81+
return nil
82+
}
83+
for _, clientRPCEvent := range config.CloudLogging.ClientRPCEvents {
84+
if err := validateLogEventMethod(clientRPCEvent.Methods, clientRPCEvent.Exclude); err != nil {
85+
return fmt.Errorf("error in clientRPCEvent method: %v", err)
86+
}
87+
}
88+
for _, serverRPCEvent := range config.CloudLogging.ServerRPCEvents {
89+
if err := validateLogEventMethod(serverRPCEvent.Methods, serverRPCEvent.Exclude); err != nil {
90+
return fmt.Errorf("error in serverRPCEvent method: %v", err)
13491
}
13592
}
13693
return nil
@@ -144,38 +101,161 @@ func unmarshalAndVerifyConfig(rawJSON json.RawMessage) (*config, error) {
144101
if err := json.Unmarshal(rawJSON, &config); err != nil {
145102
return nil, fmt.Errorf("error parsing observability config: %v", err)
146103
}
147-
if err := validateFilters(&config); err != nil {
104+
if err := validateLoggingEvents(&config); err != nil {
148105
return nil, fmt.Errorf("error parsing observability config: %v", err)
149106
}
150-
if config.GlobalTraceSamplingRate > 1 || config.GlobalTraceSamplingRate < 0 {
151-
return nil, fmt.Errorf("error parsing observability config: invalid global trace sampling rate %v", config.GlobalTraceSamplingRate)
107+
if config.CloudTrace != nil && (config.CloudTrace.SamplingRate > 1 || config.CloudTrace.SamplingRate < 0) {
108+
return nil, fmt.Errorf("error parsing observability config: invalid cloud trace sampling rate %v", config.CloudTrace.SamplingRate)
152109
}
153110
logger.Infof("Parsed ObservabilityConfig: %+v", &config)
154111
return &config, nil
155112
}
156113

157114
func parseObservabilityConfig() (*config, error) {
158-
if fileSystemPath := os.Getenv(envObservabilityConfigJSON); fileSystemPath != "" {
159-
content, err := ioutil.ReadFile(fileSystemPath) // TODO: Switch to os.ReadFile once dropped support for go 1.15
115+
if f := envconfig.ObservabilityConfigFile; f != "" {
116+
if envconfig.ObservabilityConfig != "" {
117+
logger.Warning("Ignoring GRPC_GCP_OBSERVABILITY_CONFIG and using GRPC_GCP_OBSERVABILITY_CONFIG_FILE contents.")
118+
}
119+
content, err := ioutil.ReadFile(f) // TODO: Switch to os.ReadFile once dropped support for go 1.15
160120
if err != nil {
161-
return nil, fmt.Errorf("error reading observability configuration file %q: %v", fileSystemPath, err)
121+
return nil, fmt.Errorf("error reading observability configuration file %q: %v", f, err)
162122
}
163123
return unmarshalAndVerifyConfig(content)
164-
} else if content := os.Getenv(envObservabilityConfig); content != "" {
165-
return unmarshalAndVerifyConfig([]byte(content))
124+
} else if envconfig.ObservabilityConfig != "" {
125+
return unmarshalAndVerifyConfig([]byte(envconfig.ObservabilityConfig))
166126
}
167127
// If the ENV var doesn't exist, do nothing
168128
return nil, nil
169129
}
170130

171131
func ensureProjectIDInObservabilityConfig(ctx context.Context, config *config) error {
172-
if config.DestinationProjectID == "" {
132+
if config.ProjectID == "" {
173133
// Try to fetch the GCP project id
174134
projectID := fetchDefaultProjectID(ctx)
175135
if projectID == "" {
176136
return fmt.Errorf("empty destination project ID")
177137
}
178-
config.DestinationProjectID = projectID
138+
config.ProjectID = projectID
179139
}
180140
return nil
181141
}
142+
143+
type clientRPCEvents struct {
144+
// Methods is a list of strings which can select a group of methods. By
145+
// default, the list is empty, matching no methods.
146+
//
147+
// The value of the method is in the form of <service>/<method>.
148+
//
149+
// "*" is accepted as a wildcard for:
150+
// 1. The method name. If the value is <service>/*, it matches all
151+
// methods in the specified service.
152+
// 2. The whole value of the field which matches any <service>/<method>.
153+
// It’s not supported when Exclude is true.
154+
// 3. The * wildcard cannot be used on the service name independently,
155+
// */<method> is not supported.
156+
//
157+
// The service name, when specified, must be the fully qualified service
158+
// name, including the package name.
159+
//
160+
// Examples:
161+
// 1."goo.Foo/Bar" selects only the method "Bar" from service "goo.Foo",
162+
// here “goo” is the package name.
163+
// 2."goo.Foo/*" selects all methods from service "goo.Foo"
164+
// 3. "*" selects all methods from all services.
165+
Methods []string `json:"methods,omitempty"`
166+
// Exclude represents whether the methods denoted by Methods should be
167+
// excluded from logging. The default value is false, meaning the methods
168+
// denoted by Methods are included in the logging. If Exclude is true, the
169+
// wildcard `*` cannot be used as value of an entry in Methods.
170+
Exclude bool `json:"exclude,omitempty"`
171+
// MaxMetadataBytes is the maximum number of bytes of each header to log. If
172+
// the size of the metadata is greater than the defined limit, content past
173+
// the limit will be truncated. The default value is 0.
174+
MaxMetadataBytes int `json:"max_metadata_bytes"`
175+
// MaxMessageBytes is the maximum number of bytes of each message to log. If
176+
// the size of the message is greater than the defined limit, content past
177+
// the limit will be truncated. The default value is 0.
178+
MaxMessageBytes int `json:"max_message_bytes"`
179+
}
180+
181+
type serverRPCEvents struct {
182+
// Methods is a list of strings which can select a group of methods. By
183+
// default, the list is empty, matching no methods.
184+
//
185+
// The value of the method is in the form of <service>/<method>.
186+
//
187+
// "*" is accepted as a wildcard for:
188+
// 1. The method name. If the value is <service>/*, it matches all
189+
// methods in the specified service.
190+
// 2. The whole value of the field which matches any <service>/<method>.
191+
// It’s not supported when Exclude is true.
192+
// 3. The * wildcard cannot be used on the service name independently,
193+
// */<method> is not supported.
194+
//
195+
// The service name, when specified, must be the fully qualified service
196+
// name, including the package name.
197+
//
198+
// Examples:
199+
// 1."goo.Foo/Bar" selects only the method "Bar" from service "goo.Foo",
200+
// here “goo” is the package name.
201+
// 2."goo.Foo/*" selects all methods from service "goo.Foo"
202+
// 3. "*" selects all methods from all services.
203+
Methods []string `json:"methods,omitempty"`
204+
// Exclude represents whether the methods denoted by Methods should be
205+
// excluded from logging. The default value is false, meaning the methods
206+
// denoted by Methods are included in the logging. If Exclude is true, the
207+
// wildcard `*` cannot be used as value of an entry in Methods.
208+
Exclude bool `json:"exclude,omitempty"`
209+
// MaxMetadataBytes is the maximum number of bytes of each header to log. If
210+
// the size of the metadata is greater than the defined limit, content past
211+
// the limit will be truncated. The default value is 0.
212+
MaxMetadataBytes int `json:"max_metadata_bytes"`
213+
// MaxMessageBytes is the maximum number of bytes of each message to log. If
214+
// the size of the message is greater than the defined limit, content past
215+
// the limit will be truncated. The default value is 0.
216+
MaxMessageBytes int `json:"max_message_bytes"`
217+
}
218+
219+
type cloudLogging struct {
220+
// ClientRPCEvents represents the configuration for outgoing RPC's from the
221+
// binary. The client_rpc_events configs are evaluated in text order, the
222+
// first one matched is used. If an RPC doesn't match an entry, it will
223+
// continue on to the next entry in the list.
224+
ClientRPCEvents []clientRPCEvents `json:"client_rpc_events,omitempty"`
225+
226+
// ServerRPCEvents represents the configuration for incoming RPC's to the
227+
// binary. The server_rpc_events configs are evaluated in text order, the
228+
// first one matched is used. If an RPC doesn't match an entry, it will
229+
// continue on to the next entry in the list.
230+
ServerRPCEvents []serverRPCEvents `json:"server_rpc_events,omitempty"`
231+
}
232+
233+
type cloudMonitoring struct{}
234+
235+
type cloudTrace struct {
236+
// SamplingRate is the global setting that controls the probability of a RPC
237+
// being traced. For example, 0.05 means there is a 5% chance for a RPC to
238+
// be traced, 1.0 means trace every call, 0 means don’t start new traces. By
239+
// default, the sampling_rate is 0.
240+
SamplingRate float64 `json:"sampling_rate,omitempty"`
241+
}
242+
243+
type config struct {
244+
// ProjectID is the destination GCP project identifier for uploading log
245+
// entries. If empty, the gRPC Observability plugin will attempt to fetch
246+
// the project_id from the GCP environment variables, or from the default
247+
// credentials. If not found, the observability init functions will return
248+
// an error.
249+
ProjectID string `json:"project_id,omitempty"`
250+
// CloudLogging defines the logging options. If not present, logging is disabled.
251+
CloudLogging *cloudLogging `json:"cloud_logging,omitempty"`
252+
// CloudMonitoring determines whether or not metrics are enabled based on
253+
// whether it is present or not. If present, monitoring will be enabled, if
254+
// not present, monitoring is disabled.
255+
CloudMonitoring *cloudMonitoring `json:"cloud_monitoring,omitempty"`
256+
// CloudTrace defines the tracing options. When present, tracing is enabled
257+
// with default configurations. When absent, the tracing is disabled.
258+
CloudTrace *cloudTrace `json:"cloud_trace,omitempty"`
259+
// Labels are applied to cloud logging, monitoring, and trace.
260+
Labels map[string]string `json:"labels,omitempty"`
261+
}

0 commit comments

Comments
 (0)