Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contrib/google.golang.org/api: add google api integration #313

Merged
merged 9 commits into from
Sep 11, 2018
50 changes: 50 additions & 0 deletions contrib/google.golang.org/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Package api provides functions to trace the google.golang.org/api package.
package api // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/google.golang.org/api"

//go:generate go run make_endpoints.go

import (
"net/http"

"golang.org/x/oauth2/google"
"gopkg.in/DataDog/dd-trace-go.v1/contrib/google.golang.org/api/internal"
httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
)

// apiEndpoints are all of the defined endpoints for the Google API; it is populated
// by "go generate".
var apiEndpoints *internal.Tree

// NewClient creates a new oauth http client suitable for use with the google
// APIs with all requests traced automatically.
func NewClient(options ...Option) (*http.Client, error) {
cfg := newConfig(options...)
client, err := google.DefaultClient(cfg.ctx, cfg.scopes...)
if err != nil {
return nil, err
}
client.Transport = WrapRoundTripper(client.Transport, options...)
return client, nil
}

// WrapRoundTripper wraps a RoundTripper intended for interfacing with
// Google APIs and traces all requests.
func WrapRoundTripper(transport http.RoundTripper, options ...Option) http.RoundTripper {
cfg := newConfig(options...)
return httptrace.WrapRoundTripper(transport,
httptrace.WithBefore(func(req *http.Request, span ddtrace.Span) {
e, ok := apiEndpoints.Get(req.URL.Hostname(), req.Method, req.URL.Path)
if ok {
span.SetTag(ext.ServiceName, e.ServiceName)
span.SetTag(ext.ResourceName, e.ResourceName)
} else {
span.SetTag(ext.ServiceName, "google")
span.SetTag(ext.ResourceName, req.Method+" "+req.URL.Hostname())
}
if cfg.serviceName != "" {
span.SetTag(ext.ServiceName, cfg.serviceName)
}
}))
}
106 changes: 106 additions & 0 deletions contrib/google.golang.org/api/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package api

import (
"io/ioutil"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/assert"
books "google.golang.org/api/books/v1"
civicinfo "google.golang.org/api/civicinfo/v2"
urlshortener "google.golang.org/api/urlshortener/v1"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer"
)

type roundTripperFunc func(*http.Request) (*http.Response, error)

func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}

var badRequestTransport roundTripperFunc = func(req *http.Request) (*http.Response, error) {
res := &http.Response{
Header: make(http.Header),
Request: req,
StatusCode: http.StatusBadRequest,
Body: ioutil.NopCloser(strings.NewReader("")),
}
return res, nil
}

func TestBooks(t *testing.T) {
mt := mocktracer.Start()
defer mt.Stop()

svc, err := books.New(&http.Client{
Transport: WrapRoundTripper(badRequestTransport),
})
assert.NoError(t, err)
svc.Bookshelves.
List("montana.banana").
Do()

spans := mt.FinishedSpans()
assert.Len(t, spans, 1)

s0 := spans[0]
assert.Equal(t, "http.request", s0.OperationName())
assert.Equal(t, "http", s0.Tag(ext.SpanType))
assert.Equal(t, "google.books", s0.Tag(ext.ServiceName))
assert.Equal(t, "books.bookshelves.list", s0.Tag(ext.ResourceName))
assert.Equal(t, "400", s0.Tag(ext.HTTPCode))
assert.Equal(t, "GET", s0.Tag(ext.HTTPMethod))
assert.Equal(t, "/books/v1/users/montana.banana/bookshelves", s0.Tag(ext.HTTPURL))
}

func TestCivicInfo(t *testing.T) {
mt := mocktracer.Start()
defer mt.Stop()

svc, err := civicinfo.New(&http.Client{
Transport: WrapRoundTripper(badRequestTransport),
})
assert.NoError(t, err)
svc.Representatives.
RepresentativeInfoByAddress(&civicinfo.RepresentativeInfoRequest{}).
Do()

spans := mt.FinishedSpans()
assert.Len(t, spans, 1)

s0 := spans[0]
assert.Equal(t, "http.request", s0.OperationName())
assert.Equal(t, "http", s0.Tag(ext.SpanType))
assert.Equal(t, "google.civicinfo", s0.Tag(ext.ServiceName))
assert.Equal(t, "civicinfo.representatives.representativeInfoByAddress", s0.Tag(ext.ResourceName))
assert.Equal(t, "400", s0.Tag(ext.HTTPCode))
assert.Equal(t, "GET", s0.Tag(ext.HTTPMethod))
assert.Equal(t, "/civicinfo/v2/representatives", s0.Tag(ext.HTTPURL))
}

func TestURLShortener(t *testing.T) {
mt := mocktracer.Start()
defer mt.Stop()

svc, err := urlshortener.New(&http.Client{
Transport: WrapRoundTripper(badRequestTransport),
})
assert.NoError(t, err)
svc.Url.
List().
Do()

spans := mt.FinishedSpans()
assert.Len(t, spans, 1)

s0 := spans[0]
assert.Equal(t, "http.request", s0.OperationName())
assert.Equal(t, "http", s0.Tag(ext.SpanType))
assert.Equal(t, "google.urlshortener", s0.Tag(ext.ServiceName))
assert.Equal(t, "urlshortener.url.list", s0.Tag(ext.ResourceName))
assert.Equal(t, "400", s0.Tag(ext.HTTPCode))
assert.Equal(t, "GET", s0.Tag(ext.HTTPMethod))
assert.Equal(t, "/urlshortener/v1/url/history", s0.Tag(ext.HTTPURL))
}
Loading