Skip to content

Commit 48022ac

Browse files
authored
chore(storage): Instrument existing metadata ops with storage trace package (googleapis#11107)
Update: supersedes googleapis#11051 - Adds a development flag GO_STORAGE_DEV_OTEL_TRACING that will be removed upon experimental feature launch - Replaces existing metadata ops instrumentation with storage trace package ([tracking sheet](https://docs.google.com/spreadsheets/d/1IYUimVewLLfUdQW7d5w4BFRWdMubtXc3vofJJRdpst0/edit?gid=0#gid=0)) - Adds emulated trace tests
1 parent d334239 commit 48022ac

File tree

9 files changed

+389
-44
lines changed

9 files changed

+389
-44
lines changed

storage/acl.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package storage
1717
import (
1818
"context"
1919

20-
"cloud.google.com/go/internal/trace"
2120
"cloud.google.com/go/storage/internal/apiv2/storagepb"
2221
raw "google.golang.org/api/storage/v1"
2322
)
@@ -77,8 +76,8 @@ type ACLHandle struct {
7776

7877
// Delete permanently deletes the ACL entry for the given entity.
7978
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
80-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Delete")
81-
defer func() { trace.EndSpan(ctx, err) }()
79+
ctx, _ = startSpan(ctx, "ACL.Delete")
80+
defer func() { endSpan(ctx, err) }()
8281

8382
if a.object != "" {
8483
return a.objectDelete(ctx, entity)
@@ -91,8 +90,8 @@ func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
9190

9291
// Set sets the role for the given entity.
9392
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (err error) {
94-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Set")
95-
defer func() { trace.EndSpan(ctx, err) }()
93+
ctx, _ = startSpan(ctx, "ACL.Set")
94+
defer func() { endSpan(ctx, err) }()
9695

9796
if a.object != "" {
9897
return a.objectSet(ctx, entity, role, false)
@@ -105,8 +104,8 @@ func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (er
105104

106105
// List retrieves ACL entries.
107106
func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule, err error) {
108-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.List")
109-
defer func() { trace.EndSpan(ctx, err) }()
107+
ctx, _ = startSpan(ctx, "ACL.List")
108+
defer func() { endSpan(ctx, err) }()
110109

111110
if a.object != "" {
112111
return a.objectList(ctx)

storage/bucket.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626

2727
"cloud.google.com/go/compute/metadata"
2828
"cloud.google.com/go/internal/optional"
29-
"cloud.google.com/go/internal/trace"
3029
"cloud.google.com/go/storage/internal/apiv2/storagepb"
3130
"google.golang.org/api/googleapi"
3231
"google.golang.org/api/iamcredentials/v1"
@@ -82,8 +81,8 @@ func (c *Client) Bucket(name string) *BucketHandle {
8281
// Create creates the Bucket in the project.
8382
// If attrs is nil the API defaults will be used.
8483
func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *BucketAttrs) (err error) {
85-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Create")
86-
defer func() { trace.EndSpan(ctx, err) }()
84+
ctx, _ = startSpan(ctx, "Bucket.Create")
85+
defer func() { endSpan(ctx, err) }()
8786

8887
o := makeStorageOpts(true, b.retry, b.userProject)
8988

@@ -95,8 +94,8 @@ func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *Buck
9594

9695
// Delete deletes the Bucket.
9796
func (b *BucketHandle) Delete(ctx context.Context) (err error) {
98-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Delete")
99-
defer func() { trace.EndSpan(ctx, err) }()
97+
ctx, _ = startSpan(ctx, "Bucket.Delete")
98+
defer func() { endSpan(ctx, err) }()
10099

101100
o := makeStorageOpts(true, b.retry, b.userProject)
102101
return b.c.tc.DeleteBucket(ctx, b.name, b.conds, o...)
@@ -150,17 +149,17 @@ func (b *BucketHandle) Object(name string) *ObjectHandle {
150149

151150
// Attrs returns the metadata for the bucket.
152151
func (b *BucketHandle) Attrs(ctx context.Context) (attrs *BucketAttrs, err error) {
153-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Attrs")
154-
defer func() { trace.EndSpan(ctx, err) }()
152+
ctx, _ = startSpan(ctx, "Bucket.Attrs")
153+
defer func() { endSpan(ctx, err) }()
155154

156155
o := makeStorageOpts(true, b.retry, b.userProject)
157156
return b.c.tc.GetBucket(ctx, b.name, b.conds, o...)
158157
}
159158

160159
// Update updates a bucket's attributes.
161160
func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (attrs *BucketAttrs, err error) {
162-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Update")
163-
defer func() { trace.EndSpan(ctx, err) }()
161+
ctx, _ = startSpan(ctx, "Bucket.Update")
162+
defer func() { endSpan(ctx, err) }()
164163

165164
isIdempotent := b.conds != nil && b.conds.MetagenerationMatch != 0
166165
o := makeStorageOpts(isIdempotent, b.retry, b.userProject)

storage/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ require (
1818
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0
1919
go.opentelemetry.io/otel/sdk v1.33.0
2020
go.opentelemetry.io/otel/sdk/metric v1.31.0
21+
go.opentelemetry.io/otel/trace v1.33.0
2122
golang.org/x/oauth2 v0.25.0
2223
golang.org/x/sync v0.10.0
2324
google.golang.org/api v0.217.0
@@ -53,7 +54,6 @@ require (
5354
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
5455
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
5556
go.opentelemetry.io/otel/metric v1.33.0 // indirect
56-
go.opentelemetry.io/otel/trace v1.33.0 // indirect
5757
golang.org/x/crypto v0.32.0 // indirect
5858
golang.org/x/net v0.34.0 // indirect
5959
golang.org/x/sys v0.29.0 // indirect

storage/http_client.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,9 +1281,6 @@ func (c *httpStorageClient) DeleteHMACKey(ctx context.Context, project string, a
12811281
// Note: This API does not support pagination. However, entity limits cap the number of notifications on a single bucket,
12821282
// so all results will be returned in the first response. See https://cloud.google.com/storage/quotas#buckets.
12831283
func (c *httpStorageClient) ListNotifications(ctx context.Context, bucket string, opts ...storageOption) (n map[string]*Notification, err error) {
1284-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.httpStorageClient.ListNotifications")
1285-
defer func() { trace.EndSpan(ctx, err) }()
1286-
12871284
s := callSettings(c.settings, opts...)
12881285
call := c.raw.Notifications.List(bucket)
12891286
if s.userProject != "" {
@@ -1301,9 +1298,6 @@ func (c *httpStorageClient) ListNotifications(ctx context.Context, bucket string
13011298
}
13021299

13031300
func (c *httpStorageClient) CreateNotification(ctx context.Context, bucket string, n *Notification, opts ...storageOption) (ret *Notification, err error) {
1304-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.httpStorageClient.CreateNotification")
1305-
defer func() { trace.EndSpan(ctx, err) }()
1306-
13071301
s := callSettings(c.settings, opts...)
13081302
call := c.raw.Notifications.Insert(bucket, toRawNotification(n))
13091303
if s.userProject != "" {
@@ -1321,9 +1315,6 @@ func (c *httpStorageClient) CreateNotification(ctx context.Context, bucket strin
13211315
}
13221316

13231317
func (c *httpStorageClient) DeleteNotification(ctx context.Context, bucket string, id string, opts ...storageOption) (err error) {
1324-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.httpStorageClient.DeleteNotification")
1325-
defer func() { trace.EndSpan(ctx, err) }()
1326-
13271318
s := callSettings(c.settings, opts...)
13281319
call := c.raw.Notifications.Delete(bucket, id)
13291320
if s.userProject != "" {

storage/iam.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919

2020
"cloud.google.com/go/iam"
2121
"cloud.google.com/go/iam/apiv1/iampb"
22-
"cloud.google.com/go/internal/trace"
2322
raw "google.golang.org/api/storage/v1"
2423
"google.golang.org/genproto/googleapis/type/expr"
2524
)
@@ -45,25 +44,25 @@ func (c *iamClient) Get(ctx context.Context, resource string) (p *iampb.Policy,
4544
}
4645

4746
func (c *iamClient) GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (p *iampb.Policy, err error) {
48-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Get")
49-
defer func() { trace.EndSpan(ctx, err) }()
47+
ctx, _ = startSpan(ctx, "storage.IAM.Get")
48+
defer func() { endSpan(ctx, err) }()
5049

5150
o := makeStorageOpts(true, c.retry, c.userProject)
5251
return c.client.tc.GetIamPolicy(ctx, resource, requestedPolicyVersion, o...)
5352
}
5453

5554
func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) (err error) {
56-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Set")
57-
defer func() { trace.EndSpan(ctx, err) }()
55+
ctx, _ = startSpan(ctx, "storage.IAM.Set")
56+
defer func() { endSpan(ctx, err) }()
5857

5958
isIdempotent := len(p.Etag) > 0
6059
o := makeStorageOpts(isIdempotent, c.retry, c.userProject)
6160
return c.client.tc.SetIamPolicy(ctx, resource, p, o...)
6261
}
6362

6463
func (c *iamClient) Test(ctx context.Context, resource string, perms []string) (permissions []string, err error) {
65-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Test")
66-
defer func() { trace.EndSpan(ctx, err) }()
64+
ctx, _ = startSpan(ctx, "storage.IAM.Test")
65+
defer func() { endSpan(ctx, err) }()
6766

6867
o := makeStorageOpts(true, c.retry, c.userProject)
6968
return c.client.tc.TestIamPermissions(ctx, resource, perms, o...)

storage/notifications.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"fmt"
2121
"regexp"
2222

23-
"cloud.google.com/go/internal/trace"
2423
raw "google.golang.org/api/storage/v1"
2524
)
2625

@@ -121,8 +120,8 @@ func toRawNotification(n *Notification) *raw.Notification {
121120
// returned Notification's ID can be used to refer to it.
122121
// Note: gRPC is not supported.
123122
func (b *BucketHandle) AddNotification(ctx context.Context, n *Notification) (ret *Notification, err error) {
124-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.AddNotification")
125-
defer func() { trace.EndSpan(ctx, err) }()
123+
ctx, _ = startSpan(ctx, "Bucket.AddNotification")
124+
defer func() { endSpan(ctx, err) }()
126125

127126
if n.ID != "" {
128127
return nil, errors.New("storage: AddNotification: ID must not be set")
@@ -143,8 +142,8 @@ func (b *BucketHandle) AddNotification(ctx context.Context, n *Notification) (re
143142
// indexed by notification ID.
144143
// Note: gRPC is not supported.
145144
func (b *BucketHandle) Notifications(ctx context.Context) (n map[string]*Notification, err error) {
146-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Notifications")
147-
defer func() { trace.EndSpan(ctx, err) }()
145+
ctx, _ = startSpan(ctx, "Bucket.Notifications")
146+
defer func() { endSpan(ctx, err) }()
148147

149148
opts := makeStorageOpts(true, b.retry, b.userProject)
150149
n, err = b.c.tc.ListNotifications(ctx, b.name, opts...)
@@ -162,8 +161,8 @@ func notificationsToMap(rns []*raw.Notification) map[string]*Notification {
162161
// DeleteNotification deletes the notification with the given ID.
163162
// Note: gRPC is not supported.
164163
func (b *BucketHandle) DeleteNotification(ctx context.Context, id string) (err error) {
165-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.DeleteNotification")
166-
defer func() { trace.EndSpan(ctx, err) }()
164+
ctx, _ = startSpan(ctx, "Bucket.DeleteNotification")
165+
defer func() { endSpan(ctx, err) }()
167166

168167
opts := makeStorageOpts(true, b.retry, b.userProject)
169168
return b.c.tc.DeleteNotification(ctx, b.name, id, opts...)

storage/storage.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,8 +1019,8 @@ func (o *ObjectHandle) Key(encryptionKey []byte) *ObjectHandle {
10191019
// Attrs returns meta information about the object.
10201020
// ErrObjectNotExist will be returned if the object is not found.
10211021
func (o *ObjectHandle) Attrs(ctx context.Context) (attrs *ObjectAttrs, err error) {
1022-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.Attrs")
1023-
defer func() { trace.EndSpan(ctx, err) }()
1022+
ctx, _ = startSpan(ctx, "Object.Attrs")
1023+
defer func() { endSpan(ctx, err) }()
10241024

10251025
if err := o.validate(); err != nil {
10261026
return nil, err
@@ -1033,8 +1033,8 @@ func (o *ObjectHandle) Attrs(ctx context.Context) (attrs *ObjectAttrs, err error
10331033
// ObjectAttrsToUpdate docs for details on treatment of zero values.
10341034
// ErrObjectNotExist will be returned if the object is not found.
10351035
func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (oa *ObjectAttrs, err error) {
1036-
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.Update")
1037-
defer func() { trace.EndSpan(ctx, err) }()
1036+
ctx, _ = startSpan(ctx, "Object.Update")
1037+
defer func() { endSpan(ctx, err) }()
10381038

10391039
if err := o.validate(); err != nil {
10401040
return nil, err

storage/trace.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package storage
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"os"
21+
22+
internalTrace "cloud.google.com/go/internal/trace"
23+
"cloud.google.com/go/storage/internal"
24+
"go.opentelemetry.io/otel"
25+
"go.opentelemetry.io/otel/attribute"
26+
otelcodes "go.opentelemetry.io/otel/codes"
27+
"go.opentelemetry.io/otel/trace"
28+
)
29+
30+
const (
31+
storageOtelTracingDevVar = "GO_STORAGE_DEV_OTEL_TRACING"
32+
defaultTracerName = "cloud.google.com/go/storage"
33+
gcpClientRepo = "googleapis/google-cloud-go"
34+
gcpClientArtifact = "cloud.google.com/go/storage"
35+
)
36+
37+
// isOTelTracingDevEnabled checks the development flag until experimental feature is launched.
38+
// TODO: Remove development flag upon experimental launch.
39+
func isOTelTracingDevEnabled() bool {
40+
return os.Getenv(storageOtelTracingDevVar) == "true"
41+
}
42+
43+
func tracer() trace.Tracer {
44+
return otel.Tracer(defaultTracerName, trace.WithInstrumentationVersion(internal.Version))
45+
}
46+
47+
// startSpan creates a span and a context.Context containing the newly-created span.
48+
// If the context.Context provided in `ctx` contains a span then the newly-created
49+
// span will be a child of that span, otherwise it will be a root span.
50+
func startSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
51+
name = appendPackageName(name)
52+
// TODO: Remove internalTrace upon experimental launch.
53+
if !isOTelTracingDevEnabled() {
54+
ctx = internalTrace.StartSpan(ctx, name)
55+
return ctx, nil
56+
}
57+
opts = append(opts, getCommonTraceOptions()...)
58+
ctx, span := tracer().Start(ctx, name, opts...)
59+
return ctx, span
60+
}
61+
62+
// endSpan retrieves the current span from ctx and completes the span.
63+
// If an error occurs, the error is recorded as an exception span event for this span,
64+
// and the span status is set in the form of a code and a description.
65+
func endSpan(ctx context.Context, err error) {
66+
// TODO: Remove internalTrace upon experimental launch.
67+
if !isOTelTracingDevEnabled() {
68+
internalTrace.EndSpan(ctx, err)
69+
} else {
70+
span := trace.SpanFromContext(ctx)
71+
if err != nil {
72+
span.SetStatus(otelcodes.Error, err.Error())
73+
span.RecordError(err)
74+
}
75+
span.End()
76+
}
77+
}
78+
79+
// getCommonTraceOptions includes the common attributes used for Cloud Trace adoption tracking.
80+
func getCommonTraceOptions() []trace.SpanStartOption {
81+
opts := []trace.SpanStartOption{
82+
trace.WithAttributes(
83+
attribute.String("gcp.client.version", internal.Version),
84+
attribute.String("gcp.client.repo", gcpClientRepo),
85+
attribute.String("gcp.client.artifact", gcpClientArtifact),
86+
),
87+
}
88+
return opts
89+
}
90+
91+
func appendPackageName(spanName string) string {
92+
return fmt.Sprintf("%s.%s", gcpClientArtifact, spanName)
93+
}

0 commit comments

Comments
 (0)