Skip to content

Commit

Permalink
otelaws: adding dynamodb attributes (#1582)
Browse files Browse the repository at this point in the history
* adding dynamodb attributes

* applying suggestions

* allowing users to use the default attribute setter in addition to their own

* making setting attributesetters more flexible

* Apply suggestions from code review

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

* Revert "Apply suggestions from code review"

This reverts commit 05c3847.

* Apply suggestions from code review

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

* adding comments for exported functions and types

* Apply suggestions from code review

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

* fixing test and applying suggestions

* fixing checks

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
  • Loading branch information
3 people committed Feb 25, 2022
1 parent f7fe163 commit 4be24de
Show file tree
Hide file tree
Showing 12 changed files with 805 additions and 7 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,11 +8,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]


### Added

- DynamoDB spans created with the `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` package will now have the appropriate database attributes added for the operation being performed.
These attributes are detected automatically, but it is also now possible to provide a custom function to set attributes using `WithAttributeSetter`. (#1582)

### Fixed

- Fix the `echo` middleware by using `SpanKind.SERVER` when deciding the `SpanStatus`.
This makes `4xx` response codes to not be an error anymore. (#1848)


## [1.4.0/0.29.0] - 2022-02-14

### Added
Expand Down
Expand Up @@ -17,8 +17,12 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0 h1:3ADoioDMOtF4uiK59vC
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0/go.mod h1:BsCSJHx5DnDXIrOcqB8KN1/B+hXLG/bi4Y6Vjcx/x9E=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 h1:ixotxbfTCFpqbuwFv/RcZwyzhkxPSYDYEMcj4niB5Uk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5/go.mod h1:R3sWUqPcfXSiF/LSFJhjyJmpg9uV6yP2yv3YZZjldVI=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.13.0 h1:Xlmdkxi8WcIwX5Cy9BS+scWcmvARw8pg0bi7kaeERUY=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.13.0/go.mod h1:eNvoR4P1XQN7xElmYA8cWeFENLY3pfsj/5nFRItzXnA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.7.0 h1:F1diQIOkNn8jcez4173r+PLPdkWK7chy74r3fKpDrLI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.7.0/go.mod h1:8ctElVINyp+SjhoZZceUAZw78glZH6R8ox5MVNu5j2s=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.5.0 h1:tzVhIPr/psp8Gb2Blst9mq6HklkhAGPqv2eaiSq6yoU=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.5.0/go.mod h1:u0rI/Mm45zCJe86J5kvPfG7pYzkVZzNjEkoTVbfOYE8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 h1:4QAOB3KrvI1ApJK14sliGr3Ie2pjyvNypn/lfzDHfUw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0/go.mod h1:K/qPe6AP2TGYv4l6n7c88zh9jWBDf6nHhvg1fx/EWfU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.11.0 h1:XAe+PDnaBELHr25qaJKfB415V4CKFWE8H+prUreql8k=
Expand Down Expand Up @@ -46,7 +50,9 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -78,6 +84,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
Expand Down
Expand Up @@ -14,7 +14,14 @@

package otelaws

import "go.opentelemetry.io/otel/attribute"
import (
"context"

v2Middleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/smithy-go/middleware"

"go.opentelemetry.io/otel/attribute"
)

// AWS attributes.
const (
Expand All @@ -24,6 +31,10 @@ const (
RequestIDKey attribute.Key = "aws.request_id"
)

var servicemap map[string]AttributeSetter = map[string]AttributeSetter{
"dynamodb": DynamoDBAttributeSetter,
}

func OperationAttr(operation string) attribute.KeyValue {
return OperationKey.String(operation)
}
Expand All @@ -39,3 +50,16 @@ func ServiceAttr(service string) attribute.KeyValue {
func RequestIDAttr(requestID string) attribute.KeyValue {
return RequestIDKey.String(requestID)
}

// DefaultAttributeSetter checks to see if there are service specific attributes available to set for the AWS service.
// If there are service specific attributes available then they will be included.
func DefaultAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue {

serviceID := v2Middleware.GetServiceID(ctx)

if fn, ok := servicemap[serviceID]; ok {
return fn(ctx, in)
}

return []attribute.KeyValue{}
}
29 changes: 24 additions & 5 deletions instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/aws.go
Expand Up @@ -23,6 +23,7 @@ import (
smithyhttp "github.com/aws/smithy-go/transport/http"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"go.opentelemetry.io/otel/trace"
Expand All @@ -34,8 +35,12 @@ const (

type spanTimestampKey struct{}

// AttributeSetter returns an array of KeyValue pairs, it can be used to set custom attributes.
type AttributeSetter func(context.Context, middleware.InitializeInput) []attribute.KeyValue

type otelMiddlewares struct {
tracer trace.Tracer
tracer trace.Tracer
attributeSetter []AttributeSetter
}

func (m otelMiddlewares) initializeMiddlewareBefore(stack *middleware.Stack) error {
Expand All @@ -55,12 +60,20 @@ func (m otelMiddlewares) initializeMiddlewareAfter(stack *middleware.Stack) erro
out middleware.InitializeOutput, metadata middleware.Metadata, err error) {

serviceID := v2Middleware.GetServiceID(ctx)

attributes := []attribute.KeyValue{
ServiceAttr(serviceID),
RegionAttr(v2Middleware.GetRegion(ctx)),
OperationAttr(v2Middleware.GetOperationName(ctx)),
}
for _, setter := range m.attributeSetter {
attributes = append(attributes, setter(ctx, in)...)
}

ctx, span := m.tracer.Start(ctx, serviceID,
trace.WithTimestamp(ctx.Value(spanTimestampKey{}).(time.Time)),
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(ServiceAttr(serviceID),
RegionAttr(v2Middleware.GetRegion(ctx)),
OperationAttr(v2Middleware.GetOperationName(ctx))),
trace.WithAttributes(attributes...),
)
defer span.End()

Expand Down Expand Up @@ -110,7 +123,13 @@ func AppendMiddlewares(apiOptions *[]func(*middleware.Stack) error, opts ...Opti
opt.apply(&cfg)
}

if cfg.AttributeSetter == nil {
cfg.AttributeSetter = []AttributeSetter{DefaultAttributeSetter}
}

m := otelMiddlewares{tracer: cfg.TracerProvider.Tracer(tracerName,
trace.WithInstrumentationVersion(SemVersion()))}
trace.WithInstrumentationVersion(SemVersion())),
attributeSetter: cfg.AttributeSetter}
*apiOptions = append(*apiOptions, m.initializeMiddlewareBefore, m.initializeMiddlewareAfter, m.deserializeMiddleware)

}
11 changes: 10 additions & 1 deletion instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/config.go
Expand Up @@ -19,7 +19,8 @@ import (
)

type config struct {
TracerProvider trace.TracerProvider
TracerProvider trace.TracerProvider
AttributeSetter []AttributeSetter
}

// Option applies an option value.
Expand All @@ -44,3 +45,11 @@ func WithTracerProvider(provider trace.TracerProvider) Option {
}
})
}

// WithAttributeSetter specifies an attribute setter function for setting service specific attributes.
// If none is specified, the service will be determined by the DefaultAttributeSetter function and the corresponding attributes will be included.
func WithAttributeSetter(attributesetters ...AttributeSetter) Option {
return optionFunc(func(cfg *config) {
cfg.AttributeSetter = append(cfg.AttributeSetter, attributesetters...)
})
}
@@ -0,0 +1,184 @@
// Copyright The OpenTelemetry 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 otelaws

import (
"context"
"encoding/json"

"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/smithy-go/middleware"

"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
)

// DynamoDBAttributeSetter sets DynamoDB specific attributes depending on the the DynamoDB operation being performed.
func DynamoDBAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue {

dynamodbAttributes := []attribute.KeyValue{semconv.DBSystemKey.String("dynamodb")}

switch v := in.Parameters.(type) {
case *dynamodb.GetItemInput:

dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

if v.ConsistentRead != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentReadKey.Bool(*v.ConsistentRead))
}

if v.ProjectionExpression != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjectionKey.String(*v.ProjectionExpression))
}

