@@ -21,86 +21,23 @@ package observability
21
21
import (
22
22
"context"
23
23
"encoding/json"
24
+ "errors"
24
25
"fmt"
25
26
"io/ioutil"
26
27
"os"
27
28
"regexp"
28
29
29
30
gcplogging "cloud.google.com/go/logging"
30
31
"golang.org/x/oauth2/google"
32
+ "google.golang.org/grpc/internal/envconfig"
31
33
)
32
34
33
35
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+)|[*])$`
38
38
)
39
39
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 )
104
41
105
42
// fetchDefaultProjectID fetches the default GCP project id from environment.
106
43
func fetchDefaultProjectID (ctx context.Context ) string {
@@ -123,14 +60,34 @@ func fetchDefaultProjectID(ctx context.Context) string {
123
60
return credentials .ProjectID
124
61
}
125
62
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
+ }
129
69
continue
130
70
}
131
- match := logFilterPatternRegexp .FindStringSubmatch (filter . Pattern )
71
+ match := methodStringRegexp .FindStringSubmatch (method )
132
72
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 )
134
91
}
135
92
}
136
93
return nil
@@ -144,38 +101,161 @@ func unmarshalAndVerifyConfig(rawJSON json.RawMessage) (*config, error) {
144
101
if err := json .Unmarshal (rawJSON , & config ); err != nil {
145
102
return nil , fmt .Errorf ("error parsing observability config: %v" , err )
146
103
}
147
- if err := validateFilters (& config ); err != nil {
104
+ if err := validateLoggingEvents (& config ); err != nil {
148
105
return nil , fmt .Errorf ("error parsing observability config: %v" , err )
149
106
}
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 )
152
109
}
153
110
logger .Infof ("Parsed ObservabilityConfig: %+v" , & config )
154
111
return & config , nil
155
112
}
156
113
157
114
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
160
120
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 )
162
122
}
163
123
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 ))
166
126
}
167
127
// If the ENV var doesn't exist, do nothing
168
128
return nil , nil
169
129
}
170
130
171
131
func ensureProjectIDInObservabilityConfig (ctx context.Context , config * config ) error {
172
- if config .DestinationProjectID == "" {
132
+ if config .ProjectID == "" {
173
133
// Try to fetch the GCP project id
174
134
projectID := fetchDefaultProjectID (ctx )
175
135
if projectID == "" {
176
136
return fmt .Errorf ("empty destination project ID" )
177
137
}
178
- config .DestinationProjectID = projectID
138
+ config .ProjectID = projectID
179
139
}
180
140
return nil
181
141
}
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