Skip to content

Commit 6e05dfc

Browse files
committed
Add AttributeBuilder, which will extend the AttributeSetter contract with output value. Thanks to that, users can instrument attributes based on the AWS SDK responses.
1 parent 00786cc commit 6e05dfc

File tree

12 files changed

+223
-49
lines changed

12 files changed

+223
-49
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1111
### Added
1212

1313
- Generate server metrics with semantic conventions v1.26 in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` when `OTEL_SEMCONV_STABILITY_OPT_IN` is set to `http/dup`. (#6411)
14+
- `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` change: Added new `AttributeBuilder` interface to support adding attributes based on input and output instead of input only. It is also now possible to provide a custom function to set attributes using `WithAttributeBuilder`
15+
16+
### Changed
17+
18+
- `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` change: Use of `AttributeBuilder` interface instead of `AttributeSetter`. Previous usage of `AttributeSetter` were backward compatibly changed to use `AttributeBuilder` interface.
19+
- `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` change: Attributes are added to the span after the execution instead of before the execution of AWS SDK
20+
1421

1522
## [1.33.0/0.58.0/0.27.0/0.13.0/0.8.0/0.6.0/0.5.0] - 2024-12-12
1623

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/attributes.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ const (
2323
AWSSystemVal string = "aws-api"
2424
)
2525

26-
var servicemap = map[string]AttributeSetter{
27-
dynamodb.ServiceID: DynamoDBAttributeSetter,
28-
sqs.ServiceID: SQSAttributeSetter,
29-
sns.ServiceID: SNSAttributeSetter,
26+
var servicemap = map[string]AttributeBuilder{
27+
dynamodb.ServiceID: DynamoDBAttributeBuilder,
28+
sqs.ServiceID: SQSAttributeBuilder,
29+
sns.ServiceID: SNSAttributeBuilder,
3030
}
3131

3232
// SystemAttr return the AWS RPC system attribute.
@@ -56,11 +56,18 @@ func RequestIDAttr(requestID string) attribute.KeyValue {
5656

5757
// DefaultAttributeSetter checks to see if there are service specific attributes available to set for the AWS service.
5858
// If there are service specific attributes available then they will be included.
59+
// Deprecated: Kept for backward compatibility, use DefaultAttributeBuilder instead. This will be removed in a future release.
5960
func DefaultAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue {
61+
return DefaultAttributeBuilder(ctx, in, middleware.InitializeOutput{})
62+
}
63+
64+
// DefaultAttributeBuilder checks to see if there are service specific attributes available to set for the AWS service.
65+
// If there are service specific attributes available then they will be included.
66+
func DefaultAttributeBuilder(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) []attribute.KeyValue {
6067
serviceID := v2Middleware.GetServiceID(ctx)
6168

6269
if fn, ok := servicemap[serviceID]; ok {
63-
return fn(ctx, in)
70+
return fn(ctx, in, out)
6471
}
6572

6673
return []attribute.KeyValue{}

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/attributes_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
package otelaws
55

66
import (
7+
"context"
78
"testing"
89

10+
awsMiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
11+
"github.com/aws/aws-sdk-go-v2/service/sqs"
12+
"github.com/aws/smithy-go/middleware"
913
"github.com/stretchr/testify/assert"
1014

1115
"go.opentelemetry.io/otel/attribute"
@@ -40,3 +44,29 @@ func TestSystemAttribute(t *testing.T) {
4044
attr := SystemAttr()
4145
assert.Equal(t, semconv.RPCSystemKey.String("aws-api"), attr)
4246
}
47+
48+
func TestDefaultAttributeBuilder_ShouldReturnNoAttributesOnNotSupportedService(t *testing.T) {
49+
// GIVEN
50+
testCtx := awsMiddleware.SetServiceID(context.TODO(), "not-implemented-service")
51+
52+
// WHEN
53+
attr := DefaultAttributeBuilder(testCtx, middleware.InitializeInput{}, middleware.InitializeOutput{})
54+
assert.Empty(t, attr)
55+
}
56+
57+
func TestDefaultAttributeBuilder_ShouldReturnAttributesOnSupportedService(t *testing.T) {
58+
// GIVEN
59+
testCtx := awsMiddleware.SetServiceID(context.TODO(), sqs.ServiceID)
60+
testQueueURL := "test-queue-url"
61+
62+
// WHEN
63+
attr := DefaultAttributeBuilder(testCtx, middleware.InitializeInput{
64+
Parameters: &sqs.SendMessageInput{
65+
QueueUrl: &testQueueURL,
66+
},
67+
}, middleware.InitializeOutput{})
68+
assert.ElementsMatch(t, []attribute.KeyValue{
69+
semconv.MessagingSystem("AmazonSQS"),
70+
semconv.NetPeerName(testQueueURL),
71+
}, attr)
72+
}

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/aws.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@ const (
2727
type spanTimestampKey struct{}
2828

2929
// AttributeSetter returns an array of KeyValue pairs, it can be used to set custom attributes.
30+
// Deprecated: Kept for backward compatibility, use AttributeBuilder instead. This will be removed in a future release.
3031
type AttributeSetter func(context.Context, middleware.InitializeInput) []attribute.KeyValue
3132

33+
// AttributeBuilder returns an array of KeyValue pairs, it can be used to set custom attributes.
34+
type AttributeBuilder func(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) []attribute.KeyValue
35+
3236
type otelMiddlewares struct {
33-
tracer trace.Tracer
34-
propagator propagation.TextMapPropagator
35-
attributeSetter []AttributeSetter
37+
tracer trace.Tracer
38+
propagator propagation.TextMapPropagator
39+
attributeBuilders []AttributeBuilder
3640
}
3741

3842
func (m otelMiddlewares) initializeMiddlewareBefore(stack *middleware.Stack) error {
@@ -61,9 +65,6 @@ func (m otelMiddlewares) initializeMiddlewareAfter(stack *middleware.Stack) erro
6165
RegionAttr(region),
6266
OperationAttr(operation),
6367
}
64-
for _, setter := range m.attributeSetter {
65-
attributes = append(attributes, setter(ctx, in)...)
66-
}
6768

6869
ctx, span := m.tracer.Start(ctx, spanName(serviceID, operation),
6970
trace.WithTimestamp(ctx.Value(spanTimestampKey{}).(time.Time)),
@@ -73,6 +74,7 @@ func (m otelMiddlewares) initializeMiddlewareAfter(stack *middleware.Stack) erro
7374
defer span.End()
7475

7576
out, metadata, err = next.HandleInitialize(ctx, in)
77+
span.SetAttributes(m.buildAttributes(ctx, in, out)...)
7678
if err != nil {
7779
span.RecordError(err)
7880
span.SetStatus(codes.Error, err.Error())
@@ -125,6 +127,14 @@ func (m otelMiddlewares) deserializeMiddleware(stack *middleware.Stack) error {
125127
middleware.Before)
126128
}
127129

130+
func (m otelMiddlewares) buildAttributes(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) (attributes []attribute.KeyValue) {
131+
for _, builder := range m.attributeBuilders {
132+
attributes = append(attributes, builder(ctx, in, out)...)
133+
}
134+
135+
return attributes
136+
}
137+
128138
func spanName(serviceID, operation string) string {
129139
spanName := serviceID
130140
if operation != "" {
@@ -145,15 +155,15 @@ func AppendMiddlewares(apiOptions *[]func(*middleware.Stack) error, opts ...Opti
145155
opt.apply(&cfg)
146156
}
147157

148-
if cfg.AttributeSetter == nil {
149-
cfg.AttributeSetter = []AttributeSetter{DefaultAttributeSetter}
158+
if cfg.AttributeBuilders == nil {
159+
cfg.AttributeBuilders = []AttributeBuilder{DefaultAttributeBuilder}
150160
}
151161

152162
m := otelMiddlewares{
153163
tracer: cfg.TracerProvider.Tracer(ScopeName,
154164
trace.WithInstrumentationVersion(Version())),
155-
propagator: cfg.TextMapPropagator,
156-
attributeSetter: cfg.AttributeSetter,
165+
propagator: cfg.TextMapPropagator,
166+
attributeBuilders: cfg.AttributeBuilders,
157167
}
158168
*apiOptions = append(*apiOptions, m.initializeMiddlewareBefore, m.initializeMiddlewareAfter, m.finalizeMiddlewareAfter, m.deserializeMiddleware)
159169
}

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/config.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@
44
package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws"
55

66
import (
7+
"context"
8+
9+
"github.com/aws/smithy-go/middleware"
10+
11+
"go.opentelemetry.io/otel/attribute"
712
"go.opentelemetry.io/otel/propagation"
813
"go.opentelemetry.io/otel/trace"
914
)
1015

1116
type config struct {
1217
TracerProvider trace.TracerProvider
1318
TextMapPropagator propagation.TextMapPropagator
14-
AttributeSetter []AttributeSetter
19+
AttributeBuilders []AttributeBuilder
1520
}
1621

1722
// Option applies an option value.
@@ -48,9 +53,22 @@ func WithTextMapPropagator(propagator propagation.TextMapPropagator) Option {
4853
}
4954

5055
// WithAttributeSetter specifies an attribute setter function for setting service specific attributes.
51-
// If none is specified, the service will be determined by the DefaultAttributeSetter function and the corresponding attributes will be included.
56+
// If none is specified, the service will be determined by the DefaultAttributeBuilder function and the corresponding attributes will be included.
5257
func WithAttributeSetter(attributesetters ...AttributeSetter) Option {
58+
var attributeBuilders []AttributeBuilder
59+
for _, setter := range attributesetters {
60+
attributeBuilders = append(attributeBuilders, func(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) []attribute.KeyValue {
61+
return setter(ctx, in)
62+
})
63+
}
64+
65+
return WithAttributeBuilder(attributeBuilders...)
66+
}
67+
68+
// WithAttributeBuilder specifies an attribute setter function for setting service specific attributes.
69+
// If none is specified, the service will be determined by the DefaultAttributeBuilder function and the corresponding attributes will be included.
70+
func WithAttributeBuilder(attributeBuilders ...AttributeBuilder) Option {
5371
return optionFunc(func(cfg *config) {
54-
cfg.AttributeSetter = append(cfg.AttributeSetter, attributesetters...)
72+
cfg.AttributeBuilders = append(cfg.AttributeBuilders, attributeBuilders...)
5573
})
5674
}

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/dynamodbattributes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ import (
1515
)
1616

1717
// DynamoDBAttributeSetter sets DynamoDB specific attributes depending on the DynamoDB operation being performed.
18+
// Deprecated: Kept for backward compatibility, use DynamoDBAttributeBuilder instead. This will be removed in a future release.
1819
func DynamoDBAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue {
20+
return DynamoDBAttributeBuilder(ctx, in, middleware.InitializeOutput{})
21+
}
22+
23+
// DynamoDBAttributeBuilder sets DynamoDB specific attributes depending on the DynamoDB operation being performed.
24+
func DynamoDBAttributeBuilder(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) []attribute.KeyValue {
1925
dynamodbAttributes := []attribute.KeyValue{semconv.DBSystemDynamoDB}
2026

2127
switch v := in.Parameters.(type) {

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/dynamodbattributes_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestDynamodbTagsBatchGetItemInput(t *testing.T) {
3131
},
3232
}
3333

34-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
34+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
3535

3636
assert.Contains(t, attributes, attribute.StringSlice("aws.dynamodb.table_names", []string{"table1"}))
3737
}
@@ -60,7 +60,7 @@ func TestDynamodbTagsBatchWriteItemInput(t *testing.T) {
6060
},
6161
}
6262

63-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
63+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
6464

6565
assert.Contains(t, attributes, attribute.StringSlice("aws.dynamodb.table_names", []string{"table1"}))
6666
}
@@ -114,7 +114,7 @@ func TestDynamodbTagsCreateTableInput(t *testing.T) {
114114
},
115115
}
116116

117-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
117+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
118118

119119
assert.Contains(t, attributes, attribute.StringSlice(
120120
"aws.dynamodb.table_names", []string{"table1"},
@@ -144,7 +144,7 @@ func TestDynamodbTagsDeleteItemInput(t *testing.T) {
144144
TableName: aws.String("table1"),
145145
},
146146
}
147-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
147+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
148148

149149
assert.Contains(t, attributes, attribute.StringSlice(
150150
"aws.dynamodb.table_names", []string{"table1"},
@@ -157,7 +157,7 @@ func TestDynamodbTagsDeleteTableInput(t *testing.T) {
157157
TableName: aws.String("table1"),
158158
},
159159
}
160-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
160+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
161161

162162
assert.Contains(t, attributes, attribute.StringSlice(
163163
"aws.dynamodb.table_names", []string{"table1"},
@@ -170,7 +170,7 @@ func TestDynamodbTagsDescribeTableInput(t *testing.T) {
170170
TableName: aws.String("table1"),
171171
},
172172
}
173-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
173+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
174174

175175
assert.Contains(t, attributes, attribute.StringSlice(
176176
"aws.dynamodb.table_names", []string{"table1"},
@@ -184,7 +184,7 @@ func TestDynamodbTagsListTablesInput(t *testing.T) {
184184
Limit: aws.Int32(10),
185185
},
186186
}
187-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
187+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
188188

189189
assert.Contains(t, attributes, attribute.String("aws.dynamodb.exclusive_start_table", "table1"))
190190
assert.Contains(t, attributes, attribute.Int("aws.dynamodb.limit", 10))
@@ -202,7 +202,7 @@ func TestDynamodbTagsPutItemInput(t *testing.T) {
202202
},
203203
}
204204

205-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
205+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
206206

207207
assert.Contains(t, attributes, attribute.StringSlice(
208208
"aws.dynamodb.table_names", []string{"table1"},
@@ -230,7 +230,7 @@ func TestDynamodbTagsQueryInput(t *testing.T) {
230230
},
231231
}
232232

233-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
233+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
234234

235235
assert.Contains(t, attributes, attribute.StringSlice(
236236
"aws.dynamodb.table_names", []string{"table1"},
@@ -257,7 +257,7 @@ func TestDynamodbTagsScanInput(t *testing.T) {
257257
},
258258
}
259259

260-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
260+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
261261

262262
assert.Contains(t, attributes, attribute.StringSlice(
263263
"aws.dynamodb.table_names", []string{"my-table"},
@@ -285,7 +285,7 @@ func TestDynamodbTagsUpdateItemInput(t *testing.T) {
285285
},
286286
}
287287

288-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
288+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
289289

290290
assert.Contains(t, attributes, attribute.StringSlice(
291291
"aws.dynamodb.table_names", []string{"my-table"},
@@ -326,7 +326,7 @@ func TestDynamodbTagsUpdateTableInput(t *testing.T) {
326326
},
327327
}
328328

329-
attributes := DynamoDBAttributeSetter(context.TODO(), input)
329+
attributes := DynamoDBAttributeBuilder(context.TODO(), input, middleware.InitializeOutput{})
330330

331331
assert.Contains(t, attributes, attribute.StringSlice(
332332
"aws.dynamodb.table_names", []string{"my-table"},

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ import (
1515
)
1616

1717
// SNSAttributeSetter sets SNS specific attributes depending on the SNS operation is being performed.
18+
// Deprecated: Kept for backward compatibility, use SNSAttributeBuilder instead. This will be removed in a future release.
1819
func SNSAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue {
20+
return SNSAttributeBuilder(ctx, in, middleware.InitializeOutput{})
21+
}
22+
23+
// SNSAttributeBuilder sets SNS specific attributes depending on the SNS operation is being performed.
24+
func SNSAttributeBuilder(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) []attribute.KeyValue {
1925
snsAttributes := []attribute.KeyValue{semconv.MessagingSystemKey.String("aws_sns")}
2026

2127
switch v := in.Parameters.(type) {

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestPublishInput(t *testing.T) {
2323
},
2424
}
2525

26-
attributes := SNSAttributeSetter(context.Background(), input)
26+
attributes := SNSAttributeBuilder(context.Background(), input, middleware.InitializeOutput{})
2727

2828
assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns"))
2929
assert.Contains(t, attributes, semconv.MessagingDestinationName("my-topic"))
@@ -36,7 +36,7 @@ func TestPublishInputWithNoDestination(t *testing.T) {
3636
Parameters: &sns.PublishInput{},
3737
}
3838

39-
attributes := SNSAttributeSetter(context.Background(), input)
39+
attributes := SNSAttributeBuilder(context.Background(), input, middleware.InitializeOutput{})
4040

4141
assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns"))
4242
assert.Contains(t, attributes, semconv.MessagingDestinationName(""))
@@ -52,7 +52,7 @@ func TestPublishBatchInput(t *testing.T) {
5252
},
5353
}
5454

55-
attributes := SNSAttributeSetter(context.Background(), input)
55+
attributes := SNSAttributeBuilder(context.Background(), input, middleware.InitializeOutput{})
5656

5757
assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns"))
5858
assert.Contains(t, attributes, semconv.MessagingDestinationName("my-topic-batch"))

instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/sqsattributes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ import (
1414
)
1515

1616
// SQSAttributeSetter sets SQS specific attributes depending on the SQS operation being performed.
17+
// Deprecated: Kept for backward compatibility, use SQSAttributeBuilder instead. This will be removed in a future release.
1718
func SQSAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue {
19+
return SQSAttributeBuilder(ctx, in, middleware.InitializeOutput{})
20+
}
21+
22+
// SQSAttributeBuilder sets SQS specific attributes depending on the SQS operation being performed.
23+
func SQSAttributeBuilder(ctx context.Context, in middleware.InitializeInput, out middleware.InitializeOutput) []attribute.KeyValue {
1824
sqsAttributes := []attribute.KeyValue{semconv.MessagingSystem("AmazonSQS")}
1925

2026
key := semconv.NetPeerNameKey

0 commit comments

Comments
 (0)