@@ -21,86 +21,23 @@ package observability
2121import (
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
3335const (
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.
10643func 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
157114func 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
171131func 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