case *dynamodb.BatchGetItemInput:
var tableNames []string
for k := range v.RequestItems {
tableNames = append(tableNames, k)
}
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.StringSlice(tableNames))

case *dynamodb.BatchWriteItemInput:
var tableNames []string
for k := range v.RequestItems {
tableNames = append(tableNames, k)
}
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.StringSlice(tableNames))

case *dynamodb.CreateTableInput:
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

if v.GlobalSecondaryIndexes != nil {
globalindexes, _ := json.Marshal(v.GlobalSecondaryIndexes)
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBGlobalSecondaryIndexesKey.String(string(globalindexes)))
}

if v.LocalSecondaryIndexes != nil {
localindexes, _ := json.Marshal(v.LocalSecondaryIndexes)
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLocalSecondaryIndexesKey.String(string(localindexes)))
}

if v.ProvisionedThroughput != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedReadCapacityKey.Int64(*v.ProvisionedThroughput.ReadCapacityUnits))
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedWriteCapacityKey.Int64(*v.ProvisionedThroughput.WriteCapacityUnits))
}

case *dynamodb.DeleteItemInput:
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

case *dynamodb.DeleteTableInput:
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

case *dynamodb.DescribeTableInput:
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

case *dynamodb.ListTablesInput:

if v.ExclusiveStartTableName != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBExclusiveStartTableKey.String(*v.ExclusiveStartTableName))
}

