Skip to content

Commit

Permalink
initial implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Sergei Malafeev <sergeymalafeev@gmail.com>
  • Loading branch information
malafeev committed Jan 3, 2019
1 parent 82a588c commit 5f16a96
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 1 deletion.
12 changes: 12 additions & 0 deletions .gitignore
@@ -0,0 +1,12 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
47 changes: 46 additions & 1 deletion README.md
@@ -1 +1,46 @@
# go-aws
[![Apache-2.0 license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

# OpenTracing support for AWS SDK in Go

The `otaws` package makes it easy to add OpenTracing support for AWS SDK in Go.

## Installation

```
go get github.com/opentracing-contrib/go-aws
```

## Documentation

See the basic usage examples below and the [package documentation on
godoc.org](https://godoc.org/github.com/opentracing-contrib/go-aws).

## Usage

```go
// You must have some sort of OpenTracing Tracer instance on hand
var tracer opentracing.Tracer = ...

// Optionally set Tracer as global
opentracing.SetGlobalTracer(tracer)

// Create AWS Session
sess := session.NewSession(...)

// Create AWS service client e.g. DynamoDB client
dbCient := dynamodb.New(sess)

// Add OpenTracing handlers using global tracer
AddOTHandlers(dbClient.Client)

// Or specify tracer explicitly
AddOTHandlers(dbClient.Client, WithTracer(tracer))

// Call AWS client
result, err := dbClient.ListTables(&dynamodb.ListTablesInput{})

```

## License

[Apache 2.0 License](./LICENSE).
52 changes: 52 additions & 0 deletions handler.go
@@ -0,0 +1,52 @@
package otaws

import (
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/log"
"net/http"
)

func AddOTHandlers(cl *client.Client, opts ...Option) {
c := defaultConfig()
for _, opt := range opts {
opt(c)
}

handler := otHandler(c)
cl.Handlers.Build.PushFront(handler)
}

func otHandler(c *config) func(*request.Request) {
tracer := c.tracer

return func(r *request.Request) {
sp := tracer.StartSpan(r.Operation.Name)
ext.SpanKindRPCClient.Set(sp)
ext.Component.Set(sp, "go-aws")
ext.HTTPMethod.Set(sp, r.Operation.HTTPMethod)
ext.HTTPUrl.Set(sp, r.HTTPRequest.URL.String())
ext.PeerService.Set(sp, r.ClientInfo.ServiceName)

_ = inject(tracer, sp, r.HTTPRequest.Header)

r.Handlers.Complete.PushBack(func(req *request.Request) {
if req.HTTPResponse != nil {
ext.HTTPStatusCode.Set(sp, uint16(req.HTTPResponse.StatusCode))
} else {
ext.Error.Set(sp, true)
}
sp.Finish()
})

r.Handlers.Retry.PushBack(func(req *request.Request) {
sp.LogFields(log.String("event", "retry"))
})
}
}

func inject(tracer opentracing.Tracer, span opentracing.Span, header http.Header) error {
return tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(header))
}
141 changes: 141 additions & 0 deletions handler_test.go
@@ -0,0 +1,141 @@
package otaws

import (
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/awstesting/mock"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/mocktracer"
"strings"
"testing"
)

// Test requires running local instance of DynamoDB
func TestAWS(t *testing.T) {
tracer := mocktracer.New()
opentracing.InitGlobalTracer(tracer)

client := mock.NewMockClient(&aws.Config{
Region: aws.String("us-west-2"),
})

AddOTHandlers(client)

req := client.NewRequest(&request.Operation{
Name: "Test Operation",
HTTPMethod: "POST",
HTTPPath: "/foo/bar",
}, nil, nil)

err := req.Send()
if err != nil {
t.Fatal("Expected request to succeed but failed:", err)
}

spans := tracer.FinishedSpans()

if numSpans := len(spans); numSpans != 1 {
t.Fatalf("Expected 1 span but found %d spans", numSpans)
}

span := spans[0]

if span.OperationName != "Test Operation" {
t.Errorf("Expected span to have operation name 'Test Operation' but was '%s'", span.OperationName)
}

expectedTags := map[string]interface{}{
"span.kind": ext.SpanKindRPCClientEnum,
"component": "go-aws",
"http.method": "POST",
"http.status_code": uint16(200),
"peer.service": "Mock",
}

for tag, expected := range expectedTags {
if actual := span.Tag(tag); actual != expected {
t.Errorf("Expected tag '%s' to have value '%v' but was '%v'", tag, expected, actual)
}
}

url, ok := span.Tag("http.url").(string)
if !ok {
t.Errorf("Expected span to have tag 'http.url' of type string")
}
if !strings.HasSuffix(url, "/foo/bar") {
t.Error("Expected tag 'http.url' to end with '/foo/bar' but was", url)
}
}

func TestNilResponse(t *testing.T) {
tracer := mocktracer.New()
opentracing.InitGlobalTracer(tracer)

sess, err := session.NewSession(&aws.Config{
Region: aws.String("us-west-2"),
Credentials: credentials.NewCredentials(credentials.ErrorProvider{
Err: errors.New("error credentials for test"),
ProviderName: "test error provider",
}),
})
if err != nil {
t.Fatal("Failed to instantiate session:", err)
}

dbClient := dynamodb.New(sess)

AddOTHandlers(dbClient.Client)

_, err = dbClient.ListTables(&dynamodb.ListTablesInput{})
if err == nil {
t.Fatal("Expected error but request succeeded")
}

spans := tracer.FinishedSpans()
if len(spans) != 1 {
t.Fatalf("Expected 1 span but saw %d spans", len(spans))
}

errTag, ok := spans[0].Tag("error").(bool)
if !ok {
t.Fatal("Expected span to have an 'error' tag of type bool")
} else if errTag != true {
t.Fatal("Expected span's 'error' tag to be true but was false")
}
}

func TestNonGlobalTracer(t *testing.T) {
tracer := mocktracer.New()

client := mock.NewMockClient(&aws.Config{
Region: aws.String("us-west-2"),
})

AddOTHandlers(client, WithTracer(tracer))

req := client.NewRequest(&request.Operation{
Name: "Test Operation",
HTTPMethod: "POST",
}, nil, nil)

err := req.Send()
if err != nil {
t.Fatal("Expected request to succeed but failed:", err)
}

spans := tracer.FinishedSpans()

if numSpans := len(spans); numSpans != 1 {
t.Fatalf("Expected 1 span but found %d spans", numSpans)
}

span := spans[0]
if span.OperationName != "Test Operation" {
t.Errorf("Expected span to have operation name 'Test Operation' but was '%s'", span.OperationName)
}
}
21 changes: 21 additions & 0 deletions options.go
@@ -0,0 +1,21 @@
package otaws

import "github.com/opentracing/opentracing-go"

type Option func(*config)

func WithTracer(tracer opentracing.Tracer) Option {
return func(c *config) {
c.tracer = tracer
}
}

type config struct {
tracer opentracing.Tracer
}

func defaultConfig() *config {
return &config{
tracer: opentracing.GlobalTracer(),
}
}
5 changes: 5 additions & 0 deletions package.go
@@ -0,0 +1,5 @@
// Package otaws provides OpenTracing support for AWS SDK.
//
// See the README for simple usage examples:
// https://github.com/opentracing-contrib/go-aws/README.md
package otaws

0 comments on commit 5f16a96

Please sign in to comment.