snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
273
gcp/observability/config.go
Normal file
273
gcp/observability/config.go
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
gcplogging "cloud.google.com/go/logging"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
)
|
||||
|
||||
const envProjectID = "GOOGLE_CLOUD_PROJECT"
|
||||
|
||||
// fetchDefaultProjectID fetches the default GCP project id from environment.
|
||||
func fetchDefaultProjectID(ctx context.Context) string {
|
||||
// Step 1: Check ENV var
|
||||
if s := os.Getenv(envProjectID); s != "" {
|
||||
logger.Infof("Found project ID from env %v: %v", envProjectID, s)
|
||||
return s
|
||||
}
|
||||
// Step 2: Check default credential
|
||||
credentials, err := google.FindDefaultCredentials(ctx, gcplogging.WriteScope)
|
||||
if err != nil {
|
||||
logger.Infof("Failed to locate Google Default Credential: %v", err)
|
||||
return ""
|
||||
}
|
||||
if credentials.ProjectID == "" {
|
||||
logger.Infof("Failed to find project ID in default credential: %v", err)
|
||||
return ""
|
||||
}
|
||||
logger.Infof("Found project ID from Google Default Credential: %v", credentials.ProjectID)
|
||||
return credentials.ProjectID
|
||||
}
|
||||
|
||||
// validateMethodString validates whether the string passed in is a valid
|
||||
// pattern.
|
||||
func validateMethodString(method string) error {
|
||||
if strings.HasPrefix(method, "/") {
|
||||
return errors.New("cannot have a leading slash")
|
||||
}
|
||||
serviceMethod := strings.Split(method, "/")
|
||||
if len(serviceMethod) != 2 {
|
||||
return errors.New("/ must come in between service and method, only one /")
|
||||
}
|
||||
if serviceMethod[1] == "" {
|
||||
return errors.New("method name must be non empty")
|
||||
}
|
||||
if serviceMethod[0] == "*" {
|
||||
return errors.New("cannot have service wildcard * i.e. (*/m)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLogEventMethod(methods []string, exclude bool) error {
|
||||
for _, method := range methods {
|
||||
if method == "*" {
|
||||
if exclude {
|
||||
return errors.New("cannot have exclude and a '*' wildcard")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := validateMethodString(method); err != nil {
|
||||
return fmt.Errorf("invalid method string: %v, err: %v", method, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLoggingEvents(config *config) error {
|
||||
if config.CloudLogging == nil {
|
||||
return nil
|
||||
}
|
||||
for _, clientRPCEvent := range config.CloudLogging.ClientRPCEvents {
|
||||
if err := validateLogEventMethod(clientRPCEvent.Methods, clientRPCEvent.Exclude); err != nil {
|
||||
return fmt.Errorf("error in clientRPCEvent method: %v", err)
|
||||
}
|
||||
}
|
||||
for _, serverRPCEvent := range config.CloudLogging.ServerRPCEvents {
|
||||
if err := validateLogEventMethod(serverRPCEvent.Methods, serverRPCEvent.Exclude); err != nil {
|
||||
return fmt.Errorf("error in serverRPCEvent method: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unmarshalAndVerifyConfig unmarshals a json string representing an
|
||||
// observability config into its internal go format, and also verifies the
|
||||
// configuration's fields for validity.
|
||||
func unmarshalAndVerifyConfig(rawJSON json.RawMessage) (*config, error) {
|
||||
var config config
|
||||
if err := json.Unmarshal(rawJSON, &config); err != nil {
|
||||
return nil, fmt.Errorf("error parsing observability config: %v", err)
|
||||
}
|
||||
if err := validateLoggingEvents(&config); err != nil {
|
||||
return nil, fmt.Errorf("error parsing observability config: %v", err)
|
||||
}
|
||||
if config.CloudTrace != nil && (config.CloudTrace.SamplingRate > 1 || config.CloudTrace.SamplingRate < 0) {
|
||||
return nil, fmt.Errorf("error parsing observability config: invalid cloud trace sampling rate %v", config.CloudTrace.SamplingRate)
|
||||
}
|
||||
logger.Infof("Parsed ObservabilityConfig: %+v", &config)
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func parseObservabilityConfig() (*config, error) {
|
||||
if f := envconfig.ObservabilityConfigFile; f != "" {
|
||||
if envconfig.ObservabilityConfig != "" {
|
||||
logger.Warning("Ignoring GRPC_GCP_OBSERVABILITY_CONFIG and using GRPC_GCP_OBSERVABILITY_CONFIG_FILE contents.")
|
||||
}
|
||||
content, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading observability configuration file %q: %v", f, err)
|
||||
}
|
||||
return unmarshalAndVerifyConfig(content)
|
||||
} else if envconfig.ObservabilityConfig != "" {
|
||||
return unmarshalAndVerifyConfig([]byte(envconfig.ObservabilityConfig))
|
||||
}
|
||||
// If the ENV var doesn't exist, do nothing
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func ensureProjectIDInObservabilityConfig(ctx context.Context, config *config) error {
|
||||
if config.ProjectID == "" {
|
||||
// Try to fetch the GCP project id
|
||||
projectID := fetchDefaultProjectID(ctx)
|
||||
if projectID == "" {
|
||||
return fmt.Errorf("empty destination project ID")
|
||||
}
|
||||
config.ProjectID = projectID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type clientRPCEvents struct {
|
||||
// Methods is a list of strings which can select a group of methods. By
|
||||
// default, the list is empty, matching no methods.
|
||||
//
|
||||
// The value of the method is in the form of <service>/<method>.
|
||||
//
|
||||
// "*" is accepted as a wildcard for:
|
||||
// 1. The method name. If the value is <service>/*, it matches all
|
||||
// methods in the specified service.
|
||||
// 2. The whole value of the field which matches any <service>/<method>.
|
||||
// It’s not supported when Exclude is true.
|
||||
// 3. The * wildcard cannot be used on the service name independently,
|
||||
// */<method> is not supported.
|
||||
//
|
||||
// The service name, when specified, must be the fully qualified service
|
||||
// name, including the package name.
|
||||
//
|
||||
// Examples:
|
||||
// 1."goo.Foo/Bar" selects only the method "Bar" from service "goo.Foo",
|
||||
// here “goo” is the package name.
|
||||
// 2."goo.Foo/*" selects all methods from service "goo.Foo"
|
||||
// 3. "*" selects all methods from all services.
|
||||
Methods []string `json:"methods,omitempty"`
|
||||
// Exclude represents whether the methods denoted by Methods should be
|
||||
// excluded from logging. The default value is false, meaning the methods
|
||||
// denoted by Methods are included in the logging. If Exclude is true, the
|
||||
// wildcard `*` cannot be used as value of an entry in Methods.
|
||||
Exclude bool `json:"exclude,omitempty"`
|
||||
// MaxMetadataBytes is the maximum number of bytes of each header to log. If
|
||||
// the size of the metadata is greater than the defined limit, content past
|
||||
// the limit will be truncated. The default value is 0.
|
||||
MaxMetadataBytes int `json:"max_metadata_bytes"`
|
||||
// MaxMessageBytes is the maximum number of bytes of each message to log. If
|
||||
// the size of the message is greater than the defined limit, content past
|
||||
// the limit will be truncated. The default value is 0.
|
||||
MaxMessageBytes int `json:"max_message_bytes"`
|
||||
}
|
||||
|
||||
type serverRPCEvents struct {
|
||||
// Methods is a list of strings which can select a group of methods. By
|
||||
// default, the list is empty, matching no methods.
|
||||
//
|
||||
// The value of the method is in the form of <service>/<method>.
|
||||
//
|
||||
// "*" is accepted as a wildcard for:
|
||||
// 1. The method name. If the value is <service>/*, it matches all
|
||||
// methods in the specified service.
|
||||
// 2. The whole value of the field which matches any <service>/<method>.
|
||||
// It’s not supported when Exclude is true.
|
||||
// 3. The * wildcard cannot be used on the service name independently,
|
||||
// */<method> is not supported.
|
||||
//
|
||||
// The service name, when specified, must be the fully qualified service
|
||||
// name, including the package name.
|
||||
//
|
||||
// Examples:
|
||||
// 1."goo.Foo/Bar" selects only the method "Bar" from service "goo.Foo",
|
||||
// here “goo” is the package name.
|
||||
// 2."goo.Foo/*" selects all methods from service "goo.Foo"
|
||||
// 3. "*" selects all methods from all services.
|
||||
Methods []string `json:"methods,omitempty"`
|
||||
// Exclude represents whether the methods denoted by Methods should be
|
||||
// excluded from logging. The default value is false, meaning the methods
|
||||
// denoted by Methods are included in the logging. If Exclude is true, the
|
||||
// wildcard `*` cannot be used as value of an entry in Methods.
|
||||
Exclude bool `json:"exclude,omitempty"`
|
||||
// MaxMetadataBytes is the maximum number of bytes of each header to log. If
|
||||
// the size of the metadata is greater than the defined limit, content past
|
||||
// the limit will be truncated. The default value is 0.
|
||||
MaxMetadataBytes int `json:"max_metadata_bytes"`
|
||||
// MaxMessageBytes is the maximum number of bytes of each message to log. If
|
||||
// the size of the message is greater than the defined limit, content past
|
||||
// the limit will be truncated. The default value is 0.
|
||||
MaxMessageBytes int `json:"max_message_bytes"`
|
||||
}
|
||||
|
||||
type cloudLogging struct {
|
||||
// ClientRPCEvents represents the configuration for outgoing RPC's from the
|
||||
// binary. The client_rpc_events configs are evaluated in text order, the
|
||||
// first one matched is used. If an RPC doesn't match an entry, it will
|
||||
// continue on to the next entry in the list.
|
||||
ClientRPCEvents []clientRPCEvents `json:"client_rpc_events,omitempty"`
|
||||
|
||||
// ServerRPCEvents represents the configuration for incoming RPC's to the
|
||||
// binary. The server_rpc_events configs are evaluated in text order, the
|
||||
// first one matched is used. If an RPC doesn't match an entry, it will
|
||||
// continue on to the next entry in the list.
|
||||
ServerRPCEvents []serverRPCEvents `json:"server_rpc_events,omitempty"`
|
||||
}
|
||||
|
||||
type cloudMonitoring struct{}
|
||||
|
||||
type cloudTrace struct {
|
||||
// SamplingRate is the global setting that controls the probability of an RPC
|
||||
// being traced. For example, 0.05 means there is a 5% chance for an RPC to
|
||||
// be traced, 1.0 means trace every call, 0 means don’t start new traces. By
|
||||
// default, the sampling_rate is 0.
|
||||
SamplingRate float64 `json:"sampling_rate,omitempty"`
|
||||
}
|
||||
|
||||
type config struct {
|
||||
// ProjectID is the destination GCP project identifier for uploading log
|
||||
// entries. If empty, the gRPC Observability plugin will attempt to fetch
|
||||
// the project_id from the GCP environment variables, or from the default
|
||||
// credentials. If not found, the observability init functions will return
|
||||
// an error.
|
||||
ProjectID string `json:"project_id,omitempty"`
|
||||
// CloudLogging defines the logging options. If not present, logging is disabled.
|
||||
CloudLogging *cloudLogging `json:"cloud_logging,omitempty"`
|
||||
// CloudMonitoring determines whether or not metrics are enabled based on
|
||||
// whether it is present or not. If present, monitoring will be enabled, if
|
||||
// not present, monitoring is disabled.
|
||||
CloudMonitoring *cloudMonitoring `json:"cloud_monitoring,omitempty"`
|
||||
// CloudTrace defines the tracing options. When present, tracing is enabled
|
||||
// with default configurations. When absent, the tracing is disabled.
|
||||
CloudTrace *cloudTrace `json:"cloud_trace,omitempty"`
|
||||
// Labels are applied to cloud logging, monitoring, and trace.
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user