if v.Limit != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimitKey.Int(int(*v.Limit)))
}

case *dynamodb.PutItemInput:

dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

case *dynamodb.QueryInput:

dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

if v.ConsistentRead != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentReadKey.Bool(*v.ConsistentRead))
}

if v.IndexName != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBIndexNameKey.String(*v.IndexName))
}

if v.Limit != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimitKey.Int(int(*v.Limit)))
}

if v.ScanIndexForward != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBScanForwardKey.Bool(*v.ScanIndexForward))
}

if v.ProjectionExpression != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjectionKey.String(*v.ProjectionExpression))
}

dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSelectKey.String(string(v.Select)))

case *dynamodb.ScanInput:

dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

if v.ConsistentRead != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentReadKey.Bool(*v.ConsistentRead))
}

if v.IndexName != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBIndexNameKey.String(*v.IndexName))
}

if v.Limit != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimitKey.Int(int(*v.Limit)))
}

if v.ProjectionExpression != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjectionKey.String(*v.ProjectionExpression))
}

dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSelectKey.String(string(v.Select)))

if v.Segment != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSegmentKey.Int(int(*v.Segment)))
}

if v.TotalSegments != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTotalSegmentsKey.Int(int(*v.TotalSegments)))
}

case *dynamodb.UpdateItemInput:

dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

case *dynamodb.UpdateTableInput:

dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNamesKey.String(*v.TableName))

if v.AttributeDefinitions != nil {
attributedefinitions, _ := json.Marshal(v.AttributeDefinitions)
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBAttributeDefinitionsKey.String(string(attributedefinitions)))
}

if v.GlobalSecondaryIndexUpdates != nil {
globalsecondaryindexupdates, _ := json.Marshal(v.GlobalSecondaryIndexUpdates)
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBGlobalSecondaryIndexUpdatesKey.String(string(globalsecondaryindexupdates)))
}

if v.ProvisionedThroughput != nil {
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedReadCapacityKey.Int64(*v.ProvisionedThroughput.ReadCapacityUnits))
dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedWriteCapacityKey.Int64(*v.ProvisionedThroughput.WriteCapacityUnits))
}

}

return dynamodbAttributes

}

0 comments on commit 4be24de

Please sign in to comment.