Skip to content

Commit c7fe135

Browse files
authoredAug 11, 2022
O11Y: Added support for custom tags (#5565)
* O11Y: Added support for custom tags
1 parent 7981af4 commit c7fe135

10 files changed

+236
-647
lines changed
 

‎gcp/observability/config.go

+74-13
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import (
2828

2929
gcplogging "cloud.google.com/go/logging"
3030
"golang.org/x/oauth2/google"
31-
configpb "google.golang.org/grpc/gcp/observability/internal/config"
32-
"google.golang.org/protobuf/encoding/protojson"
3331
)
3432

3533
const (
@@ -41,6 +39,69 @@ const (
4139

4240
var logFilterPatternRegexp = regexp.MustCompile(logFilterPatternRegexpStr)
4341

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+
}
104+
44105
// fetchDefaultProjectID fetches the default GCP project id from environment.
45106
func fetchDefaultProjectID(ctx context.Context) string {
46107
// Step 1: Check ENV var
@@ -62,25 +123,25 @@ func fetchDefaultProjectID(ctx context.Context) string {
62123
return credentials.ProjectID
63124
}
64125

65-
func validateFilters(config *configpb.ObservabilityConfig) error {
66-
for _, filter := range config.GetLogFilters() {
126+
func validateFilters(config *config) error {
127+
for _, filter := range config.LogFilters {
67128
if filter.Pattern == "*" {
68129
continue
69130
}
70131
match := logFilterPatternRegexp.FindStringSubmatch(filter.Pattern)
71132
if match == nil {
72-
return fmt.Errorf("invalid log filter pattern: %v", filter.Pattern)
133+
return fmt.Errorf("invalid log filter Pattern: %v", filter.Pattern)
73134
}
74135
}
75136
return nil
76137
}
77138

78139
// unmarshalAndVerifyConfig unmarshals a json string representing an
79-
// observability config into its protobuf format, and also verifies the
140+
// observability config into its internal go format, and also verifies the
80141
// configuration's fields for validity.
81-
func unmarshalAndVerifyConfig(rawJSON json.RawMessage) (*configpb.ObservabilityConfig, error) {
82-
var config configpb.ObservabilityConfig
83-
if err := protojson.Unmarshal(rawJSON, &config); err != nil {
142+
func unmarshalAndVerifyConfig(rawJSON json.RawMessage) (*config, error) {
143+
var config config
144+
if err := json.Unmarshal(rawJSON, &config); err != nil {
84145
return nil, fmt.Errorf("error parsing observability config: %v", err)
85146
}
86147
if err := validateFilters(&config); err != nil {
@@ -93,7 +154,7 @@ func unmarshalAndVerifyConfig(rawJSON json.RawMessage) (*configpb.ObservabilityC
93154
return &config, nil
94155
}
95156

96-
func parseObservabilityConfig() (*configpb.ObservabilityConfig, error) {
157+
func parseObservabilityConfig() (*config, error) {
97158
if fileSystemPath := os.Getenv(envObservabilityConfigJSON); fileSystemPath != "" {
98159
content, err := ioutil.ReadFile(fileSystemPath) // TODO: Switch to os.ReadFile once dropped support for go 1.15
99160
if err != nil {
@@ -107,14 +168,14 @@ func parseObservabilityConfig() (*configpb.ObservabilityConfig, error) {
107168
return nil, nil
108169
}
109170

110-
func ensureProjectIDInObservabilityConfig(ctx context.Context, config *configpb.ObservabilityConfig) error {
111-
if config.GetDestinationProjectId() == "" {
171+
func ensureProjectIDInObservabilityConfig(ctx context.Context, config *config) error {
172+
if config.DestinationProjectID == "" {
112173
// Try to fetch the GCP project id
113174
projectID := fetchDefaultProjectID(ctx)
114175
if projectID == "" {
115176
return fmt.Errorf("empty destination project ID")
116177
}
117-
config.DestinationProjectId = projectID
178+
config.DestinationProjectID = projectID
118179
}
119180
return nil
120181
}

‎gcp/observability/exporting.go

+6-8
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"context"
2323
"encoding/json"
2424
"fmt"
25-
"os"
2625

2726
gcplogging "cloud.google.com/go/logging"
2827
grpclogrecordpb "google.golang.org/grpc/gcp/observability/internal/logging"
@@ -45,20 +44,19 @@ type cloudLoggingExporter struct {
4544
logger *gcplogging.Logger
4645
}
4746

48-
func newCloudLoggingExporter(ctx context.Context, projectID string) (*cloudLoggingExporter, error) {
49-
c, err := gcplogging.NewClient(ctx, fmt.Sprintf("projects/%v", projectID))
47+
func newCloudLoggingExporter(ctx context.Context, config *config) (*cloudLoggingExporter, error) {
48+
c, err := gcplogging.NewClient(ctx, fmt.Sprintf("projects/%v", config.DestinationProjectID))
5049
if err != nil {
5150
return nil, fmt.Errorf("failed to create cloudLoggingExporter: %v", err)
5251
}
5352
defer logger.Infof("Successfully created cloudLoggingExporter")
54-
customTags := getCustomTags(os.Environ())
55-
if len(customTags) != 0 {
56-
logger.Infof("Adding custom tags: %+v", customTags)
53+
if len(config.CustomTags) != 0 {
54+
logger.Infof("Adding custom tags: %+v", config.CustomTags)
5755
}
5856
return &cloudLoggingExporter{
59-
projectID: projectID,
57+
projectID: config.DestinationProjectID,
6058
client: c,
61-
logger: c.Logger("microservices.googleapis.com/observability/grpc", gcplogging.CommonLabels(customTags)),
59+
logger: c.Logger("microservices.googleapis.com/observability/grpc", gcplogging.CommonLabels(config.CustomTags)),
6260
}, nil
6361
}
6462

‎gcp/observability/internal/config/config.pb.go

-364
This file was deleted.

‎gcp/observability/internal/config/config.proto

-89
This file was deleted.

‎gcp/observability/logging.go

+8-9
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727

2828
"github.com/google/uuid"
2929
binlogpb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
30-
configpb "google.golang.org/grpc/gcp/observability/internal/config"
3130
grpclogrecordpb "google.golang.org/grpc/gcp/observability/internal/logging"
3231
iblog "google.golang.org/grpc/internal/binarylog"
3332
)
@@ -248,7 +247,7 @@ func (l *binaryLogger) Close() {
248247
}
249248
}
250249

251-
func validateExistingMethodLoggerConfig(existing *iblog.MethodLoggerConfig, filter *configpb.ObservabilityConfig_LogFilter) bool {
250+
func validateExistingMethodLoggerConfig(existing *iblog.MethodLoggerConfig, filter logFilter) bool {
252251
// In future, we could add more validations. Currently, we only check if the
253252
// new filter configs are different than the existing one, if so, we log a
254253
// warning.
@@ -258,7 +257,7 @@ func validateExistingMethodLoggerConfig(existing *iblog.MethodLoggerConfig, filt
258257
return existing == nil
259258
}
260259

261-
func createBinaryLoggerConfig(filters []*configpb.ObservabilityConfig_LogFilter) iblog.LoggerConfig {
260+
func createBinaryLoggerConfig(filters []logFilter) iblog.LoggerConfig {
262261
config := iblog.LoggerConfig{
263262
Services: make(map[string]*iblog.MethodLoggerConfig),
264263
Methods: make(map[string]*iblog.MethodLoggerConfig),
@@ -296,8 +295,8 @@ func createBinaryLoggerConfig(filters []*configpb.ObservabilityConfig_LogFilter)
296295

297296
// start is the core logic for setting up the custom binary logging logger, and
298297
// it's also useful for testing.
299-
func (l *binaryLogger) start(config *configpb.ObservabilityConfig, exporter loggingExporter) error {
300-
filters := config.GetLogFilters()
298+
func (l *binaryLogger) start(config *config, exporter loggingExporter) error {
299+
filters := config.LogFilters
301300
if len(filters) == 0 || exporter == nil {
302301
// Doing nothing is allowed
303302
if exporter != nil {
@@ -318,14 +317,14 @@ func (l *binaryLogger) start(config *configpb.ObservabilityConfig, exporter logg
318317
return nil
319318
}
320319

321-
func (l *binaryLogger) Start(ctx context.Context, config *configpb.ObservabilityConfig) error {
322-
if config == nil || !config.GetEnableCloudLogging() {
320+
func (l *binaryLogger) Start(ctx context.Context, config *config) error {
321+
if config == nil || !config.EnableCloudLogging {
323322
return nil
324323
}
325-
if config.GetDestinationProjectId() == "" {
324+
if config.DestinationProjectID == "" {
326325
return fmt.Errorf("failed to enable CloudLogging: empty destination_project_id")
327326
}
328-
exporter, err := newCloudLoggingExporter(ctx, config.DestinationProjectId)
327+
exporter, err := newCloudLoggingExporter(ctx, config)
329328
if err != nil {
330329
return fmt.Errorf("unable to create CloudLogging exporter: %v", err)
331330
}

‎gcp/observability/observability.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func Start(ctx context.Context) error {
6464
}
6565

6666
// Enabling tracing and metrics via OpenCensus
67-
if err := startOpenCensus(config, nil); err != nil {
67+
if err := startOpenCensus(config); err != nil {
6868
return fmt.Errorf("failed to instrument OpenCensus: %v", err)
6969
}
7070

‎gcp/observability/observability_test.go

+102-40
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ import (
3434
"go.opencensus.io/trace"
3535
"google.golang.org/grpc"
3636
"google.golang.org/grpc/credentials/insecure"
37-
configpb "google.golang.org/grpc/gcp/observability/internal/config"
3837
grpclogrecordpb "google.golang.org/grpc/gcp/observability/internal/logging"
38+
"google.golang.org/grpc/internal"
3939
iblog "google.golang.org/grpc/internal/binarylog"
4040
"google.golang.org/grpc/internal/grpctest"
4141
"google.golang.org/grpc/internal/leakcheck"
@@ -129,7 +129,6 @@ func (fle *fakeLoggingExporter) Close() error {
129129
type test struct {
130130
t *testing.T
131131
fle *fakeLoggingExporter
132-
fe *fakeOpenCensusExporter
133132

134133
testServer testgrpc.TestServiceServer // nil means none
135134
// srv and srvAddr are set once startServer is called.
@@ -197,7 +196,7 @@ func (te *test) clientConn() *grpc.ClientConn {
197196
return te.cc
198197
}
199198

200-
func (te *test) enablePluginWithConfig(config *configpb.ObservabilityConfig) {
199+
func (te *test) enablePluginWithConfig(config *config) {
201200
// Injects the fake exporter for testing purposes
202201
te.fle = &fakeLoggingExporter{t: te.t}
203202
defaultLogger = newBinaryLogger(nil)
@@ -208,10 +207,10 @@ func (te *test) enablePluginWithConfig(config *configpb.ObservabilityConfig) {
208207
}
209208

210209
func (te *test) enablePluginWithCaptureAll() {
211-
te.enablePluginWithConfig(&configpb.ObservabilityConfig{
210+
te.enablePluginWithConfig(&config{
212211
EnableCloudLogging: true,
213-
DestinationProjectId: "fake",
214-
LogFilters: []*configpb.ObservabilityConfig_LogFilter{
212+
DestinationProjectID: "fake",
213+
LogFilters: []logFilter{
215214
{
216215
Pattern: "*",
217216
HeaderBytes: infinitySizeBytes,
@@ -223,14 +222,13 @@ func (te *test) enablePluginWithCaptureAll() {
223222

224223
func (te *test) enableOpenCensus() {
225224
defaultMetricsReportingInterval = time.Millisecond * 100
226-
config := &configpb.ObservabilityConfig{
225+
config := &config{
227226
EnableCloudLogging: true,
228227
EnableCloudTrace: true,
229228
EnableCloudMonitoring: true,
230229
GlobalTraceSamplingRate: 1.0,
231230
}
232-
te.fe = &fakeOpenCensusExporter{SeenViews: make(map[string]string), t: te.t}
233-
startOpenCensus(config, te.fe)
231+
startOpenCensus(config)
234232
}
235233

236234
func checkEventCommon(t *testing.T, seen *grpclogrecordpb.GrpcLogRecord) {
@@ -522,7 +520,7 @@ func (s) TestLoggingForErrorCall(t *testing.T) {
522520
func (s) TestEmptyConfig(t *testing.T) {
523521
te := newTest(t)
524522
defer te.tearDown()
525-
te.enablePluginWithConfig(&configpb.ObservabilityConfig{})
523+
te.enablePluginWithConfig(&config{})
526524
te.startServer(&testServer{})
527525
tc := testgrpc.NewTestServiceClient(te.clientConn())
528526

@@ -554,10 +552,10 @@ func (s) TestOverrideConfig(t *testing.T) {
554552
// most specific one. The third filter allows message payload logging, and
555553
// others disabling the message payload logging. We should observe this
556554
// behavior latter.
557-
te.enablePluginWithConfig(&configpb.ObservabilityConfig{
555+
te.enablePluginWithConfig(&config{
558556
EnableCloudLogging: true,
559-
DestinationProjectId: "fake",
560-
LogFilters: []*configpb.ObservabilityConfig_LogFilter{
557+
DestinationProjectID: "fake",
558+
LogFilters: []logFilter{
561559
{
562560
Pattern: "wont/match",
563561
MessageBytes: 0,
@@ -621,10 +619,10 @@ func (s) TestNoMatch(t *testing.T) {
621619
// Setting 3 filters, expected to use the second filter. The second filter
622620
// allows message payload logging, and others disabling the message payload
623621
// logging. We should observe this behavior latter.
624-
te.enablePluginWithConfig(&configpb.ObservabilityConfig{
622+
te.enablePluginWithConfig(&config{
625623
EnableCloudLogging: true,
626-
DestinationProjectId: "fake",
627-
LogFilters: []*configpb.ObservabilityConfig_LogFilter{
624+
DestinationProjectID: "fake",
625+
LogFilters: []logFilter{
628626
{
629627
Pattern: "wont/match",
630628
MessageBytes: 0,
@@ -661,10 +659,10 @@ func (s) TestNoMatch(t *testing.T) {
661659
}
662660

663661
func (s) TestRefuseStartWithInvalidPatterns(t *testing.T) {
664-
config := &configpb.ObservabilityConfig{
662+
config := &config{
665663
EnableCloudLogging: true,
666-
DestinationProjectId: "fake",
667-
LogFilters: []*configpb.ObservabilityConfig_LogFilter{
664+
DestinationProjectID: "fake",
665+
LogFilters: []logFilter{
668666
{
669667
Pattern: ":-)",
670668
},
@@ -673,7 +671,7 @@ func (s) TestRefuseStartWithInvalidPatterns(t *testing.T) {
673671
},
674672
},
675673
}
676-
configJSON, err := protojson.Marshal(config)
674+
configJSON, err := json.Marshal(config)
677675
if err != nil {
678676
t.Fatalf("failed to convert config to JSON: %v", err)
679677
}
@@ -689,7 +687,7 @@ func (s) TestRefuseStartWithInvalidPatterns(t *testing.T) {
689687
// place in the temporary portion of the file system dependent on system. It
690688
// also sets the environment variable GRPC_CONFIG_OBSERVABILITY_JSON to point to
691689
// this created config.
692-
func createTmpConfigInFileSystem(rawJSON string) (*os.File, error) {
690+
func createTmpConfigInFileSystem(rawJSON string) (func(), error) {
693691
configJSONFile, err := ioutil.TempFile(os.TempDir(), "configJSON-")
694692
if err != nil {
695693
return nil, fmt.Errorf("cannot create file %v: %v", configJSONFile.Name(), err)
@@ -699,19 +697,23 @@ func createTmpConfigInFileSystem(rawJSON string) (*os.File, error) {
699697
return nil, fmt.Errorf("cannot write marshalled JSON: %v", err)
700698
}
701699
os.Setenv(envObservabilityConfigJSON, configJSONFile.Name())
702-
return configJSONFile, nil
700+
return func() {
701+
configJSONFile.Close()
702+
os.Setenv(envObservabilityConfigJSON, "")
703+
}, nil
703704
}
704705

705706
// TestJSONEnvVarSet tests a valid observability configuration specified by the
706707
// GRPC_CONFIG_OBSERVABILITY_JSON environment variable, whose value represents a
707708
// file path pointing to a JSON encoded config.
708709
func (s) TestJSONEnvVarSet(t *testing.T) {
709710
configJSON := `{
710-
"destinationProjectId": "fake",
711-
"logFilters":[{"pattern":"*","headerBytes":1073741824,"messageBytes":1073741824}]
711+
"destination_project_id": "fake",
712+
"log_filters":[{"pattern":"*","header_bytes":1073741824,"message_bytes":1073741824}]
712713
}`
713-
configJSONFile, err := createTmpConfigInFileSystem(configJSON)
714-
defer configJSONFile.Close()
714+
cleanup, err := createTmpConfigInFileSystem(configJSON)
715+
defer cleanup()
716+
715717
if err != nil {
716718
t.Fatalf("failed to create config in file system: %v", err)
717719
}
@@ -730,27 +732,27 @@ func (s) TestJSONEnvVarSet(t *testing.T) {
730732
// variable is set and valid.
731733
func (s) TestBothConfigEnvVarsSet(t *testing.T) {
732734
configJSON := `{
733-
"destinationProjectId":"fake",
734-
"logFilters":[{"pattern":":-)"}, {"pattern":"*"}]
735+
"destination_project_id":"fake",
736+
"log_filters":[{"pattern":":-)"}, {"pattern_string":"*"}]
735737
}`
736-
configJSONFile, err := createTmpConfigInFileSystem(configJSON)
737-
defer configJSONFile.Close()
738+
cleanup, err := createTmpConfigInFileSystem(configJSON)
739+
defer cleanup()
738740
if err != nil {
739741
t.Fatalf("failed to create config in file system: %v", err)
740742
}
741743
// This configuration should be ignored, as precedence 2.
742-
validConfig := &configpb.ObservabilityConfig{
744+
validConfig := &config{
743745
EnableCloudLogging: true,
744-
DestinationProjectId: "fake",
745-
LogFilters: []*configpb.ObservabilityConfig_LogFilter{
746+
DestinationProjectID: "fake",
747+
LogFilters: []logFilter{
746748
{
747749
Pattern: "*",
748750
HeaderBytes: infinitySizeBytes,
749751
MessageBytes: infinitySizeBytes,
750752
},
751753
},
752754
}
753-
validConfigJSON, err := protojson.Marshal(validConfig)
755+
validConfigJSON, err := json.Marshal(validConfig)
754756
if err != nil {
755757
t.Fatalf("failed to convert config to JSON: %v", err)
756758
}
@@ -766,6 +768,7 @@ func (s) TestBothConfigEnvVarsSet(t *testing.T) {
766768
// a file (or valid configuration).
767769
func (s) TestErrInFileSystemEnvVar(t *testing.T) {
768770
os.Setenv(envObservabilityConfigJSON, "/this-file/does-not-exist")
771+
defer os.Setenv(envObservabilityConfigJSON, "")
769772
if err := Start(context.Background()); err == nil {
770773
t.Fatalf("Invalid file system path not triggering error")
771774
}
@@ -783,6 +786,16 @@ func (s) TestNoEnvSet(t *testing.T) {
783786
func (s) TestOpenCensusIntegration(t *testing.T) {
784787
te := newTest(t)
785788
defer te.tearDown()
789+
fe := &fakeOpenCensusExporter{SeenViews: make(map[string]string), t: te.t}
790+
791+
defer func(ne func(config *config) (tracingMetricsExporter, error)) {
792+
newExporter = ne
793+
}(newExporter)
794+
795+
newExporter = func(config *config) (tracingMetricsExporter, error) {
796+
return fe, nil
797+
}
798+
786799
te.enableOpenCensus()
787800
te.startServer(&testServer{})
788801
tc := testgrpc.NewTestServiceClient(te.clientConn())
@@ -807,17 +820,17 @@ func (s) TestOpenCensusIntegration(t *testing.T) {
807820
defer cancel()
808821
for ctx.Err() == nil {
809822
errs = nil
810-
te.fe.mu.RLock()
811-
if value := te.fe.SeenViews["grpc.io/client/completed_rpcs"]; value != TypeOpenCensusViewCount {
823+
fe.mu.RLock()
824+
if value := fe.SeenViews["grpc.io/client/completed_rpcs"]; value != TypeOpenCensusViewCount {
812825
errs = append(errs, fmt.Errorf("unexpected type for grpc.io/client/completed_rpcs: %s != %s", value, TypeOpenCensusViewCount))
813826
}
814-
if value := te.fe.SeenViews["grpc.io/server/completed_rpcs"]; value != TypeOpenCensusViewCount {
827+
if value := fe.SeenViews["grpc.io/server/completed_rpcs"]; value != TypeOpenCensusViewCount {
815828
errs = append(errs, fmt.Errorf("unexpected type for grpc.io/server/completed_rpcs: %s != %s", value, TypeOpenCensusViewCount))
816829
}
817-
if te.fe.SeenSpans <= 0 {
818-
errs = append(errs, fmt.Errorf("unexpected number of seen spans: %v <= 0", te.fe.SeenSpans))
830+
if fe.SeenSpans <= 0 {
831+
errs = append(errs, fmt.Errorf("unexpected number of seen spans: %v <= 0", fe.SeenSpans))
819832
}
820-
te.fe.mu.RUnlock()
833+
fe.mu.RUnlock()
821834
if len(errs) == 0 {
822835
break
823836
}
@@ -827,3 +840,52 @@ func (s) TestOpenCensusIntegration(t *testing.T) {
827840
t.Fatalf("Invalid OpenCensus export data: %v", errs)
828841
}
829842
}
843+
844+
// TestCustomTagsTracingMetrics verifies that the custom tags defined in our
845+
// observability configuration and set to two hardcoded values are passed to the
846+
// function to create an exporter.
847+
func (s) TestCustomTagsTracingMetrics(t *testing.T) {
848+
defer func(ne func(config *config) (tracingMetricsExporter, error)) {
849+
newExporter = ne
850+
}(newExporter)
851+
fe := &fakeOpenCensusExporter{SeenViews: make(map[string]string), t: t}
852+
newExporter = func(config *config) (tracingMetricsExporter, error) {
853+
ct := config.CustomTags
854+
if len(ct) < 1 {
855+
t.Fatalf("less than 2 custom tags sent in")
856+
}
857+
if val, ok := ct["customtag1"]; !ok || val != "wow" {
858+
t.Fatalf("incorrect custom tag: got %v, want %v", val, "wow")
859+
}
860+
if val, ok := ct["customtag2"]; !ok || val != "nice" {
861+
t.Fatalf("incorrect custom tag: got %v, want %v", val, "nice")
862+
}
863+
return fe, nil
864+
}
865+
866+
// This configuration present in file system and it's defined custom tags should make it
867+
// to the created exporter.
868+
configJSON := `{
869+
"destination_project_id": "fake",
870+
"enable_cloud_trace": true,
871+
"enable_cloud_monitoring": true,
872+
"global_trace_sampling_rate": 1.0,
873+
"custom_tags":{"customtag1":"wow","customtag2":"nice"}
874+
}`
875+
cleanup, err := createTmpConfigInFileSystem(configJSON)
876+
defer cleanup()
877+
878+
// To clear globally registered tracing and metrics exporters.
879+
defer func() {
880+
internal.ClearExtraDialOptions()
881+
internal.ClearExtraServerOptions()
882+
}()
883+
884+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
885+
defer cancel()
886+
err = Start(ctx)
887+
defer End()
888+
if err != nil {
889+
t.Fatalf("Start() failed with err: %v", err)
890+
}
891+
}

‎gcp/observability/opencensus.go

+45-13
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
"go.opencensus.io/stats/view"
3030
"go.opencensus.io/trace"
3131
"google.golang.org/grpc"
32-
configpb "google.golang.org/grpc/gcp/observability/internal/config"
3332
"google.golang.org/grpc/internal"
3433
)
3534

@@ -38,26 +37,59 @@ var (
3837
defaultMetricsReportingInterval = time.Second * 30
3938
)
4039

40+
func tagsToMonitoringLabels(tags map[string]string) *stackdriver.Labels {
41+
labels := &stackdriver.Labels{}
42+
for k, v := range tags {
43+
labels.Set(k, v, "")
44+
}
45+
return labels
46+
}
47+
48+
func tagsToTraceAttributes(tags map[string]string) map[string]interface{} {
49+
ta := make(map[string]interface{}, len(tags))
50+
for k, v := range tags {
51+
ta[k] = v
52+
}
53+
return ta
54+
}
55+
56+
type tracingMetricsExporter interface {
57+
trace.Exporter
58+
view.Exporter
59+
}
60+
61+
// global to stub out in tests
62+
var newExporter = newStackdriverExporter
63+
64+
func newStackdriverExporter(config *config) (tracingMetricsExporter, error) {
65+
// Create the Stackdriver exporter, which is shared between tracing and stats
66+
mr := monitoredresource.Autodetect()
67+
logger.Infof("Detected MonitoredResource:: %+v", mr)
68+
var err error
69+
exporter, err := stackdriver.NewExporter(stackdriver.Options{
70+
ProjectID: config.DestinationProjectID,
71+
MonitoredResource: mr,
72+
DefaultMonitoringLabels: tagsToMonitoringLabels(config.CustomTags),
73+
DefaultTraceAttributes: tagsToTraceAttributes(config.CustomTags),
74+
})
75+
if err != nil {
76+
return nil, fmt.Errorf("failed to create Stackdriver exporter: %v", err)
77+
}
78+
return exporter, nil
79+
}
80+
4181
// This method accepts config and exporter; the exporter argument is exposed to
4282
// assist unit testing of the OpenCensus behavior.
43-
func startOpenCensus(config *configpb.ObservabilityConfig, exporter interface{}) error {
83+
func startOpenCensus(config *config) error {
4484
// If both tracing and metrics are disabled, there's no point inject default
4585
// StatsHandler.
4686
if config == nil || (!config.EnableCloudTrace && !config.EnableCloudMonitoring) {
4787
return nil
4888
}
4989

50-
if exporter == nil {
51-
// Create the Stackdriver exporter, which is shared between tracing and stats
52-
mr := monitoredresource.Autodetect()
53-
logger.Infof("Detected MonitoredResource:: %+v", mr)
54-
var err error
55-
if exporter, err = stackdriver.NewExporter(stackdriver.Options{
56-
ProjectID: config.DestinationProjectId,
57-
MonitoredResource: mr,
58-
}); err != nil {
59-
return fmt.Errorf("failed to create Stackdriver exporter: %v", err)
60-
}
90+
exporter, err := newExporter(config)
91+
if err != nil {
92+
return err
6193
}
6294

6395
var so trace.StartOptions

‎gcp/observability/tags.go

-46
This file was deleted.

‎gcp/observability/tags_test.go

-64
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.