plugin/etcdv3: Add etcd v3 plugin (#1702)
* Update dependencies and add etcdv3 client * Update etcd plugin to support etcd v3 clients Fixes #341
This commit is contained in:
parent
f3afd70021
commit
6fe27d99be
10327 changed files with 4196998 additions and 82 deletions
.travis.ymlGopkg.lockGopkg.toml
plugin/etcd
test
vendor/github.com/DataDog/dd-trace-go
contrib
README.md
database/sql
example_test.gomysql_test.goparse.goparse_test.go
parsedsn
pq_test.gosql.gosqltest
sqlutils
utils.goutils_test.gogaryburd/redigo
gin-gonic/gin
go-redis/redis
gocql/gocql
google.golang.org
grpc.v12
grpc
gorilla/mux
jmoiron/sqlx
net/http
olivere/elastic
tasks
tracer/contrib
|
@ -15,10 +15,10 @@ git:
|
||||||
depth: 3
|
depth: 3
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- TEST_TYPE=coverage ETCD_VERSION=2.3.1
|
- TEST_TYPE=coverage ETCD_VERSION=3.3.8
|
||||||
- TEST_TYPE=integration ETCD_VERSION=2.3.1
|
- TEST_TYPE=integration ETCD_VERSION=3.3.8
|
||||||
- TEST_TYPE=core ETCD_VERSION=2.3.1
|
- TEST_TYPE=core ETCD_VERSION=3.3.8
|
||||||
- TEST_TYPE=plugin ETCD_VERSION=2.3.1
|
- TEST_TYPE=plugin ETCD_VERSION=3.3.8
|
||||||
|
|
||||||
# In the Travis VM-based build environment, IPv6 networking is not
|
# In the Travis VM-based build environment, IPv6 networking is not
|
||||||
# enabled by default. The sysctl operations below enable IPv6.
|
# enabled by default. The sysctl operations below enable IPv6.
|
||||||
|
|
10
Gopkg.lock
generated
10
Gopkg.lock
generated
|
@ -60,7 +60,12 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/coreos/etcd"
|
name = "github.com/coreos/etcd"
|
||||||
packages = [
|
packages = [
|
||||||
|
"auth/authpb",
|
||||||
"client",
|
"client",
|
||||||
|
"clientv3",
|
||||||
|
"etcdserver/api/v3rpc/rpctypes",
|
||||||
|
"etcdserver/etcdserverpb",
|
||||||
|
"mvcc/mvccpb",
|
||||||
"pkg/pathutil",
|
"pkg/pathutil",
|
||||||
"pkg/srv",
|
"pkg/srv",
|
||||||
"pkg/types",
|
"pkg/types",
|
||||||
|
@ -132,7 +137,9 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/gogo/protobuf"
|
name = "github.com/gogo/protobuf"
|
||||||
packages = [
|
packages = [
|
||||||
|
"gogoproto",
|
||||||
"proto",
|
"proto",
|
||||||
|
"protoc-gen-gogo/descriptor",
|
||||||
"sortkeys"
|
"sortkeys"
|
||||||
]
|
]
|
||||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||||
|
@ -391,6 +398,7 @@
|
||||||
"encoding/proto",
|
"encoding/proto",
|
||||||
"grpclb/grpc_lb_v1/messages",
|
"grpclb/grpc_lb_v1/messages",
|
||||||
"grpclog",
|
"grpclog",
|
||||||
|
"health/grpc_health_v1",
|
||||||
"internal",
|
"internal",
|
||||||
"keepalive",
|
"keepalive",
|
||||||
"metadata",
|
"metadata",
|
||||||
|
@ -560,6 +568,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "dbbdcbcd4c0e11f040230e43a145f113ed7e67ff2c52b2a5830e117c16a23630"
|
inputs-digest = "435926fcc83a4f1a93fd257248f2b1256eaa5a212159b07743408b8cafdbffff"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -26,3 +26,7 @@ ignored = [
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/ugorji/go"
|
name = "github.com/ugorji/go"
|
||||||
revision = "f3cacc17c85ecb7f1b6a9e373ee85d1480919868"
|
revision = "f3cacc17c85ecb7f1b6a9e373ee85d1480919868"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/coreos/etcd"
|
||||||
|
version = "3.3.5"
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
## Name
|
## Name
|
||||||
|
|
||||||
*etcd* - enables reading zone data from an etcd instance.
|
*etcd* - enables reading zone data from an etcd version 3 instance.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
The data in etcd has to be encoded as
|
The data in etcd instance has to be encoded as
|
||||||
a [message](https://github.com/skynetservices/skydns/blob/2fcff74cdc9f9a7dd64189a447ef27ac354b725f/msg/service.go#L26)
|
a [message](https://github.com/skynetservices/skydns/blob/2fcff74cdc9f9a7dd64189a447ef27ac354b725f/msg/service.go#L26)
|
||||||
like [SkyDNS](https://github.com/skynetservices/skydns). It should also work just like SkyDNS.
|
like [SkyDNS](https://github.com/skynetservices/skydns). It should also work just like SkyDNS.
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ etcd [ZONES...]
|
||||||
|
|
||||||
* **ZONES** zones etcd should be authoritative for.
|
* **ZONES** zones etcd should be authoritative for.
|
||||||
|
|
||||||
The path will default to `/skydns` the local etcd proxy (http://localhost:2379). If no zones are
|
The path will default to `/skydns` the local etcd3 proxy (http://localhost:2379). If no zones are
|
||||||
specified the block's zone will be used as the zone.
|
specified the block's zone will be used as the zone.
|
||||||
|
|
||||||
If you want to `round robin` A and AAAA responses look at the `loadbalance` plugin.
|
If you want to `round robin` A and AAAA responses look at the `loadbalance` plugin.
|
||||||
|
@ -169,7 +169,3 @@ dig +short skydns.local AAAA @localhost
|
||||||
2003::8:1
|
2003::8:1
|
||||||
2003::8:2
|
2003::8:2
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
## Bugs
|
|
||||||
|
|
||||||
Only the etcdv2 protocol is supported.
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Package etcd provides the etcd backend plugin.
|
// Package etcd provides the etcd version 3 backend plugin.
|
||||||
package etcd
|
package etcd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,10 +16,19 @@ import (
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||||
etcdc "github.com/coreos/etcd/client"
|
etcdcv3 "github.com/coreos/etcd/clientv3"
|
||||||
|
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
priority = 10 // default priority when nothing is set
|
||||||
|
ttl = 300 // default ttl when nothing is set
|
||||||
|
etcdTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var errKeyNotFound = errors.New("Key not found")
|
||||||
|
|
||||||
// Etcd is a plugin talks to an etcd cluster.
|
// Etcd is a plugin talks to an etcd cluster.
|
||||||
type Etcd struct {
|
type Etcd struct {
|
||||||
Next plugin.Handler
|
Next plugin.Handler
|
||||||
|
@ -26,7 +36,7 @@ type Etcd struct {
|
||||||
Zones []string
|
Zones []string
|
||||||
PathPrefix string
|
PathPrefix string
|
||||||
Upstream upstream.Upstream // Proxy for looking up names during the resolution process
|
Upstream upstream.Upstream // Proxy for looking up names during the resolution process
|
||||||
Client etcdc.KeysAPI
|
Client *etcdcv3.Client
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
Stubmap *map[string]proxy.Proxy // list of proxies for stub resolving.
|
Stubmap *map[string]proxy.Proxy // list of proxies for stub resolving.
|
||||||
|
|
||||||
|
@ -56,10 +66,7 @@ func (e *Etcd) Lookup(state request.Request, name string, typ uint16) (*dns.Msg,
|
||||||
|
|
||||||
// IsNameError implements the ServiceBackend interface.
|
// IsNameError implements the ServiceBackend interface.
|
||||||
func (e *Etcd) IsNameError(err error) bool {
|
func (e *Etcd) IsNameError(err error) bool {
|
||||||
if ee, ok := err.(etcdc.Error); ok && ee.Code == etcdc.ErrorCodeKeyNotFound {
|
return err == errKeyNotFound
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Records looks up records in etcd. If exact is true, it will lookup just this
|
// Records looks up records in etcd. If exact is true, it will lookup just this
|
||||||
|
@ -73,51 +80,50 @@ func (e *Etcd) Records(state request.Request, exact bool) ([]msg.Service, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
segments := strings.Split(msg.Path(name, e.PathPrefix), "/")
|
segments := strings.Split(msg.Path(name, e.PathPrefix), "/")
|
||||||
switch {
|
return e.loopNodes(r.Kvs, segments, star)
|
||||||
case exact && r.Node.Dir:
|
|
||||||
return nil, nil
|
|
||||||
case r.Node.Dir:
|
|
||||||
return e.loopNodes(r.Node.Nodes, segments, star, nil)
|
|
||||||
default:
|
|
||||||
return e.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get is a wrapper for client.Get
|
func (e *Etcd) get(path string, recursive bool) (*etcdcv3.GetResponse, error) {
|
||||||
func (e *Etcd) get(path string, recursive bool) (*etcdc.Response, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout)
|
ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
r, err := e.Client.Get(ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive})
|
if recursive == true {
|
||||||
|
if !strings.HasSuffix(path, "/") {
|
||||||
|
path = path + "/"
|
||||||
|
}
|
||||||
|
r, err := e.Client.Get(ctx, path, etcdcv3.WithPrefix())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.Count == 0 {
|
||||||
|
path = strings.TrimSuffix(path, "/")
|
||||||
|
r, err = e.Client.Get(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.Count == 0 {
|
||||||
|
return nil, errKeyNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := e.Client.Get(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if r.Count == 0 {
|
||||||
|
return nil, errKeyNotFound
|
||||||
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// skydns/local/skydns/east/staging/web
|
func (e *Etcd) loopNodes(kv []*mvccpb.KeyValue, nameParts []string, star bool) (sx []msg.Service, err error) {
|
||||||
// skydns/local/skydns/west/production/web
|
bx := make(map[msg.Service]bool)
|
||||||
//
|
|
||||||
// skydns/local/skydns/*/*/web
|
|
||||||
// skydns/local/skydns/*/web
|
|
||||||
|
|
||||||
// loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname
|
|
||||||
// will be match against any wildcards when star is true.
|
|
||||||
func (e *Etcd) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) {
|
|
||||||
if bx == nil {
|
|
||||||
bx = make(map[msg.Service]bool)
|
|
||||||
}
|
|
||||||
Nodes:
|
Nodes:
|
||||||
for _, n := range ns {
|
for _, n := range kv {
|
||||||
if n.Dir {
|
|
||||||
nodes, err := e.loopNodes(n.Nodes, nameParts, star, bx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sx = append(sx, nodes...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if star {
|
if star {
|
||||||
keyParts := strings.Split(n.Key, "/")
|
s := string(n.Key)
|
||||||
|
keyParts := strings.Split(s, "/")
|
||||||
for i, n := range nameParts {
|
for i, n := range nameParts {
|
||||||
if i > len(keyParts)-1 {
|
if i > len(keyParts)-1 {
|
||||||
// name is longer than key
|
// name is longer than key
|
||||||
|
@ -132,16 +138,16 @@ Nodes:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serv := new(msg.Service)
|
serv := new(msg.Service)
|
||||||
if err := json.Unmarshal([]byte(n.Value), serv); err != nil {
|
if err := json.Unmarshal(n.Value, serv); err != nil {
|
||||||
return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
|
return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
|
||||||
}
|
}
|
||||||
b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text, Key: n.Key}
|
b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text, Key: string(n.Key)}
|
||||||
if _, ok := bx[b]; ok {
|
if _, ok := bx[b]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bx[b] = true
|
bx[b] = true
|
||||||
|
|
||||||
serv.Key = n.Key
|
serv.Key = string(n.Key)
|
||||||
serv.TTL = e.TTL(n, serv)
|
serv.TTL = e.TTL(n, serv)
|
||||||
if serv.Priority == 0 {
|
if serv.Priority == 0 {
|
||||||
serv.Priority = priority
|
serv.Priority = priority
|
||||||
|
@ -153,8 +159,8 @@ Nodes:
|
||||||
|
|
||||||
// TTL returns the smaller of the etcd TTL and the service's
|
// TTL returns the smaller of the etcd TTL and the service's
|
||||||
// TTL. If neither of these are set (have a zero value), a default is used.
|
// TTL. If neither of these are set (have a zero value), a default is used.
|
||||||
func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 {
|
func (e *Etcd) TTL(kv *mvccpb.KeyValue, serv *msg.Service) uint32 {
|
||||||
etcdTTL := uint32(node.TTL)
|
etcdTTL := uint32(kv.Lease)
|
||||||
|
|
||||||
if etcdTTL == 0 && serv.TTL == 0 {
|
if etcdTTL == 0 && serv.TTL == 0 {
|
||||||
return ttl
|
return ttl
|
||||||
|
@ -170,9 +176,3 @@ func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 {
|
||||||
}
|
}
|
||||||
return serv.TTL
|
return serv.TTL
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
priority = 10 // default priority when nothing is set
|
|
||||||
ttl = 300 // default ttl when nothing is set
|
|
||||||
etcdTimeout = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
|
@ -65,8 +65,7 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
|
||||||
// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
|
// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
|
||||||
_, err = plugin.A(e, zone, state, nil, opt)
|
_, err = plugin.A(e, zone, state, nil, opt)
|
||||||
}
|
}
|
||||||
|
if err != nil && e.IsNameError(err) {
|
||||||
if e.IsNameError(err) {
|
|
||||||
if e.Fall.Through(state.Name()) {
|
if e.Fall.Through(state.Name()) {
|
||||||
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
|
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
"github.com/coredns/coredns/plugin/proxy"
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
|
|
||||||
etcdc "github.com/coreos/etcd/client"
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -300,12 +299,12 @@ func set(t *testing.T, e *Etcd, k string, ttl time.Duration, m *msg.Service) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
|
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
|
||||||
e.Client.Set(ctxt, path, string(b), &etcdc.SetOptions{TTL: ttl})
|
e.Client.KV.Put(ctxt, path, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete(t *testing.T, e *Etcd, k string) {
|
func delete(t *testing.T, e *Etcd, k string) {
|
||||||
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
|
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
|
||||||
e.Client.Delete(ctxt, path, &etcdc.DeleteOptions{Recursive: false})
|
e.Client.Delete(ctxt, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLookup(t *testing.T) {
|
func TestLookup(t *testing.T) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
"github.com/coredns/coredns/plugin/proxy"
|
||||||
|
|
||||||
etcdc "github.com/coreos/etcd/client"
|
etcdcv3 "github.com/coreos/etcd/clientv3"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -124,22 +124,22 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
|
||||||
}
|
}
|
||||||
etc.Client = client
|
etc.Client = client
|
||||||
etc.endpoints = endpoints
|
etc.endpoints = endpoints
|
||||||
|
|
||||||
return &etc, stubzones, nil
|
return &etc, stubzones, nil
|
||||||
}
|
}
|
||||||
return &Etcd{}, false, nil
|
return &Etcd{}, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEtcdClient(endpoints []string, cc *tls.Config) (etcdc.KeysAPI, error) {
|
func newEtcdClient(endpoints []string, cc *tls.Config) (*etcdcv3.Client, error) {
|
||||||
etcdCfg := etcdc.Config{
|
etcdCfg := etcdcv3.Config{
|
||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
Transport: mwtls.NewHTTPSTransport(cc),
|
TLS: cc,
|
||||||
}
|
}
|
||||||
cli, err := etcdc.New(etcdCfg)
|
cli, err := etcdcv3.New(etcdCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return etcdc.NewKeysAPI(cli), nil
|
return cli, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultEndpoint = "http://localhost:2379"
|
const defaultEndpoint = "http://localhost:2379"
|
||||||
|
|
|
@ -16,17 +16,16 @@ import (
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
etcdc "github.com/coreos/etcd/client"
|
etcdcv3 "github.com/coreos/etcd/clientv3"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func etcdPlugin() *etcd.Etcd {
|
func etcdPlugin() *etcd.Etcd {
|
||||||
etcdCfg := etcdc.Config{
|
etcdCfg := etcdcv3.Config{
|
||||||
Endpoints: []string{"http://localhost:2379"},
|
Endpoints: []string{"http://localhost:2379"},
|
||||||
}
|
}
|
||||||
cli, _ := etcdc.New(etcdCfg)
|
cli, _ := etcdcv3.New(etcdCfg)
|
||||||
client := etcdc.NewKeysAPI(cli)
|
return &etcd.Etcd{Client: cli, PathPrefix: "/skydns"}
|
||||||
return &etcd.Etcd{Client: client, PathPrefix: "/skydns"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test starts two coredns servers (and needs etcd). Configure a stubzones in both (that will loop) and
|
// This test starts two coredns servers (and needs etcd). Configure a stubzones in both (that will loop) and
|
||||||
|
@ -94,11 +93,11 @@ func set(ctx context.Context, t *testing.T, e *etcd.Etcd, k string, ttl time.Dur
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
|
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
|
||||||
e.Client.Set(ctx, path, string(b), &etcdc.SetOptions{TTL: ttl})
|
e.Client.KV.Put(ctx, path, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from plugin/etcd/setup_test.go
|
// Copied from plugin/etcd/setup_test.go
|
||||||
func delete(ctx context.Context, t *testing.T, e *etcd.Etcd, k string) {
|
func delete(ctx context.Context, t *testing.T, e *etcd.Etcd, k string) {
|
||||||
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
|
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
|
||||||
e.Client.Delete(ctx, path, &etcdc.DeleteOptions{Recursive: false})
|
e.Client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
36
vendor/github.com/DataDog/dd-trace-go/contrib/README.md
generated
vendored
Normal file
36
vendor/github.com/DataDog/dd-trace-go/contrib/README.md
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Libraries supported for tracing
|
||||||
|
|
||||||
|
All of these libraries are supported by our Application Performance Monitoring tool.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Check if your library is supported (*i.e.* you find it in this directory).
|
||||||
|
*ex:* if you're using the `net/http` package for your server, you see it's present in this directory.
|
||||||
|
|
||||||
|
2. In your app, replace your import by our traced version of the library.
|
||||||
|
*ex:*
|
||||||
|
```go
|
||||||
|
import "net/http"
|
||||||
|
```
|
||||||
|
becomes
|
||||||
|
```go
|
||||||
|
import "github.com/DataDog/dd-trace-go/contrib/net/http"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Read through the `example_test.go` present in each folder of the libraries to understand how to trace your app.
|
||||||
|
*ex:* for `net/http`, see [net/http/example_test.go](https://github.com/DataDog/dd-trace-go/blob/master/contrib/net/http/example_test.go)
|
||||||
|
|
||||||
|
## Contribution guidelines
|
||||||
|
|
||||||
|
### 1. Follow the package naming convention
|
||||||
|
|
||||||
|
If a library looks like this: `github.com/user/lib`, the contribution must looks like this `user/lib`.
|
||||||
|
In the case of the standard library, just use the path after `src`.
|
||||||
|
*E.g.* `src/database/sql` becomes `database/sql`.
|
||||||
|
|
||||||
|
### 2. Respect the original API
|
||||||
|
|
||||||
|
Keep the original names for exported functions, don't use the prefix or suffix `trace`.
|
||||||
|
*E.g.* prefer `Open` instead of `OpenTrace`.
|
||||||
|
|
||||||
|
Of course you can modify the number of arguments of a function if you need to pass the tracer for example.
|
169
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/example_test.go
generated
vendored
Normal file
169
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
package sql_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
sqltrace "github.com/DataDog/dd-trace-go/contrib/database/sql"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To trace the sql calls, you just need to open your sql.DB with OpenTraced.
|
||||||
|
// All calls through this sql.DB object will then be traced.
|
||||||
|
func Example() {
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sql.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
db, err := sqltrace.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All calls through the database/sql API will then be traced.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you want to link your db calls with existing traces, you need to use
|
||||||
|
// the context version of the database/sql API.
|
||||||
|
// Just make sure you are passing the parent span within the context.
|
||||||
|
func Example_context() {
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sql.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
db, err := sqltrace.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create a parent span and put it within the context.
|
||||||
|
span := tracer.NewRootSpan("postgres.parent", "web-backend", "query-parent")
|
||||||
|
ctx := tracer.ContextWithSpan(context.Background(), span)
|
||||||
|
|
||||||
|
// We need to use the context version of the database/sql API
|
||||||
|
// in order to link this call with the parent span.
|
||||||
|
db.PingContext(ctx)
|
||||||
|
rows, _ := db.QueryContext(ctx, "SELECT * FROM city LIMIT 5")
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
stmt, _ := db.PrepareContext(ctx, "INSERT INTO city(name) VALUES($1)")
|
||||||
|
stmt.Exec("New York")
|
||||||
|
stmt, _ = db.PrepareContext(ctx, "SELECT name FROM city LIMIT $1")
|
||||||
|
rows, _ = stmt.Query(1)
|
||||||
|
rows.Close()
|
||||||
|
stmt.Close()
|
||||||
|
|
||||||
|
tx, _ := db.BeginTx(ctx, nil)
|
||||||
|
tx.ExecContext(ctx, "INSERT INTO city(name) VALUES('New York')")
|
||||||
|
rows, _ = tx.QueryContext(ctx, "SELECT * FROM city LIMIT 5")
|
||||||
|
rows.Close()
|
||||||
|
stmt, _ = tx.PrepareContext(ctx, "SELECT name FROM city LIMIT $1")
|
||||||
|
rows, _ = stmt.Query(1)
|
||||||
|
rows.Close()
|
||||||
|
stmt.Close()
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
|
// Calling span.Finish() will send the span into the tracer's buffer
|
||||||
|
// and then being processed.
|
||||||
|
span.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can trace all drivers implementing the database/sql/driver interface.
|
||||||
|
// For example, you can trace the go-sql-driver/mysql with the following code.
|
||||||
|
func Example_mySQL() {
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sql.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
db, err := sqltrace.OpenTraced(&mysql.MySQLDriver{}, "user:password@/dbname", "web-backend")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All calls through the database/sql API will then be traced.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sql.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
func ExampleOpenTraced() {
|
||||||
|
// The first argument is a reference to the driver to trace.
|
||||||
|
// The second argument is the dataSourceName.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
// The last argument allows you to specify a custom tracer to use for tracing.
|
||||||
|
db, err := sqltrace.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the database/sql API as usual and see traces appear in the Datadog app.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can use a custom tracer by passing it through the optional last argument of OpenTraced.
|
||||||
|
func ExampleOpenTraced_tracer() {
|
||||||
|
// Create and customize a new tracer that will forward 50% of generated traces to the agent.
|
||||||
|
// (useful to manage resource usage in high-throughput environments)
|
||||||
|
trc := tracer.NewTracer()
|
||||||
|
trc.SetSampleRate(0.5)
|
||||||
|
|
||||||
|
// Pass your custom tracer through the last argument of OpenTraced to trace your db calls with it.
|
||||||
|
db, err := sqltrace.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend", trc)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the database/sql API as usual and see traces appear in the Datadog app.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you need more granularity, you can register the traced driver seperately from the Open call.
|
||||||
|
func ExampleRegister() {
|
||||||
|
// Register a traced version of your driver.
|
||||||
|
sqltrace.Register("postgres", &pq.Driver{})
|
||||||
|
|
||||||
|
// Returns a sql.DB object that holds the traced connection to the database.
|
||||||
|
// Note: the sql.DB object returned by sql.Open will not be traced so make sure to use sql.Open.
|
||||||
|
db, _ := sqltrace.Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Use the database/sql API as usual and see traces appear in the Datadog app.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can use a custom tracer by passing it through the optional last argument of Register.
|
||||||
|
func ExampleRegister_tracer() {
|
||||||
|
// Create and customize a new tracer that will forward 50% of generated traces to the agent.
|
||||||
|
// (useful to manage resource usage in high-throughput environments)
|
||||||
|
trc := tracer.NewTracer()
|
||||||
|
trc.SetSampleRate(0.5)
|
||||||
|
|
||||||
|
// Register a traced version of your driver and specify to use the previous tracer
|
||||||
|
// to send the traces to the agent.
|
||||||
|
sqltrace.Register("postgres", &pq.Driver{}, trc)
|
||||||
|
|
||||||
|
// Returns a sql.DB object that holds the traced connection to the database.
|
||||||
|
// Note: the sql.DB object returned by sql.Open will not be traced so make sure to use sql.Open.
|
||||||
|
db, _ := sqltrace.Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
defer db.Close()
|
||||||
|
}
|
41
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/mysql_test.go
generated
vendored
Normal file
41
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/mysql_test.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/contrib/database/sql/sqltest"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMySQL(t *testing.T) {
|
||||||
|
trc, transport := tracertest.GetTestTracer()
|
||||||
|
db, err := OpenTraced(&mysql.MySQLDriver{}, "test:test@tcp(127.0.0.1:53306)/test", "mysql-test", trc)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
testDB := &sqltest.DB{
|
||||||
|
DB: db,
|
||||||
|
Tracer: trc,
|
||||||
|
Transport: transport,
|
||||||
|
DriverName: "mysql",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSpan := &tracer.Span{
|
||||||
|
Name: "mysql.query",
|
||||||
|
Service: "mysql-test",
|
||||||
|
Type: "sql",
|
||||||
|
}
|
||||||
|
expectedSpan.Meta = map[string]string{
|
||||||
|
"db.user": "test",
|
||||||
|
"out.host": "127.0.0.1",
|
||||||
|
"out.port": "53306",
|
||||||
|
"db.name": "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
sqltest.AllSQLTests(t, testDB, expectedSpan)
|
||||||
|
}
|
42
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parse.go
generated
vendored
Normal file
42
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parse.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseDSN returns all information passed through the DSN:
|
||||||
|
func parseDSN(driverName, dsn string) (meta map[string]string, err error) {
|
||||||
|
switch driverName {
|
||||||
|
case "mysql":
|
||||||
|
meta, err = parsedsn.MySQL(dsn)
|
||||||
|
case "postgres":
|
||||||
|
meta, err = parsedsn.Postgres(dsn)
|
||||||
|
}
|
||||||
|
meta = normalize(meta)
|
||||||
|
return meta, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalize(meta map[string]string) map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for k, v := range meta {
|
||||||
|
if nk, ok := normalizeKey(k); ok {
|
||||||
|
m[nk] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeKey(k string) (string, bool) {
|
||||||
|
switch k {
|
||||||
|
case "user":
|
||||||
|
return "db.user", true
|
||||||
|
case "application_name":
|
||||||
|
return "db.application", true
|
||||||
|
case "dbname":
|
||||||
|
return "db.name", true
|
||||||
|
case "host", "port":
|
||||||
|
return "out." + k, true
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
44
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parse_test.go
generated
vendored
Normal file
44
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parse_test.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseDSN(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
expected := map[string]string{
|
||||||
|
"db.user": "bob",
|
||||||
|
"out.host": "1.2.3.4",
|
||||||
|
"out.port": "5432",
|
||||||
|
"db.name": "mydb",
|
||||||
|
}
|
||||||
|
m, err := parseDSN("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
|
||||||
|
expected = map[string]string{
|
||||||
|
"db.user": "bob",
|
||||||
|
"out.host": "1.2.3.4",
|
||||||
|
"out.port": "5432",
|
||||||
|
"db.name": "mydb",
|
||||||
|
}
|
||||||
|
m, err = parseDSN("mysql", "bob:secret@tcp(1.2.3.4:5432)/mydb")
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
|
||||||
|
expected = map[string]string{
|
||||||
|
"out.port": "5433",
|
||||||
|
"out.host": "master-db-master-active.postgres.service.consul",
|
||||||
|
"db.name": "dogdatastaging",
|
||||||
|
"db.application": "trace-api",
|
||||||
|
"db.user": "dog",
|
||||||
|
}
|
||||||
|
dsn := "connect_timeout=0 binary_parameters=no password=zMWmQz26GORmgVVKEbEl dbname=dogdatastaging application_name=trace-api port=5433 sslmode=disable host=master-db-master-active.postgres.service.consul user=dog"
|
||||||
|
m, err = parseDSN("postgres", dsn)
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
}
|
25
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql/collations.go
generated
vendored
Normal file
25
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql/collations.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
const defaultCollation = "utf8_general_ci"
|
||||||
|
|
||||||
|
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||||
|
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
||||||
|
var unsafeCollations = map[string]bool{
|
||||||
|
"big5_chinese_ci": true,
|
||||||
|
"sjis_japanese_ci": true,
|
||||||
|
"gbk_chinese_ci": true,
|
||||||
|
"big5_bin": true,
|
||||||
|
"gb2312_bin": true,
|
||||||
|
"gbk_bin": true,
|
||||||
|
"sjis_bin": true,
|
||||||
|
"cp932_japanese_ci": true,
|
||||||
|
"cp932_bin": true,
|
||||||
|
}
|
148
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql/dsn.go
generated
vendored
Normal file
148
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql/dsn.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?")
|
||||||
|
errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)")
|
||||||
|
errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name")
|
||||||
|
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a configuration parsed from a DSN string
|
||||||
|
type Config struct {
|
||||||
|
User string // Username
|
||||||
|
Passwd string // Password (requires User)
|
||||||
|
Net string // Network type
|
||||||
|
Addr string // Network address (requires Net)
|
||||||
|
DBName string // Database name
|
||||||
|
Params map[string]string // Connection parameters
|
||||||
|
Collation string // Connection collation
|
||||||
|
Loc *time.Location // Location for time.Time values
|
||||||
|
MaxAllowedPacket int // Max packet size allowed
|
||||||
|
TLSConfig string // TLS configuration name
|
||||||
|
tls *tls.Config // TLS configuration
|
||||||
|
Timeout time.Duration // Dial timeout
|
||||||
|
ReadTimeout time.Duration // I/O read timeout
|
||||||
|
WriteTimeout time.Duration // I/O write timeout
|
||||||
|
|
||||||
|
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
|
||||||
|
AllowCleartextPasswords bool // Allows the cleartext client side plugin
|
||||||
|
AllowNativePasswords bool // Allows the native password authentication method
|
||||||
|
AllowOldPasswords bool // Allows the old insecure password method
|
||||||
|
ClientFoundRows bool // Return number of matching rows instead of rows changed
|
||||||
|
ColumnsWithAlias bool // Prepend table alias to column names
|
||||||
|
InterpolateParams bool // Interpolate placeholders into query string
|
||||||
|
MultiStatements bool // Allow multiple statements in one query
|
||||||
|
ParseTime bool // Parse time values to time.Time
|
||||||
|
Strict bool // Return warnings as errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDSN parses the DSN string to a Config
|
||||||
|
func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
|
// New config with some default values
|
||||||
|
cfg = &Config{
|
||||||
|
Loc: time.UTC,
|
||||||
|
Collation: defaultCollation,
|
||||||
|
}
|
||||||
|
|
||||||
|
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||||
|
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||||
|
foundSlash := false
|
||||||
|
for i := len(dsn) - 1; i >= 0; i-- {
|
||||||
|
if dsn[i] == '/' {
|
||||||
|
foundSlash = true
|
||||||
|
var j, k int
|
||||||
|
|
||||||
|
// left part is empty if i <= 0
|
||||||
|
if i > 0 {
|
||||||
|
// [username[:password]@][protocol[(address)]]
|
||||||
|
// Find the last '@' in dsn[:i]
|
||||||
|
for j = i; j >= 0; j-- {
|
||||||
|
if dsn[j] == '@' {
|
||||||
|
// username[:password]
|
||||||
|
// Find the first ':' in dsn[:j]
|
||||||
|
for k = 0; k < j; k++ {
|
||||||
|
if dsn[k] == ':' {
|
||||||
|
cfg.Passwd = dsn[k+1 : j]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.User = dsn[:k]
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [protocol[(address)]]
|
||||||
|
// Find the first '(' in dsn[j+1:i]
|
||||||
|
for k = j + 1; k < i; k++ {
|
||||||
|
if dsn[k] == '(' {
|
||||||
|
// dsn[i-1] must be == ')' if an address is specified
|
||||||
|
if dsn[i-1] != ')' {
|
||||||
|
if strings.ContainsRune(dsn[k+1:i], ')') {
|
||||||
|
return nil, errInvalidDSNUnescaped
|
||||||
|
}
|
||||||
|
return nil, errInvalidDSNAddr
|
||||||
|
}
|
||||||
|
cfg.Addr = dsn[k+1 : i-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.Net = dsn[j+1 : k]
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbname[?param1=value1&...¶mN=valueN]
|
||||||
|
// Find the first '?' in dsn[i+1:]
|
||||||
|
for j = i + 1; j < len(dsn); j++ {
|
||||||
|
if dsn[j] == '?' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.DBName = dsn[i+1 : j]
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundSlash && len(dsn) > 0 {
|
||||||
|
return nil, errInvalidDSNNoSlash
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
||||||
|
return nil, errInvalidDSNUnsafeCollation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default network if empty
|
||||||
|
if cfg.Net == "" {
|
||||||
|
cfg.Net = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default address if empty
|
||||||
|
if cfg.Addr == "" {
|
||||||
|
switch cfg.Net {
|
||||||
|
case "tcp":
|
||||||
|
cfg.Addr = "127.0.0.1:3306"
|
||||||
|
case "unix":
|
||||||
|
cfg.Addr = "/tmp/mysql.sock"
|
||||||
|
default:
|
||||||
|
return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
3
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql/mysql.go
generated
vendored
Normal file
3
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql/mysql.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Package mysql is the minimal fork of go-sql-driver/mysql so we can use their code
|
||||||
|
// to parse the mysql DSNs
|
||||||
|
package mysql
|
23
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql/utils.go
generated
vendored
Normal file
23
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
// Returns the bool value of the input.
|
||||||
|
// The 2nd return value indicates if the input was a valid bool value
|
||||||
|
func readBool(input string) (value bool, valid bool) {
|
||||||
|
switch input {
|
||||||
|
case "1", "true", "TRUE", "True":
|
||||||
|
return true, true
|
||||||
|
case "0", "false", "FALSE", "False":
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a valid bool value
|
||||||
|
return
|
||||||
|
}
|
47
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/parsedsn.go
generated
vendored
Normal file
47
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/parsedsn.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// Package parsedsn provides functions to parse any kind of DSNs into a map[string]string
|
||||||
|
package parsedsn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/mysql"
|
||||||
|
"github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Postgres parses a postgres-type dsn into a map
|
||||||
|
func Postgres(dsn string) (map[string]string, error) {
|
||||||
|
var err error
|
||||||
|
meta := make(map[string]string)
|
||||||
|
|
||||||
|
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
|
||||||
|
dsn, err = pq.ParseURL(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pq.ParseOpts(dsn, meta); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assure that we do not pass the user secret
|
||||||
|
delete(meta, "password")
|
||||||
|
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL parses a mysql-type dsn into a map
|
||||||
|
func MySQL(dsn string) (m map[string]string, err error) {
|
||||||
|
var cfg *mysql.Config
|
||||||
|
if cfg, err = mysql.ParseDSN(dsn); err == nil {
|
||||||
|
addr := strings.Split(cfg.Addr, ":")
|
||||||
|
m = map[string]string{
|
||||||
|
"user": cfg.User,
|
||||||
|
"host": addr[0],
|
||||||
|
"port": addr[1],
|
||||||
|
"dbname": cfg.DBName,
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
49
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/parsedsn_test.go
generated
vendored
Normal file
49
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/parsedsn_test.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package parsedsn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMySQL(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
expected := map[string]string{
|
||||||
|
"user": "bob",
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": "5432",
|
||||||
|
"dbname": "mydb",
|
||||||
|
}
|
||||||
|
m, err := MySQL("bob:secret@tcp(1.2.3.4:5432)/mydb")
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPostgres(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
expected := map[string]string{
|
||||||
|
"user": "bob",
|
||||||
|
"host": "1.2.3.4",
|
||||||
|
"port": "5432",
|
||||||
|
"dbname": "mydb",
|
||||||
|
"sslmode": "verify-full",
|
||||||
|
}
|
||||||
|
m, err := Postgres("postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
|
||||||
|
expected = map[string]string{
|
||||||
|
"user": "dog",
|
||||||
|
"port": "5433",
|
||||||
|
"host": "master-db-master-active.postgres.service.consul",
|
||||||
|
"dbname": "dogdatastaging",
|
||||||
|
"application_name": "trace-api",
|
||||||
|
}
|
||||||
|
dsn := "password=zMWmQz26GORmgVVKEbEl dbname=dogdatastaging application_name=trace-api port=5433 host=master-db-master-active.postgres.service.consul user=dog"
|
||||||
|
m, err = Postgres(dsn)
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
}
|
118
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/pq/conn.go
generated
vendored
Normal file
118
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/pq/conn.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type values map[string]string
|
||||||
|
|
||||||
|
// scanner implements a tokenizer for libpq-style option strings.
|
||||||
|
type scanner struct {
|
||||||
|
s []rune
|
||||||
|
i int
|
||||||
|
}
|
||||||
|
|
||||||
|
// newScanner returns a new scanner initialized with the option string s.
|
||||||
|
func newScanner(s string) *scanner {
|
||||||
|
return &scanner{[]rune(s), 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next rune.
|
||||||
|
// It returns 0, false if the end of the text has been reached.
|
||||||
|
func (s *scanner) Next() (rune, bool) {
|
||||||
|
if s.i >= len(s.s) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
r := s.s[s.i]
|
||||||
|
s.i++
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipSpaces returns the next non-whitespace rune.
|
||||||
|
// It returns 0, false if the end of the text has been reached.
|
||||||
|
func (s *scanner) SkipSpaces() (rune, bool) {
|
||||||
|
r, ok := s.Next()
|
||||||
|
for unicode.IsSpace(r) && ok {
|
||||||
|
r, ok = s.Next()
|
||||||
|
}
|
||||||
|
return r, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseOpts parses the options from name and adds them to the values.
|
||||||
|
// The parsing code is based on conninfo_parse from libpq's fe-connect.c
|
||||||
|
func ParseOpts(name string, o values) error {
|
||||||
|
s := newScanner(name)
|
||||||
|
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
keyRunes, valRunes []rune
|
||||||
|
r rune
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if r, ok = s.SkipSpaces(); !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the key
|
||||||
|
for !unicode.IsSpace(r) && r != '=' {
|
||||||
|
keyRunes = append(keyRunes, r)
|
||||||
|
if r, ok = s.Next(); !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip any whitespace if we're not at the = yet
|
||||||
|
if r != '=' {
|
||||||
|
r, ok = s.SkipSpaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current character should be =
|
||||||
|
if r != '=' || !ok {
|
||||||
|
return fmt.Errorf(`missing "=" after %q in connection info string"`, string(keyRunes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip any whitespace after the =
|
||||||
|
if r, ok = s.SkipSpaces(); !ok {
|
||||||
|
// If we reach the end here, the last value is just an empty string as per libpq.
|
||||||
|
o[string(keyRunes)] = ""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if r != '\'' {
|
||||||
|
for !unicode.IsSpace(r) {
|
||||||
|
if r == '\\' {
|
||||||
|
if r, ok = s.Next(); !ok {
|
||||||
|
return fmt.Errorf(`missing character after backslash`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valRunes = append(valRunes, r)
|
||||||
|
|
||||||
|
if r, ok = s.Next(); !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote:
|
||||||
|
for {
|
||||||
|
if r, ok = s.Next(); !ok {
|
||||||
|
return fmt.Errorf(`unterminated quoted string literal in connection string`)
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '\'':
|
||||||
|
break quote
|
||||||
|
case '\\':
|
||||||
|
r, _ = s.Next()
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
valRunes = append(valRunes, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o[string(keyRunes)] = string(valRunes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
2
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/pq/pq.go
generated
vendored
Normal file
2
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/pq/pq.go
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package pq is the minimal fork of lib/pq so we can use their code to parse the postgres DSNs
|
||||||
|
package pq
|
76
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/pq/url.go
generated
vendored
Normal file
76
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/parsedsn/pq/url.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
nurl "net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseURL no longer needs to be used by clients of this library since supplying a URL as a
|
||||||
|
// connection string to sql.Open() is now supported:
|
||||||
|
//
|
||||||
|
// sql.Open("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
|
||||||
|
//
|
||||||
|
// It remains exported here for backwards-compatibility.
|
||||||
|
//
|
||||||
|
// ParseURL converts a url to a connection string for driver.Open.
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full"
|
||||||
|
//
|
||||||
|
// converts to:
|
||||||
|
//
|
||||||
|
// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full"
|
||||||
|
//
|
||||||
|
// A minimal example:
|
||||||
|
//
|
||||||
|
// "postgres://"
|
||||||
|
//
|
||||||
|
// This will be blank, causing driver.Open to use all of the defaults
|
||||||
|
func ParseURL(url string) (string, error) {
|
||||||
|
u, err := nurl.Parse(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "postgres" && u.Scheme != "postgresql" {
|
||||||
|
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var kvs []string
|
||||||
|
escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
|
||||||
|
accrue := func(k, v string) {
|
||||||
|
if v != "" {
|
||||||
|
kvs = append(kvs, k+"="+escaper.Replace(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
v := u.User.Username()
|
||||||
|
accrue("user", v)
|
||||||
|
|
||||||
|
v, _ = u.User.Password()
|
||||||
|
accrue("password", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||||
|
accrue("host", u.Host)
|
||||||
|
} else {
|
||||||
|
accrue("host", host)
|
||||||
|
accrue("port", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Path != "" {
|
||||||
|
accrue("dbname", u.Path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
q := u.Query()
|
||||||
|
for k := range q {
|
||||||
|
accrue(k, q.Get(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(kvs) // Makes testing easier (not a performance concern)
|
||||||
|
return strings.Join(kvs, " "), nil
|
||||||
|
}
|
41
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/pq_test.go
generated
vendored
Normal file
41
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/pq_test.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/contrib/database/sql/sqltest"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPostgres(t *testing.T) {
|
||||||
|
trc, transport := tracertest.GetTestTracer()
|
||||||
|
db, err := OpenTraced(&pq.Driver{}, "postgres://postgres:postgres@127.0.0.1:55432/postgres?sslmode=disable", "postgres-test", trc)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
testDB := &sqltest.DB{
|
||||||
|
DB: db,
|
||||||
|
Tracer: trc,
|
||||||
|
Transport: transport,
|
||||||
|
DriverName: "postgres",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSpan := &tracer.Span{
|
||||||
|
Name: "postgres.query",
|
||||||
|
Service: "postgres-test",
|
||||||
|
Type: "sql",
|
||||||
|
}
|
||||||
|
expectedSpan.Meta = map[string]string{
|
||||||
|
"db.user": "postgres",
|
||||||
|
"out.host": "127.0.0.1",
|
||||||
|
"out.port": "55432",
|
||||||
|
"db.name": "postgres",
|
||||||
|
}
|
||||||
|
|
||||||
|
sqltest.AllSQLTests(t, testDB, expectedSpan)
|
||||||
|
}
|
384
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sql.go
generated
vendored
Normal file
384
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sql.go
generated
vendored
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
// Package sqltraced provides a traced version of any driver implementing the database/sql/driver interface.
|
||||||
|
// To trace jmoiron/sqlx, see https://godoc.org/github.com/DataDog/dd-trace-go/tracer/contrib/sqlxtraced.
|
||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/sqlutils"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenTraced will first register the traced version of the `driver` if not yet registered and will then open a connection with it.
|
||||||
|
// This is usually the only function to use when there is no need for the granularity offered by Register and Open.
|
||||||
|
// The last parameter is optional and enables you to use a custom tracer.
|
||||||
|
func OpenTraced(driver driver.Driver, dataSourceName, service string, trcv ...*tracer.Tracer) (*sql.DB, error) {
|
||||||
|
driverName := sqlutils.GetDriverName(driver)
|
||||||
|
Register(driverName, driver, trcv...)
|
||||||
|
return Open(driverName, dataSourceName, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register takes a driver and registers a traced version of this one.
|
||||||
|
// The last parameter is optional and enables you to use a custom tracer.
|
||||||
|
func Register(driverName string, driver driver.Driver, trcv ...*tracer.Tracer) {
|
||||||
|
if driver == nil {
|
||||||
|
log.Error("RegisterTracedDriver: driver is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var trc *tracer.Tracer
|
||||||
|
if len(trcv) == 0 || (len(trcv) > 0 && trcv[0] == nil) {
|
||||||
|
trc = tracer.DefaultTracer
|
||||||
|
} else {
|
||||||
|
trc = trcv[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
tracedDriverName := sqlutils.GetTracedDriverName(driverName)
|
||||||
|
if !stringInSlice(sql.Drivers(), tracedDriverName) {
|
||||||
|
td := tracedDriver{
|
||||||
|
Driver: driver,
|
||||||
|
tracer: trc,
|
||||||
|
driverName: driverName,
|
||||||
|
}
|
||||||
|
sql.Register(tracedDriverName, td)
|
||||||
|
log.Infof("Register %s driver", tracedDriverName)
|
||||||
|
} else {
|
||||||
|
log.Warnf("RegisterTracedDriver: %s already registered", tracedDriverName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open extends the usual API of sql.Open so you can specify the name of the service
|
||||||
|
// under which the traces will appear in the datadog app.
|
||||||
|
func Open(driverName, dataSourceName, service string) (*sql.DB, error) {
|
||||||
|
tracedDriverName := sqlutils.GetTracedDriverName(driverName)
|
||||||
|
// The service is passed through the DSN
|
||||||
|
dsnAndService := newDSNAndService(dataSourceName, service)
|
||||||
|
return sql.Open(tracedDriverName, dsnAndService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracedDriver is a driver we use as a middleware between the database/sql package
|
||||||
|
// and the driver chosen (e.g. mysql, postgresql...).
|
||||||
|
// It implements the driver.Driver interface and add the tracing features on top
|
||||||
|
// of the driver's methods.
|
||||||
|
type tracedDriver struct {
|
||||||
|
driver.Driver
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
driverName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns a tracedConn so that we can pass all the info we get from the DSN
|
||||||
|
// all along the tracing
|
||||||
|
func (td tracedDriver) Open(dsnAndService string) (c driver.Conn, err error) {
|
||||||
|
var meta map[string]string
|
||||||
|
var conn driver.Conn
|
||||||
|
|
||||||
|
dsn, service := parseDSNAndService(dsnAndService)
|
||||||
|
|
||||||
|
// Register the service to Datadog tracing API
|
||||||
|
td.tracer.SetServiceInfo(service, td.driverName, ext.AppTypeDB)
|
||||||
|
|
||||||
|
// Get all kinds of information from the DSN
|
||||||
|
meta, err = parseDSN(td.driverName, dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err = td.Driver.Open(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ti := traceInfo{
|
||||||
|
tracer: td.tracer,
|
||||||
|
driverName: td.driverName,
|
||||||
|
service: service,
|
||||||
|
meta: meta,
|
||||||
|
}
|
||||||
|
return &tracedConn{conn, ti}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceInfo stores all information relative to the tracing
|
||||||
|
type traceInfo struct {
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
driverName string
|
||||||
|
service string
|
||||||
|
resource string
|
||||||
|
meta map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ti traceInfo) getSpan(ctx context.Context, resource string, query ...string) *tracer.Span {
|
||||||
|
name := fmt.Sprintf("%s.%s", ti.driverName, "query")
|
||||||
|
span := ti.tracer.NewChildSpanFromContext(name, ctx)
|
||||||
|
span.Type = ext.SQLType
|
||||||
|
span.Service = ti.service
|
||||||
|
span.Resource = resource
|
||||||
|
if len(query) > 0 {
|
||||||
|
span.Resource = query[0]
|
||||||
|
span.SetMeta(ext.SQLQuery, query[0])
|
||||||
|
}
|
||||||
|
for k, v := range ti.meta {
|
||||||
|
span.SetMeta(k, v)
|
||||||
|
}
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
type tracedConn struct {
|
||||||
|
driver.Conn
|
||||||
|
traceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) {
|
||||||
|
span := tc.getSpan(ctx, "Begin")
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok {
|
||||||
|
tx, err = connBeginTx.BeginTx(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracedTx{tx, tc.traceInfo, ctx}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err = tc.Conn.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracedTx{tx, tc.traceInfo, ctx}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) {
|
||||||
|
span := tc.getSpan(ctx, "Prepare", query)
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Check if the driver implements PrepareContext
|
||||||
|
if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok {
|
||||||
|
stmt, err := connPrepareCtx.PrepareContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tracedStmt{stmt, tc.traceInfo, ctx, query}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the driver does not implement PrepareContex (lib/pq for example)
|
||||||
|
stmt, err = tc.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tracedStmt{stmt, tc.traceInfo, ctx, query}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc tracedConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||||
|
if execer, ok := tc.Conn.(driver.Execer); ok {
|
||||||
|
return execer.Exec(query, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) {
|
||||||
|
span := tc.getSpan(ctx, "Exec", query)
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if execContext, ok := tc.Conn.(driver.ExecerContext); ok {
|
||||||
|
res, err := execContext.ExecContext(ctx, query, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback implementation
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tc.Exec(query, dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracedConn has a Ping method in order to implement the pinger interface
|
||||||
|
func (tc tracedConn) Ping(ctx context.Context) (err error) {
|
||||||
|
span := tc.getSpan(ctx, "Ping")
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if pinger, ok := tc.Conn.(driver.Pinger); ok {
|
||||||
|
err = pinger.Ping(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc tracedConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||||
|
if queryer, ok := tc.Conn.(driver.Queryer); ok {
|
||||||
|
return queryer.Query(query, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) {
|
||||||
|
span := tc.getSpan(ctx, "Query", query)
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok {
|
||||||
|
rows, err := queryerContext.QueryContext(ctx, query, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tc.Query(query, dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracedTx is a traced version of sql.Tx
|
||||||
|
type tracedTx struct {
|
||||||
|
driver.Tx
|
||||||
|
traceInfo
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit sends a span at the end of the transaction
|
||||||
|
func (t tracedTx) Commit() (err error) {
|
||||||
|
span := t.getSpan(t.ctx, "Commit")
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return t.Tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback sends a span if the connection is aborted
|
||||||
|
func (t tracedTx) Rollback() (err error) {
|
||||||
|
span := t.getSpan(t.ctx, "Rollback")
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return t.Tx.Rollback()
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracedStmt is traced version of sql.Stmt
|
||||||
|
type tracedStmt struct {
|
||||||
|
driver.Stmt
|
||||||
|
traceInfo
|
||||||
|
ctx context.Context
|
||||||
|
query string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close sends a span before closing a statement
|
||||||
|
func (s tracedStmt) Close() (err error) {
|
||||||
|
span := s.getSpan(s.ctx, "Close")
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s.Stmt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecContext is needed to implement the driver.StmtExecContext interface
|
||||||
|
func (s tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) {
|
||||||
|
span := s.getSpan(s.ctx, "Exec", s.query)
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok {
|
||||||
|
res, err = stmtExecContext.ExecContext(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback implementation
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Exec(dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryContext is needed to implement the driver.StmtQueryContext interface
|
||||||
|
func (s tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) {
|
||||||
|
span := s.getSpan(s.ctx, "Query", s.query)
|
||||||
|
defer func() {
|
||||||
|
span.SetError(err)
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok {
|
||||||
|
rows, err = stmtQueryContext.QueryContext(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback implementation
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Query(dargs)
|
||||||
|
}
|
211
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sqltest/sqltest.go
generated
vendored
Normal file
211
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sqltest/sqltest.go
generated
vendored
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
// Package sqltest is used for testing sql packages
|
||||||
|
package sqltest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setupTestCase initializes MySQL or Postgres databases and returns a
|
||||||
|
// teardown function that must be executed via `defer`
|
||||||
|
func setupTestCase(t *testing.T, db *DB) func(t *testing.T, db *DB) {
|
||||||
|
// creates the database
|
||||||
|
db.Exec("DROP TABLE IF EXISTS city")
|
||||||
|
db.Exec("CREATE TABLE city (id integer NOT NULL DEFAULT '0', name text)")
|
||||||
|
|
||||||
|
// Empty the tracer
|
||||||
|
db.Tracer.ForceFlush()
|
||||||
|
db.Transport.Traces()
|
||||||
|
|
||||||
|
return func(t *testing.T, db *DB) {
|
||||||
|
// drop the table
|
||||||
|
db.Exec("DROP TABLE city")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllSQLTests applies a sequence of unit tests to check the correct tracing of sql features.
|
||||||
|
func AllSQLTests(t *testing.T, db *DB, expectedSpan *tracer.Span) {
|
||||||
|
// database setup and cleanup
|
||||||
|
tearDown := setupTestCase(t, db)
|
||||||
|
defer tearDown(t, db)
|
||||||
|
|
||||||
|
testDB(t, db, expectedSpan)
|
||||||
|
testStatement(t, db, expectedSpan)
|
||||||
|
testTransaction(t, db, expectedSpan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDB(t *testing.T, db *DB, expectedSpan *tracer.Span) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
const query = "SELECT id, name FROM city LIMIT 5"
|
||||||
|
|
||||||
|
// Test db.Ping
|
||||||
|
err := db.Ping()
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
|
||||||
|
db.Tracer.ForceFlush()
|
||||||
|
traces := db.Transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
actualSpan := spans[0]
|
||||||
|
pingSpan := tracertest.CopySpan(expectedSpan, db.Tracer)
|
||||||
|
pingSpan.Resource = "Ping"
|
||||||
|
tracertest.CompareSpan(t, pingSpan, actualSpan)
|
||||||
|
|
||||||
|
// Test db.Query
|
||||||
|
rows, err := db.Query(query)
|
||||||
|
defer rows.Close()
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
|
||||||
|
db.Tracer.ForceFlush()
|
||||||
|
traces = db.Transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans = traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
actualSpan = spans[0]
|
||||||
|
querySpan := tracertest.CopySpan(expectedSpan, db.Tracer)
|
||||||
|
querySpan.Resource = query
|
||||||
|
querySpan.SetMeta("sql.query", query)
|
||||||
|
tracertest.CompareSpan(t, querySpan, actualSpan)
|
||||||
|
delete(expectedSpan.Meta, "sql.query")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStatement(t *testing.T, db *DB, expectedSpan *tracer.Span) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
query := "INSERT INTO city(name) VALUES(%s)"
|
||||||
|
switch db.DriverName {
|
||||||
|
case "postgres":
|
||||||
|
query = fmt.Sprintf(query, "$1")
|
||||||
|
case "mysql":
|
||||||
|
query = fmt.Sprintf(query, "?")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test TracedConn.PrepareContext
|
||||||
|
stmt, err := db.Prepare(query)
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
|
||||||
|
db.Tracer.ForceFlush()
|
||||||
|
traces := db.Transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
actualSpan := spans[0]
|
||||||
|
prepareSpan := tracertest.CopySpan(expectedSpan, db.Tracer)
|
||||||
|
prepareSpan.Resource = query
|
||||||
|
prepareSpan.SetMeta("sql.query", query)
|
||||||
|
tracertest.CompareSpan(t, prepareSpan, actualSpan)
|
||||||
|
delete(expectedSpan.Meta, "sql.query")
|
||||||
|
|
||||||
|
// Test Exec
|
||||||
|
_, err2 := stmt.Exec("New York")
|
||||||
|
assert.Equal(nil, err2)
|
||||||
|
|
||||||
|
db.Tracer.ForceFlush()
|
||||||
|
traces = db.Transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans = traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
actualSpan = spans[0]
|
||||||
|
|
||||||
|
execSpan := tracertest.CopySpan(expectedSpan, db.Tracer)
|
||||||
|
execSpan.Resource = query
|
||||||
|
execSpan.SetMeta("sql.query", query)
|
||||||
|
tracertest.CompareSpan(t, execSpan, actualSpan)
|
||||||
|
delete(expectedSpan.Meta, "sql.query")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTransaction(t *testing.T, db *DB, expectedSpan *tracer.Span) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
query := "INSERT INTO city(name) VALUES('New York')"
|
||||||
|
|
||||||
|
// Test Begin
|
||||||
|
tx, err := db.Begin()
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
|
||||||
|
db.Tracer.ForceFlush()
|
||||||
|
traces := db.Transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
actualSpan := spans[0]
|
||||||
|
beginSpan := tracertest.CopySpan(expectedSpan, db.Tracer)
|
||||||
|
beginSpan.Resource = "Begin"
|
||||||
|
tracertest.CompareSpan(t, beginSpan, actualSpan)
|
||||||
|
|
||||||
|
// Test Rollback
|
||||||
|
err = tx.Rollback()
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
|
||||||
|
db.Tracer.ForceFlush()
|
||||||
|
traces = db.Transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans = traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
actualSpan = spans[0]
|
||||||
|
rollbackSpan := tracertest.CopySpan(expectedSpan, db.Tracer)
|
||||||
|
rollbackSpan.Resource = "Rollback"
|
||||||
|
tracertest.CompareSpan(t, rollbackSpan, actualSpan)
|
||||||
|
|
||||||
|
// Test Exec
|
||||||
|
parentSpan := db.Tracer.NewRootSpan("test.parent", "test", "parent")
|
||||||
|
ctx := tracer.ContextWithSpan(context.Background(), parentSpan)
|
||||||
|
|
||||||
|
tx, err = db.BeginTx(ctx, nil)
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
|
||||||
|
_, err = tx.ExecContext(ctx, query)
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
|
||||||
|
parentSpan.Finish() // need to do this else children are not flushed at all
|
||||||
|
|
||||||
|
db.Tracer.ForceFlush()
|
||||||
|
traces = db.Transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans = traces[0]
|
||||||
|
assert.Len(spans, 4)
|
||||||
|
|
||||||
|
for _, s := range spans {
|
||||||
|
if s.Name == expectedSpan.Name && s.Resource == query {
|
||||||
|
actualSpan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(actualSpan)
|
||||||
|
execSpan := tracertest.CopySpan(expectedSpan, db.Tracer)
|
||||||
|
execSpan.Resource = query
|
||||||
|
execSpan.SetMeta("sql.query", query)
|
||||||
|
tracertest.CompareSpan(t, execSpan, actualSpan)
|
||||||
|
delete(expectedSpan.Meta, "sql.query")
|
||||||
|
|
||||||
|
for _, s := range spans {
|
||||||
|
if s.Name == expectedSpan.Name && s.Resource == "Commit" {
|
||||||
|
actualSpan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(actualSpan)
|
||||||
|
commitSpan := tracertest.CopySpan(expectedSpan, db.Tracer)
|
||||||
|
commitSpan.Resource = "Commit"
|
||||||
|
tracertest.CompareSpan(t, commitSpan, actualSpan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB is a struct dedicated for testing
|
||||||
|
type DB struct {
|
||||||
|
*sql.DB
|
||||||
|
Tracer *tracer.Tracer
|
||||||
|
Transport *tracertest.DummyTransport
|
||||||
|
DriverName string
|
||||||
|
}
|
2
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sqlutils/sqlutils.go
generated
vendored
Normal file
2
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sqlutils/sqlutils.go
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package sqlutils share some utils functions for sql packages
|
||||||
|
package sqlutils
|
59
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sqlutils/utils.go
generated
vendored
Normal file
59
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sqlutils/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package sqlutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDriverName returns the driver type.
|
||||||
|
func GetDriverName(driver driver.Driver) string {
|
||||||
|
if driver == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
driverType := fmt.Sprintf("%s", reflect.TypeOf(driver))
|
||||||
|
switch driverType {
|
||||||
|
case "*mysql.MySQLDriver":
|
||||||
|
return "mysql"
|
||||||
|
case "*pq.Driver":
|
||||||
|
return "postgres"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTracedDriverName add the suffix "Traced" to the driver name.
|
||||||
|
func GetTracedDriverName(driverName string) string {
|
||||||
|
return driverName + "Traced"
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDSNAndService(dsn, service string) string {
|
||||||
|
return dsn + "|" + service
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDSNAndService(dsnAndService string) (dsn, service string) {
|
||||||
|
tab := strings.Split(dsnAndService, "|")
|
||||||
|
return tab[0], tab[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// namedValueToValue is a helper function copied from the database/sql package.
|
||||||
|
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
|
||||||
|
dargs := make([]driver.Value, len(named))
|
||||||
|
for n, param := range named {
|
||||||
|
if len(param.Name) > 0 {
|
||||||
|
return nil, errors.New("sql: driver does not support the use of Named Parameters")
|
||||||
|
}
|
||||||
|
dargs[n] = param.Value
|
||||||
|
}
|
||||||
|
return dargs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringInSlice returns true if the string s is in the list.
|
||||||
|
func stringInSlice(list []string, s string) bool {
|
||||||
|
sort.Strings(list)
|
||||||
|
i := sort.SearchStrings(list, s)
|
||||||
|
return i < len(list) && list[i] == s
|
||||||
|
}
|
17
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sqlutils/utils_test.go
generated
vendored
Normal file
17
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/sqlutils/utils_test.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package sqlutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetDriverName(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
assert.Equal("postgres", GetDriverName(&pq.Driver{}))
|
||||||
|
assert.Equal("mysql", GetDriverName(&mysql.MySQLDriver{}))
|
||||||
|
assert.Equal("", GetDriverName(nil))
|
||||||
|
}
|
36
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/utils.go
generated
vendored
Normal file
36
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDSNAndService(dsn, service string) string {
|
||||||
|
return dsn + "|" + service
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDSNAndService(dsnAndService string) (dsn, service string) {
|
||||||
|
tab := strings.Split(dsnAndService, "|")
|
||||||
|
return tab[0], tab[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// namedValueToValue is a helper function copied from the database/sql package.
|
||||||
|
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
|
||||||
|
dargs := make([]driver.Value, len(named))
|
||||||
|
for n, param := range named {
|
||||||
|
if len(param.Name) > 0 {
|
||||||
|
return nil, errors.New("sql: driver does not support the use of Named Parameters")
|
||||||
|
}
|
||||||
|
dargs[n] = param.Value
|
||||||
|
}
|
||||||
|
return dargs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringInSlice returns true if the string s is in the list.
|
||||||
|
func stringInSlice(list []string, s string) bool {
|
||||||
|
sort.Strings(list)
|
||||||
|
i := sort.SearchStrings(list, s)
|
||||||
|
return i < len(list) && list[i] == s
|
||||||
|
}
|
29
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/utils_test.go
generated
vendored
Normal file
29
vendor/github.com/DataDog/dd-trace-go/contrib/database/sql/utils_test.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStringInSlice(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
list := []string{"mysql", "postgres", "pq"}
|
||||||
|
assert.True(stringInSlice(list, "pq"))
|
||||||
|
assert.False(stringInSlice(list, "Postgres"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDSNAndService(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
dsn := "postgres://ubuntu@127.0.0.1:5432/circle_test?sslmode=disable"
|
||||||
|
service := "master-db"
|
||||||
|
|
||||||
|
dsnAndService := "postgres://ubuntu@127.0.0.1:5432/circle_test?sslmode=disable|master-db"
|
||||||
|
assert.Equal(dsnAndService, newDSNAndService(dsn, service))
|
||||||
|
|
||||||
|
actualDSN, actualService := parseDSNAndService(dsnAndService)
|
||||||
|
assert.Equal(dsn, actualDSN)
|
||||||
|
assert.Equal(service, actualService)
|
||||||
|
}
|
59
vendor/github.com/DataDog/dd-trace-go/contrib/garyburd/redigo/example_test.go
generated
vendored
Normal file
59
vendor/github.com/DataDog/dd-trace-go/contrib/garyburd/redigo/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package redigo_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
redigotrace "github.com/DataDog/dd-trace-go/contrib/garyburd/redigo"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To start tracing Redis commands, use the TracedDial function to create a connection,
|
||||||
|
// passing in a service name of choice.
|
||||||
|
func Example() {
|
||||||
|
c, _ := redigotrace.TracedDial("my-redis-backend", tracer.DefaultTracer, "tcp", "127.0.0.1:6379")
|
||||||
|
|
||||||
|
// Emit spans per command by using your Redis connection as usual
|
||||||
|
c.Do("SET", "vehicle", "truck")
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// When passed a context as the final argument, c.Do will emit a span inheriting from 'parent.request'
|
||||||
|
c.Do("SET", "food", "cheese", ctx)
|
||||||
|
root.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleTracedConn() {
|
||||||
|
c, _ := redigotrace.TracedDial("my-redis-backend", tracer.DefaultTracer, "tcp", "127.0.0.1:6379")
|
||||||
|
|
||||||
|
// Emit spans per command by using your Redis connection as usual
|
||||||
|
c.Do("SET", "vehicle", "truck")
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// When passed a context as the final argument, c.Do will emit a span inheriting from 'parent.request'
|
||||||
|
c.Do("SET", "food", "cheese", ctx)
|
||||||
|
root.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternatively, provide a redis URL to the TracedDialURL function
|
||||||
|
func Example_dialURL() {
|
||||||
|
c, _ := redigotrace.TracedDialURL("my-redis-backend", tracer.DefaultTracer, "redis://127.0.0.1:6379")
|
||||||
|
c.Do("SET", "vehicle", "truck")
|
||||||
|
}
|
||||||
|
|
||||||
|
// When using a redigo Pool, set your Dial function to return a traced connection
|
||||||
|
func Example_pool() {
|
||||||
|
pool := &redis.Pool{
|
||||||
|
Dial: func() (redis.Conn, error) {
|
||||||
|
return redigotrace.TracedDial("my-redis-backend", tracer.DefaultTracer, "tcp", "127.0.0.1:6379")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c := pool.Get()
|
||||||
|
|
||||||
|
c.Do("SET", " whiskey", " glass")
|
||||||
|
}
|
131
vendor/github.com/DataDog/dd-trace-go/contrib/garyburd/redigo/redigo.go
generated
vendored
Normal file
131
vendor/github.com/DataDog/dd-trace-go/contrib/garyburd/redigo/redigo.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Package redigo provides tracing for the Redigo Redis client (https://github.com/garyburd/redigo)
|
||||||
|
package redigo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
redis "github.com/garyburd/redigo/redis"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TracedConn is an implementation of the redis.Conn interface that supports tracing
|
||||||
|
type TracedConn struct {
|
||||||
|
redis.Conn
|
||||||
|
p traceParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceParams contains fields and metadata useful for command tracing
|
||||||
|
type traceParams struct {
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
service string
|
||||||
|
network string
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracedDial takes a Conn returned by redis.Dial and configures it to emit spans with the given service name
|
||||||
|
func TracedDial(service string, tracer *tracer.Tracer, network, address string, options ...redis.DialOption) (redis.Conn, error) {
|
||||||
|
c, err := redis.Dial(network, address, options...)
|
||||||
|
addr := strings.Split(address, ":")
|
||||||
|
var host, port string
|
||||||
|
if len(addr) == 2 && addr[1] != "" {
|
||||||
|
port = addr[1]
|
||||||
|
} else {
|
||||||
|
port = "6379"
|
||||||
|
}
|
||||||
|
host = addr[0]
|
||||||
|
tracer.SetServiceInfo(service, "redis", ext.AppTypeDB)
|
||||||
|
tc := TracedConn{c, traceParams{tracer, service, network, host, port}}
|
||||||
|
return tc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracedDialURL takes a Conn returned by redis.DialURL and configures it to emit spans with the given service name
|
||||||
|
func TracedDialURL(service string, tracer *tracer.Tracer, rawurl string, options ...redis.DialOption) (redis.Conn, error) {
|
||||||
|
u, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return TracedConn{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting host and port, usind code from https://github.com/garyburd/redigo/blob/master/redis/conn.go#L226
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
host = u.Host
|
||||||
|
port = "6379"
|
||||||
|
}
|
||||||
|
if host == "" {
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
// Set in redis.DialUrl source code
|
||||||
|
network := "tcp"
|
||||||
|
c, err := redis.DialURL(rawurl, options...)
|
||||||
|
tc := TracedConn{c, traceParams{tracer, service, network, host, port}}
|
||||||
|
return tc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChildSpan creates a span inheriting from the given context. It adds to the span useful metadata about the traced Redis connection
|
||||||
|
func (tc TracedConn) NewChildSpan(ctx context.Context) *tracer.Span {
|
||||||
|
span := tc.p.tracer.NewChildSpanFromContext("redis.command", ctx)
|
||||||
|
span.Service = tc.p.service
|
||||||
|
span.SetMeta("out.network", tc.p.network)
|
||||||
|
span.SetMeta("out.port", tc.p.port)
|
||||||
|
span.SetMeta("out.host", tc.p.host)
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do wraps redis.Conn.Do. It sends a command to the Redis server and returns the received reply.
|
||||||
|
// In the process it emits a span containing key information about the command sent.
|
||||||
|
// When passed a context.Context as the final argument, Do will ensure that any span created
|
||||||
|
// inherits from this context. The rest of the arguments are passed through to the Redis server unchanged
|
||||||
|
func (tc TracedConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||||
|
var ctx context.Context
|
||||||
|
var ok bool
|
||||||
|
if len(args) > 0 {
|
||||||
|
ctx, ok = args[len(args)-1].(context.Context)
|
||||||
|
if ok {
|
||||||
|
args = args[:len(args)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span := tc.NewChildSpan(ctx)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
span.SetMeta("redis.args_length", strconv.Itoa(len(args)))
|
||||||
|
|
||||||
|
if len(commandName) > 0 {
|
||||||
|
span.Resource = commandName
|
||||||
|
} else {
|
||||||
|
// When the command argument to the Do method is "", then the Do method will flush the output buffer
|
||||||
|
// See https://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining
|
||||||
|
span.Resource = "redigo.Conn.Flush"
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.WriteString(commandName)
|
||||||
|
for _, arg := range args {
|
||||||
|
b.WriteString(" ")
|
||||||
|
switch arg := arg.(type) {
|
||||||
|
case string:
|
||||||
|
b.WriteString(arg)
|
||||||
|
case int:
|
||||||
|
b.WriteString(strconv.Itoa(arg))
|
||||||
|
case int32:
|
||||||
|
b.WriteString(strconv.FormatInt(int64(arg), 10))
|
||||||
|
case int64:
|
||||||
|
b.WriteString(strconv.FormatInt(arg, 10))
|
||||||
|
case fmt.Stringer:
|
||||||
|
b.WriteString(arg.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span.SetMeta("redis.raw_command", b.String())
|
||||||
|
return tc.Conn.Do(commandName, args...)
|
||||||
|
}
|
214
vendor/github.com/DataDog/dd-trace-go/contrib/garyburd/redigo/redigo_test.go
generated
vendored
Normal file
214
vendor/github.com/DataDog/dd-trace-go/contrib/garyburd/redigo/redigo_test.go
generated
vendored
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
package redigo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
c, _ := TracedDial("my-service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
c.Do("SET", 1, "truck")
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Resource, "SET")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "SET 1 truck")
|
||||||
|
assert.Equal(span.GetMeta("redis.args_length"), "2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandError(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
c, _ := TracedDial("my-service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
_, err := c.Do("NOT_A_COMMAND", context.Background())
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
|
||||||
|
assert.Equal(int32(span.Error), int32(1))
|
||||||
|
assert.Equal(span.GetMeta("error.msg"), err.Error())
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Resource, "NOT_A_COMMAND")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "NOT_A_COMMAND")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectionError(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, _ := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
_, err := TracedDial("redis-service", testTracer, "tcp", "127.0.0.1:1000")
|
||||||
|
|
||||||
|
assert.Contains(err.Error(), "dial tcp 127.0.0.1:1000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInheritance(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
// Parent span
|
||||||
|
ctx := context.Background()
|
||||||
|
parent_span := testTracer.NewChildSpanFromContext("parent_span", ctx)
|
||||||
|
ctx = tracer.ContextWithSpan(ctx, parent_span)
|
||||||
|
client, _ := TracedDial("my_service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
client.Do("SET", "water", "bottle", ctx)
|
||||||
|
parent_span.Finish()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
|
||||||
|
var child_span, pspan *tracer.Span
|
||||||
|
for _, s := range spans {
|
||||||
|
// order of traces in buffer is not garanteed
|
||||||
|
switch s.Name {
|
||||||
|
case "redis.command":
|
||||||
|
child_span = s
|
||||||
|
case "parent_span":
|
||||||
|
pspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotNil(child_span, "there should be a child redis.command span")
|
||||||
|
assert.NotNil(child_span, "there should be a parent span")
|
||||||
|
|
||||||
|
assert.Equal(child_span.ParentID, pspan.SpanID)
|
||||||
|
assert.Equal(child_span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(child_span.GetMeta("out.port"), "56379")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandsToSring(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
stringify_test := TestStruct{Cpython: 57, Cgo: 8}
|
||||||
|
c, _ := TracedDial("my-service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
c.Do("SADD", "testSet", "a", int(0), int32(1), int64(2), stringify_test, context.Background())
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Resource, "SADD")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "SADD testSet a 0 1 2 [57, 8]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPool(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
pool := &redis.Pool{
|
||||||
|
MaxIdle: 2,
|
||||||
|
MaxActive: 3,
|
||||||
|
IdleTimeout: 23,
|
||||||
|
Wait: true,
|
||||||
|
Dial: func() (redis.Conn, error) {
|
||||||
|
return TracedDial("my-service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := pool.Get()
|
||||||
|
pc.Do("SET", " whiskey", " glass", context.Background())
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
assert.Equal(span.GetMeta("out.network"), "tcp")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTracingDialUrl(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
url := "redis://127.0.0.1:56379"
|
||||||
|
client, _ := TracedDialURL("redis-service", testTracer, url)
|
||||||
|
client.Do("SET", "ONE", " TWO", context.Background())
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStruct implements String interface
|
||||||
|
type TestStruct struct {
|
||||||
|
Cpython int
|
||||||
|
Cgo int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts TestStruct) String() string {
|
||||||
|
return fmt.Sprintf("[%d, %d]", ts.Cpython, ts.Cgo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
58
vendor/github.com/DataDog/dd-trace-go/contrib/gin-gonic/gin/example_test.go
generated
vendored
Normal file
58
vendor/github.com/DataDog/dd-trace-go/contrib/gin-gonic/gin/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package gin_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
gintrace "github.com/DataDog/dd-trace-go/contrib/gin-gonic/gin"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To start tracing requests, add the trace middleware to your Gin router.
|
||||||
|
func Example() {
|
||||||
|
// Create your router and use the middleware.
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(gintrace.Middleware("my-web-app"))
|
||||||
|
|
||||||
|
r.GET("/hello", func(c *gin.Context) {
|
||||||
|
c.String(200, "hello world!")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Profit!
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleHTML() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(gintrace.Middleware("my-web-app"))
|
||||||
|
r.LoadHTMLGlob("templates/*")
|
||||||
|
|
||||||
|
r.GET("/index", func(c *gin.Context) {
|
||||||
|
// This will render the html and trace the execution time.
|
||||||
|
gintrace.HTML(c, 200, "index.tmpl", gin.H{
|
||||||
|
"title": "Main website",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSpanDefault() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(gintrace.Middleware("image-encoder"))
|
||||||
|
|
||||||
|
r.GET("/image/encode", func(c *gin.Context) {
|
||||||
|
// The middleware patches a span to the request. Let's add some metadata,
|
||||||
|
// and create a child span.
|
||||||
|
span := gintrace.SpanDefault(c)
|
||||||
|
span.SetMeta("user.handle", "admin")
|
||||||
|
span.SetMeta("user.id", "1234")
|
||||||
|
|
||||||
|
encodeSpan := tracer.NewChildSpan("image.encode", span)
|
||||||
|
// encode a image
|
||||||
|
encodeSpan.Finish()
|
||||||
|
|
||||||
|
uploadSpan := tracer.NewChildSpan("image.upload", span)
|
||||||
|
// upload the image
|
||||||
|
uploadSpan.Finish()
|
||||||
|
|
||||||
|
c.String(200, "ok!")
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
143
vendor/github.com/DataDog/dd-trace-go/contrib/gin-gonic/gin/gintrace.go
generated
vendored
Normal file
143
vendor/github.com/DataDog/dd-trace-go/contrib/gin-gonic/gin/gintrace.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
// Package gin provides tracing middleware for the Gin web framework.
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// key is the string that we'll use to store spans in the tracer.
|
||||||
|
var key = "datadog_trace_span"
|
||||||
|
|
||||||
|
// Middleware returns middleware that will trace requests with the default
|
||||||
|
// tracer.
|
||||||
|
func Middleware(service string) gin.HandlerFunc {
|
||||||
|
return MiddlewareTracer(service, tracer.DefaultTracer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiddlewareTracer returns middleware that will trace requests with the given
|
||||||
|
// tracer.
|
||||||
|
func MiddlewareTracer(service string, t *tracer.Tracer) gin.HandlerFunc {
|
||||||
|
t.SetServiceInfo(service, "gin-gonic", ext.AppTypeWeb)
|
||||||
|
mw := newMiddleware(service, t)
|
||||||
|
return mw.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// middleware implements gin middleware.
|
||||||
|
type middleware struct {
|
||||||
|
service string
|
||||||
|
trc *tracer.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMiddleware(service string, trc *tracer.Tracer) *middleware {
|
||||||
|
return &middleware{
|
||||||
|
service: service,
|
||||||
|
trc: trc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle is a gin HandlerFunc that will add tracing to the given request.
|
||||||
|
func (m *middleware) Handle(c *gin.Context) {
|
||||||
|
|
||||||
|
// bail if not enabled
|
||||||
|
if !m.trc.Enabled() {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME[matt] the handler name is a bit unwieldy and uses reflection
|
||||||
|
// under the hood. might be better to tackle this task and do it right
|
||||||
|
// so we can end up with "user/:user/whatever" instead of
|
||||||
|
// "github.com/foobar/blah"
|
||||||
|
//
|
||||||
|
// See here: https://github.com/gin-gonic/gin/issues/649
|
||||||
|
resource := c.HandlerName()
|
||||||
|
|
||||||
|
// Create our span and patch it to the context for downstream.
|
||||||
|
span := m.trc.NewRootSpan("gin.request", m.service, resource)
|
||||||
|
c.Set(key, span)
|
||||||
|
|
||||||
|
// Pass along the request.
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// Set http tags.
|
||||||
|
span.SetMeta(ext.HTTPCode, strconv.Itoa(c.Writer.Status()))
|
||||||
|
span.SetMeta(ext.HTTPMethod, c.Request.Method)
|
||||||
|
span.SetMeta(ext.HTTPURL, c.Request.URL.Path)
|
||||||
|
|
||||||
|
// Set any error information.
|
||||||
|
var err error
|
||||||
|
if len(c.Errors) > 0 {
|
||||||
|
span.SetMeta("gin.errors", c.Errors.String()) // set all errors
|
||||||
|
err = c.Errors[0] // but use the first for standard fields
|
||||||
|
}
|
||||||
|
span.FinishWithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span returns the Span stored in the given Context and true. If it doesn't exist,
|
||||||
|
// it will returns (nil, false)
|
||||||
|
func Span(c *gin.Context) (*tracer.Span, bool) {
|
||||||
|
if c == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := c.Get(key)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
switch span := s.(type) {
|
||||||
|
case *tracer.Span:
|
||||||
|
return span, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpanDefault returns the span stored in the given Context. If none exists,
|
||||||
|
// it will return an empty span.
|
||||||
|
func SpanDefault(c *gin.Context) *tracer.Span {
|
||||||
|
span, ok := Span(c)
|
||||||
|
if !ok {
|
||||||
|
return &tracer.Span{}
|
||||||
|
}
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChildSpan will create a span that is the child of the span stored in
|
||||||
|
// the context.
|
||||||
|
func NewChildSpan(name string, c *gin.Context) *tracer.Span {
|
||||||
|
span, ok := Span(c)
|
||||||
|
if !ok {
|
||||||
|
return &tracer.Span{}
|
||||||
|
}
|
||||||
|
return span.Tracer().NewChildSpan(name, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML will trace the rendering of the template as a child of the span in the
|
||||||
|
// given context.
|
||||||
|
func HTML(c *gin.Context, code int, name string, obj interface{}) {
|
||||||
|
span, _ := Span(c)
|
||||||
|
if span == nil {
|
||||||
|
c.HTML(code, name, obj)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
child := span.Tracer().NewChildSpan("gin.render.html", span)
|
||||||
|
child.SetMeta("go.template", name)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err := fmt.Errorf("error rendering tmpl:%s: %s", name, r)
|
||||||
|
child.FinishWithErr(err)
|
||||||
|
panic(r)
|
||||||
|
} else {
|
||||||
|
child.Finish()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// render
|
||||||
|
c.HTML(code, name, obj)
|
||||||
|
}
|
250
vendor/github.com/DataDog/dd-trace-go/contrib/gin-gonic/gin/gintrace_test.go
generated
vendored
Normal file
250
vendor/github.com/DataDog/dd-trace-go/contrib/gin-gonic/gin/gintrace_test.go
generated
vendored
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gin.SetMode(gin.ReleaseMode) // silence annoying log msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChildSpan(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, _ := getTestTracer()
|
||||||
|
|
||||||
|
middleware := newMiddleware("foobar", testTracer)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
router.GET("/user/:id", func(c *gin.Context) {
|
||||||
|
span, ok := tracer.SpanFromContext(c)
|
||||||
|
assert.True(ok)
|
||||||
|
assert.NotNil(span)
|
||||||
|
})
|
||||||
|
|
||||||
|
r := httptest.NewRequest("GET", "/user/123", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrace200(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
|
||||||
|
middleware := newMiddleware("foobar", testTracer)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
router.GET("/user/:id", func(c *gin.Context) {
|
||||||
|
// assert we patch the span on the request context.
|
||||||
|
span := SpanDefault(c)
|
||||||
|
span.SetMeta("test.gin", "ginny")
|
||||||
|
assert.Equal(span.Service, "foobar")
|
||||||
|
id := c.Param("id")
|
||||||
|
c.Writer.Write([]byte(id))
|
||||||
|
})
|
||||||
|
|
||||||
|
r := httptest.NewRequest("GET", "/user/123", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// do and verify the request
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 200)
|
||||||
|
|
||||||
|
// verify traces look good
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
if len(spans) < 1 {
|
||||||
|
t.Fatalf("no spans")
|
||||||
|
}
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Service, "foobar")
|
||||||
|
assert.Equal(s.Name, "gin.request")
|
||||||
|
// FIXME[matt] would be much nicer to have "/user/:id" here
|
||||||
|
assert.True(strings.Contains(s.Resource, "gin.TestTrace200"))
|
||||||
|
assert.Equal(s.GetMeta("test.gin"), "ginny")
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "200")
|
||||||
|
assert.Equal(s.GetMeta("http.method"), "GET")
|
||||||
|
assert.Equal(s.GetMeta("http.url"), "/user/123")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabled(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetEnabled(false)
|
||||||
|
|
||||||
|
middleware := newMiddleware("foobar", testTracer)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
|
span, ok := Span(c)
|
||||||
|
assert.Nil(span)
|
||||||
|
assert.False(ok)
|
||||||
|
c.Writer.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
|
||||||
|
r := httptest.NewRequest("GET", "/ping", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// do and verify the request
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 200)
|
||||||
|
|
||||||
|
// verify traces look good
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
spans := testTransport.Traces()
|
||||||
|
assert.Len(spans, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
|
||||||
|
// setup
|
||||||
|
middleware := newMiddleware("foobar", testTracer)
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
|
||||||
|
// a handler with an error and make the requests
|
||||||
|
router.GET("/err", func(c *gin.Context) {
|
||||||
|
c.AbortWithError(500, errors.New("oh no"))
|
||||||
|
})
|
||||||
|
r := httptest.NewRequest("GET", "/err", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 500)
|
||||||
|
|
||||||
|
// verify the errors and status are correct
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
if len(spans) < 1 {
|
||||||
|
t.Fatalf("no spans")
|
||||||
|
}
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Service, "foobar")
|
||||||
|
assert.Equal(s.Name, "gin.request")
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "500")
|
||||||
|
assert.Equal(s.GetMeta(ext.ErrorMsg), "oh no")
|
||||||
|
assert.Equal(s.Error, int32(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTML(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
|
||||||
|
// setup
|
||||||
|
middleware := newMiddleware("tmplservice", testTracer)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
|
||||||
|
// add a template
|
||||||
|
tmpl := template.Must(template.New("hello").Parse("hello {{.}}"))
|
||||||
|
router.SetHTMLTemplate(tmpl)
|
||||||
|
|
||||||
|
// a handler with an error and make the requests
|
||||||
|
router.GET("/hello", func(c *gin.Context) {
|
||||||
|
HTML(c, 200, "hello", "world")
|
||||||
|
})
|
||||||
|
r := httptest.NewRequest("GET", "/hello", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 200)
|
||||||
|
assert.Equal("hello world", w.Body.String())
|
||||||
|
|
||||||
|
// verify the errors and status are correct
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
for _, s := range spans {
|
||||||
|
assert.Equal(s.Service, "tmplservice")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tspan *tracer.Span
|
||||||
|
for _, s := range spans {
|
||||||
|
// we need to pick up the span we're searching for, as the
|
||||||
|
// order is not garanteed within the buffer
|
||||||
|
if s.Name == "gin.render.html" {
|
||||||
|
tspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotNil(tspan, "we should have found a span with name gin.render.html")
|
||||||
|
assert.Equal(tspan.GetMeta("go.template"), "hello")
|
||||||
|
fmt.Println(spans)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSpanNotInstrumented(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
router := gin.New()
|
||||||
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
|
// Assert we don't have a span on the context.
|
||||||
|
s, ok := Span(c)
|
||||||
|
assert.False(ok)
|
||||||
|
assert.Nil(s)
|
||||||
|
// and the default span is empty
|
||||||
|
s = SpanDefault(c)
|
||||||
|
assert.Equal(s.Service, "")
|
||||||
|
c.Writer.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
r := httptest.NewRequest("GET", "/ping", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
81
vendor/github.com/DataDog/dd-trace-go/contrib/go-redis/redis/example_test.go
generated
vendored
Normal file
81
vendor/github.com/DataDog/dd-trace-go/contrib/go-redis/redis/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package redis_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
redistrace "github.com/DataDog/dd-trace-go/contrib/go-redis/redis"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To start tracing Redis commands, use the NewTracedClient function to create a traced Redis clienty,
|
||||||
|
// passing in a service name of choice.
|
||||||
|
func Example() {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:6379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
c := redistrace.NewTracedClient(opts, tracer.DefaultTracer, "my-redis-backend")
|
||||||
|
// Emit spans per command by using your Redis connection as usual
|
||||||
|
c.Set("test_key", "test_value", 0)
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// When set with a context, the traced client will emit a span inheriting from 'parent.request'
|
||||||
|
c.SetContext(ctx)
|
||||||
|
c.Set("food", "cheese", 0)
|
||||||
|
root.Finish()
|
||||||
|
|
||||||
|
// Contexts can be easily passed between Datadog integrations
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(gintrace.Middleware("web-admin"))
|
||||||
|
client := redistrace.NewTracedClient(opts, tracer.DefaultTracer, "redis-img-backend")
|
||||||
|
|
||||||
|
r.GET("/user/settings/:id", func(ctx *gin.Context) {
|
||||||
|
// create a span that is a child of your http request
|
||||||
|
client.SetContext(ctx)
|
||||||
|
client.Get(fmt.Sprintf("cached_user_details_%s", ctx.Param("id")))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can also trace Redis Pipelines
|
||||||
|
func Example_pipeline() {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:6379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
c := redistrace.NewTracedClient(opts, tracer.DefaultTracer, "my-redis-backend")
|
||||||
|
// pipe is a TracedPipeliner
|
||||||
|
pipe := c.Pipeline()
|
||||||
|
pipe.Incr("pipeline_counter")
|
||||||
|
pipe.Expire("pipeline_counter", time.Hour)
|
||||||
|
|
||||||
|
pipe.Exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewTracedClient() {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:6379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
c := redistrace.NewTracedClient(opts, tracer.DefaultTracer, "my-redis-backend")
|
||||||
|
// Emit spans per command by using your Redis connection as usual
|
||||||
|
c.Set("test_key", "test_value", 0)
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// When set with a context, the traced client will emit a span inheriting from 'parent.request'
|
||||||
|
c.SetContext(ctx)
|
||||||
|
c.Set("food", "cheese", 0)
|
||||||
|
root.Finish()
|
||||||
|
}
|
151
vendor/github.com/DataDog/dd-trace-go/contrib/go-redis/redis/redis.go
generated
vendored
Normal file
151
vendor/github.com/DataDog/dd-trace-go/contrib/go-redis/redis/redis.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
// Package redis provides tracing for the go-redis Redis client (https://github.com/go-redis/redis)
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TracedClient is used to trace requests to a redis server.
|
||||||
|
type TracedClient struct {
|
||||||
|
*redis.Client
|
||||||
|
traceParams traceParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracedPipeline is used to trace pipelines executed on a redis server.
|
||||||
|
type TracedPipeliner struct {
|
||||||
|
redis.Pipeliner
|
||||||
|
traceParams traceParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type traceParams struct {
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
db string
|
||||||
|
service string
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTracedClient takes a Client returned by redis.NewClient and configures it to emit spans under the given service name
|
||||||
|
func NewTracedClient(opt *redis.Options, t *tracer.Tracer, service string) *TracedClient {
|
||||||
|
var host, port string
|
||||||
|
addr := strings.Split(opt.Addr, ":")
|
||||||
|
if len(addr) == 2 && addr[1] != "" {
|
||||||
|
port = addr[1]
|
||||||
|
} else {
|
||||||
|
port = "6379"
|
||||||
|
}
|
||||||
|
host = addr[0]
|
||||||
|
db := strconv.Itoa(opt.DB)
|
||||||
|
|
||||||
|
client := redis.NewClient(opt)
|
||||||
|
t.SetServiceInfo(service, "redis", ext.AppTypeDB)
|
||||||
|
tc := &TracedClient{
|
||||||
|
client,
|
||||||
|
traceParams{
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
db,
|
||||||
|
service,
|
||||||
|
t},
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.Client.WrapProcess(createWrapperFromClient(tc))
|
||||||
|
return tc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipeline creates a TracedPipeline from a TracedClient
|
||||||
|
func (c *TracedClient) Pipeline() *TracedPipeliner {
|
||||||
|
return &TracedPipeliner{
|
||||||
|
c.Client.Pipeline(),
|
||||||
|
c.traceParams,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecWithContext calls Pipeline.Exec(). It ensures that the resulting Redis calls
|
||||||
|
// are traced, and that emitted spans are children of the given Context
|
||||||
|
func (c *TracedPipeliner) ExecWithContext(ctx context.Context) ([]redis.Cmder, error) {
|
||||||
|
span := c.traceParams.tracer.NewChildSpanFromContext("redis.command", ctx)
|
||||||
|
span.Service = c.traceParams.service
|
||||||
|
|
||||||
|
span.SetMeta("out.host", c.traceParams.host)
|
||||||
|
span.SetMeta("out.port", c.traceParams.port)
|
||||||
|
span.SetMeta("out.db", c.traceParams.db)
|
||||||
|
|
||||||
|
cmds, err := c.Pipeliner.Exec()
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
span.Resource = String(cmds)
|
||||||
|
span.SetMeta("redis.pipeline_length", strconv.Itoa(len(cmds)))
|
||||||
|
span.Finish()
|
||||||
|
return cmds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec calls Pipeline.Exec() ensuring that the resulting Redis calls are traced
|
||||||
|
func (c *TracedPipeliner) Exec() ([]redis.Cmder, error) {
|
||||||
|
span := c.traceParams.tracer.NewRootSpan("redis.command", c.traceParams.service, "redis")
|
||||||
|
|
||||||
|
span.SetMeta("out.host", c.traceParams.host)
|
||||||
|
span.SetMeta("out.port", c.traceParams.port)
|
||||||
|
span.SetMeta("out.db", c.traceParams.db)
|
||||||
|
|
||||||
|
cmds, err := c.Pipeliner.Exec()
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
span.Resource = String(cmds)
|
||||||
|
span.SetMeta("redis.pipeline_length", strconv.Itoa(len(cmds)))
|
||||||
|
span.Finish()
|
||||||
|
return cmds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of a slice of redis Commands, separated by newlines
|
||||||
|
func String(cmds []redis.Cmder) string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
b.WriteString(cmd.String())
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext sets a context on a TracedClient. Use it to ensure that emitted spans have the correct parent
|
||||||
|
func (c *TracedClient) SetContext(ctx context.Context) {
|
||||||
|
c.Client = c.Client.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createWrapperFromClient wraps tracing into redis.Process().
|
||||||
|
func createWrapperFromClient(tc *TracedClient) func(oldProcess func(cmd redis.Cmder) error) func(cmd redis.Cmder) error {
|
||||||
|
return func(oldProcess func(cmd redis.Cmder) error) func(cmd redis.Cmder) error {
|
||||||
|
return func(cmd redis.Cmder) error {
|
||||||
|
ctx := tc.Client.Context()
|
||||||
|
|
||||||
|
var resource string
|
||||||
|
resource = strings.Split(cmd.String(), " ")[0]
|
||||||
|
args_length := len(strings.Split(cmd.String(), " ")) - 1
|
||||||
|
span := tc.traceParams.tracer.NewChildSpanFromContext("redis.command", ctx)
|
||||||
|
|
||||||
|
span.Service = tc.traceParams.service
|
||||||
|
span.Resource = resource
|
||||||
|
|
||||||
|
span.SetMeta("redis.raw_command", cmd.String())
|
||||||
|
span.SetMeta("redis.args_length", strconv.Itoa(args_length))
|
||||||
|
span.SetMeta("out.host", tc.traceParams.host)
|
||||||
|
span.SetMeta("out.port", tc.traceParams.port)
|
||||||
|
span.SetMeta("out.db", tc.traceParams.db)
|
||||||
|
|
||||||
|
err := oldProcess(cmd)
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
span.Finish()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
228
vendor/github.com/DataDog/dd-trace-go/contrib/go-redis/redis/redis_test.go
generated
vendored
Normal file
228
vendor/github.com/DataDog/dd-trace-go/contrib/go-redis/redis/redis_test.go
generated
vendored
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
client.Set("test_key", "test_value", 0)
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
span := spans[0]
|
||||||
|
assert.Equal(span.Service, "my-redis")
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "set test_key test_value: ")
|
||||||
|
assert.Equal(span.GetMeta("redis.args_length"), "3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPipeline(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
pipeline := client.Pipeline()
|
||||||
|
pipeline.Expire("pipeline_counter", time.Hour)
|
||||||
|
|
||||||
|
// Exec with context test
|
||||||
|
pipeline.ExecWithContext(context.Background())
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
span := spans[0]
|
||||||
|
assert.Equal(span.Service, "my-redis")
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.pipeline_length"), "1")
|
||||||
|
assert.Equal(span.Resource, "expire pipeline_counter 3600: false\n")
|
||||||
|
|
||||||
|
pipeline.Expire("pipeline_counter", time.Hour)
|
||||||
|
pipeline.Expire("pipeline_counter_1", time.Minute)
|
||||||
|
|
||||||
|
// Rewriting Exec
|
||||||
|
pipeline.Exec()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces = testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans = traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
span = spans[0]
|
||||||
|
assert.Equal(span.Service, "my-redis")
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.GetMeta("redis.pipeline_length"), "2")
|
||||||
|
assert.Equal(span.Resource, "expire pipeline_counter 3600: false\nexpire pipeline_counter_1 60: false\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChildSpan(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
// Parent span
|
||||||
|
ctx := context.Background()
|
||||||
|
parent_span := testTracer.NewChildSpanFromContext("parent_span", ctx)
|
||||||
|
ctx = tracer.ContextWithSpan(ctx, parent_span)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
client.SetContext(ctx)
|
||||||
|
|
||||||
|
client.Set("test_key", "test_value", 0)
|
||||||
|
parent_span.Finish()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
|
||||||
|
var child_span, pspan *tracer.Span
|
||||||
|
for _, s := range spans {
|
||||||
|
// order of traces in buffer is not garanteed
|
||||||
|
switch s.Name {
|
||||||
|
case "redis.command":
|
||||||
|
child_span = s
|
||||||
|
case "parent_span":
|
||||||
|
pspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotNil(child_span, "there should be a child redis.command span")
|
||||||
|
assert.NotNil(child_span, "there should be a parent span")
|
||||||
|
|
||||||
|
assert.Equal(child_span.ParentID, pspan.SpanID)
|
||||||
|
assert.Equal(child_span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(child_span.GetMeta("out.port"), "56379")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleCommands(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
client.Set("test_key", "test_value", 0)
|
||||||
|
client.Get("test_key")
|
||||||
|
client.Incr("int_key")
|
||||||
|
client.ClientList()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 4)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
// Checking all commands were recorded
|
||||||
|
var commands [4]string
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
commands[i] = traces[i][0].GetMeta("redis.raw_command")
|
||||||
|
}
|
||||||
|
assert.Contains(commands, "set test_key test_value: ")
|
||||||
|
assert.Contains(commands, "get test_key: ")
|
||||||
|
assert.Contains(commands, "incr int_key: 0")
|
||||||
|
assert.Contains(commands, "client list: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
err := client.Get("non_existent_key")
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
|
||||||
|
assert.Equal(int32(span.Error), int32(1))
|
||||||
|
assert.Equal(span.GetMeta("error.msg"), err.Err().Error())
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "get non_existent_key: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
27
vendor/github.com/DataDog/dd-trace-go/contrib/gocql/gocql/example_test.go
generated
vendored
Normal file
27
vendor/github.com/DataDog/dd-trace-go/contrib/gocql/gocql/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package gocql_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
gocqltrace "github.com/DataDog/dd-trace-go/contrib/gocql/gocql"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To trace Cassandra commands, use our query wrapper TraceQuery.
|
||||||
|
func Example() {
|
||||||
|
// Initialise a Cassandra session as usual, create a query.
|
||||||
|
cluster := gocql.NewCluster("127.0.0.1")
|
||||||
|
session, _ := cluster.CreateSession()
|
||||||
|
query := session.Query("CREATE KEYSPACE if not exists trace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor': 1}")
|
||||||
|
|
||||||
|
// Use context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// Wrap the query to trace it and pass the context for inheritance
|
||||||
|
tracedQuery := gocqltrace.TraceQuery("ServiceName", tracer.DefaultTracer, query)
|
||||||
|
tracedQuery.WithContext(ctx)
|
||||||
|
|
||||||
|
// Execute your query as usual
|
||||||
|
tracedQuery.Exec()
|
||||||
|
}
|
146
vendor/github.com/DataDog/dd-trace-go/contrib/gocql/gocql/gocql.go
generated
vendored
Normal file
146
vendor/github.com/DataDog/dd-trace-go/contrib/gocql/gocql/gocql.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
// Package gocql provides tracing for the Cassandra Gocql client (https://github.com/gocql/gocql)
|
||||||
|
package gocql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TracedQuery inherits from gocql.Query, it keeps the tracer and the context.
|
||||||
|
type TracedQuery struct {
|
||||||
|
*gocql.Query
|
||||||
|
p traceParams
|
||||||
|
traceContext context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracedIter inherits from gocql.Iter and contains a span.
|
||||||
|
type TracedIter struct {
|
||||||
|
*gocql.Iter
|
||||||
|
span *tracer.Span
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceParams containes fields and metadata useful for command tracing
|
||||||
|
type traceParams struct {
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
service string
|
||||||
|
keyspace string
|
||||||
|
paginated string
|
||||||
|
consistancy string
|
||||||
|
query string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceQuery wraps a gocql.Query into a TracedQuery
|
||||||
|
func TraceQuery(service string, tracer *tracer.Tracer, q *gocql.Query) *TracedQuery {
|
||||||
|
stringQuery := `"` + strings.SplitN(q.String(), "\"", 3)[1] + `"`
|
||||||
|
stringQuery, err := strconv.Unquote(stringQuery)
|
||||||
|
if err != nil {
|
||||||
|
// An invalid string, so that the trace is not dropped
|
||||||
|
// due to having an empty resource
|
||||||
|
stringQuery = "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
tq := &TracedQuery{q, traceParams{tracer, service, "", "false", strconv.Itoa(int(q.GetConsistency())), stringQuery}, context.Background()}
|
||||||
|
tracer.SetServiceInfo(service, ext.CassandraType, ext.AppTypeDB)
|
||||||
|
return tq
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext rewrites the original function so that ctx can be used for inheritance
|
||||||
|
func (tq *TracedQuery) WithContext(ctx context.Context) *TracedQuery {
|
||||||
|
tq.traceContext = ctx
|
||||||
|
tq.Query.WithContext(ctx)
|
||||||
|
return tq
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageState rewrites the original function so that spans are aware of the change.
|
||||||
|
func (tq *TracedQuery) PageState(state []byte) *TracedQuery {
|
||||||
|
tq.p.paginated = "true"
|
||||||
|
tq.Query = tq.Query.PageState(state)
|
||||||
|
return tq
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChildSpan creates a new span from the traceParams and the context.
|
||||||
|
func (tq *TracedQuery) NewChildSpan(ctx context.Context) *tracer.Span {
|
||||||
|
span := tq.p.tracer.NewChildSpanFromContext(ext.CassandraQuery, ctx)
|
||||||
|
span.Type = ext.CassandraType
|
||||||
|
span.Service = tq.p.service
|
||||||
|
span.Resource = tq.p.query
|
||||||
|
span.SetMeta(ext.CassandraPaginated, tq.p.paginated)
|
||||||
|
span.SetMeta(ext.CassandraKeyspace, tq.p.keyspace)
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec is rewritten so that it passes by our custom Iter
|
||||||
|
func (tq *TracedQuery) Exec() error {
|
||||||
|
return tq.Iter().Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapScan wraps in a span query.MapScan call.
|
||||||
|
func (tq *TracedQuery) MapScan(m map[string]interface{}) error {
|
||||||
|
span := tq.NewChildSpan(tq.traceContext)
|
||||||
|
defer span.Finish()
|
||||||
|
err := tq.Query.MapScan(m)
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan wraps in a span query.Scan call.
|
||||||
|
func (tq *TracedQuery) Scan(dest ...interface{}) error {
|
||||||
|
span := tq.NewChildSpan(tq.traceContext)
|
||||||
|
defer span.Finish()
|
||||||
|
err := tq.Query.Scan(dest...)
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanCAS wraps in a span query.ScanCAS call.
|
||||||
|
func (tq *TracedQuery) ScanCAS(dest ...interface{}) (applied bool, err error) {
|
||||||
|
span := tq.NewChildSpan(tq.traceContext)
|
||||||
|
defer span.Finish()
|
||||||
|
applied, err = tq.Query.ScanCAS(dest...)
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
return applied, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iter starts a new span at query.Iter call.
|
||||||
|
func (tq *TracedQuery) Iter() *TracedIter {
|
||||||
|
span := tq.NewChildSpan(tq.traceContext)
|
||||||
|
iter := tq.Query.Iter()
|
||||||
|
span.SetMeta(ext.CassandraRowCount, strconv.Itoa(iter.NumRows()))
|
||||||
|
span.SetMeta(ext.CassandraConsistencyLevel, strconv.Itoa(int(tq.GetConsistency())))
|
||||||
|
|
||||||
|
columns := iter.Columns()
|
||||||
|
if len(columns) > 0 {
|
||||||
|
span.SetMeta(ext.CassandraKeyspace, columns[0].Keyspace)
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
tIter := &TracedIter{iter, span}
|
||||||
|
if tIter.Host() != nil {
|
||||||
|
tIter.span.SetMeta(ext.TargetHost, tIter.Iter.Host().HostID())
|
||||||
|
tIter.span.SetMeta(ext.TargetPort, strconv.Itoa(tIter.Iter.Host().Port()))
|
||||||
|
tIter.span.SetMeta(ext.CassandraCluster, tIter.Iter.Host().DataCenter())
|
||||||
|
|
||||||
|
}
|
||||||
|
return tIter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the TracedIter and finish the span created on Iter call.
|
||||||
|
func (tIter *TracedIter) Close() error {
|
||||||
|
err := tIter.Iter.Close()
|
||||||
|
if err != nil {
|
||||||
|
tIter.span.SetError(err)
|
||||||
|
}
|
||||||
|
tIter.span.Finish()
|
||||||
|
return err
|
||||||
|
}
|
144
vendor/github.com/DataDog/dd-trace-go/contrib/gocql/gocql/gocql_test.go
generated
vendored
Normal file
144
vendor/github.com/DataDog/dd-trace-go/contrib/gocql/gocql/gocql_test.go
generated
vendored
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
package gocql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
CASSANDRA_HOST = "127.0.0.1:59042"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCassandraCluster() *gocql.ClusterConfig {
|
||||||
|
cluster := gocql.NewCluster(CASSANDRA_HOST)
|
||||||
|
// the InitialHostLookup must be disabled in newer versions of
|
||||||
|
// gocql otherwise "no connections were made when creating the session"
|
||||||
|
// error is returned for Cassandra misconfiguration (that we don't need
|
||||||
|
// since we're testing another behavior and not the client).
|
||||||
|
// Check: https://github.com/gocql/gocql/issues/946
|
||||||
|
cluster.DisableInitialHostLookup = true
|
||||||
|
return cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMain sets up the Keyspace and table if they do not exist
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
cluster := newCassandraCluster()
|
||||||
|
session, _ := cluster.CreateSession()
|
||||||
|
|
||||||
|
// Ensures test keyspace and table person exists.
|
||||||
|
session.Query("CREATE KEYSPACE if not exists trace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor': 1}").Exec()
|
||||||
|
session.Query("CREATE TABLE if not exists trace.person (name text PRIMARY KEY, age int, description text)").Exec()
|
||||||
|
session.Query("INSERT INTO trace.person (name, age, description) VALUES ('Cassandra', 100, 'A cruel mistress')").Exec()
|
||||||
|
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorWrapper(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
cluster := newCassandraCluster()
|
||||||
|
session, _ := cluster.CreateSession()
|
||||||
|
q := session.Query("CREATE KEYSPACE trace WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'datacenter1' : 1 };")
|
||||||
|
err := TraceQuery("ServiceName", testTracer, q).Exec()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
|
||||||
|
assert.Equal(int32(span.Error), int32(1))
|
||||||
|
assert.Equal(span.GetMeta("error.msg"), err.Error())
|
||||||
|
assert.Equal(span.Name, ext.CassandraQuery)
|
||||||
|
assert.Equal(span.Resource, "CREATE KEYSPACE trace WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'datacenter1' : 1 };")
|
||||||
|
assert.Equal(span.Service, "ServiceName")
|
||||||
|
assert.Equal(span.GetMeta(ext.CassandraConsistencyLevel), "4")
|
||||||
|
assert.Equal(span.GetMeta(ext.CassandraPaginated), "false")
|
||||||
|
|
||||||
|
// Not added in case of an error
|
||||||
|
assert.Equal(span.GetMeta(ext.TargetHost), "")
|
||||||
|
assert.Equal(span.GetMeta(ext.TargetPort), "")
|
||||||
|
assert.Equal(span.GetMeta(ext.CassandraCluster), "")
|
||||||
|
assert.Equal(span.GetMeta(ext.CassandraKeyspace), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChildWrapperSpan(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
// Parent span
|
||||||
|
ctx := context.Background()
|
||||||
|
parentSpan := testTracer.NewChildSpanFromContext("parentSpan", ctx)
|
||||||
|
ctx = tracer.ContextWithSpan(ctx, parentSpan)
|
||||||
|
|
||||||
|
cluster := newCassandraCluster()
|
||||||
|
session, _ := cluster.CreateSession()
|
||||||
|
q := session.Query("SELECT * from trace.person")
|
||||||
|
tq := TraceQuery("TestServiceName", testTracer, q)
|
||||||
|
tq.WithContext(ctx).Exec()
|
||||||
|
parentSpan.Finish()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
|
||||||
|
var childSpan, pSpan *tracer.Span
|
||||||
|
if spans[0].ParentID == spans[1].SpanID {
|
||||||
|
childSpan = spans[0]
|
||||||
|
pSpan = spans[1]
|
||||||
|
} else {
|
||||||
|
childSpan = spans[1]
|
||||||
|
pSpan = spans[0]
|
||||||
|
}
|
||||||
|
assert.Equal(pSpan.Name, "parentSpan")
|
||||||
|
assert.Equal(childSpan.ParentID, pSpan.SpanID)
|
||||||
|
assert.Equal(childSpan.Name, ext.CassandraQuery)
|
||||||
|
assert.Equal(childSpan.Resource, "SELECT * from trace.person")
|
||||||
|
assert.Equal(childSpan.GetMeta(ext.CassandraKeyspace), "trace")
|
||||||
|
assert.Equal(childSpan.GetMeta(ext.TargetPort), "59042")
|
||||||
|
assert.Equal(childSpan.GetMeta(ext.TargetHost), "127.0.0.1")
|
||||||
|
assert.Equal(childSpan.GetMeta(ext.CassandraCluster), "datacenter1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
11
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/compile.sh
generated
vendored
Executable file
11
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/compile.sh
generated
vendored
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# compiles test fixtures
|
||||||
|
set -e
|
||||||
|
protoc -I . fixtures.proto --go_out=plugins=grpc:.
|
||||||
|
|
||||||
|
# FIXME[matt] hacks to move the fixtures into the testing package
|
||||||
|
# and make it pass our lint rules. This is cheesy but very simple.
|
||||||
|
mv fixtures.pb.go fixtures_test.go
|
||||||
|
sed -i 's/_Fixture_Ping_Handler/fixturePingHandler/' fixtures_test.go
|
||||||
|
sed -i 's/_Fixture_serviceDesc/fixtureServiceDesc/' fixtures_test.go
|
22
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/fixtures.proto
generated
vendored
Normal file
22
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/fixtures.proto
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.examples.testgrpc";
|
||||||
|
option java_outer_classname = "TestGRPCProto";
|
||||||
|
|
||||||
|
package grpc;
|
||||||
|
|
||||||
|
service Fixture {
|
||||||
|
|
||||||
|
rpc Ping (FixtureRequest) returns (FixtureReply) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request message containing the user's name.
|
||||||
|
message FixtureRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing the greetings
|
||||||
|
message FixtureReply {
|
||||||
|
string message = 1;
|
||||||
|
}
|
164
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/fixtures_test.go
generated
vendored
Normal file
164
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/fixtures_test.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
// Code generated by protoc-gen-go.
|
||||||
|
// source: fixtures.proto
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package grpc is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
fixtures.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
FixtureRequest
|
||||||
|
FixtureReply
|
||||||
|
*/
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import proto "github.com/golang/protobuf/proto"
|
||||||
|
import fmt "fmt"
|
||||||
|
import math "math"
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
// The request message containing the user's name.
|
||||||
|
type FixtureRequest struct {
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FixtureRequest) Reset() { *m = FixtureRequest{} }
|
||||||
|
func (m *FixtureRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*FixtureRequest) ProtoMessage() {}
|
||||||
|
func (*FixtureRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||||
|
|
||||||
|
func (m *FixtureRequest) GetName() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing the greetings
|
||||||
|
type FixtureReply struct {
|
||||||
|
Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FixtureReply) Reset() { *m = FixtureReply{} }
|
||||||
|
func (m *FixtureReply) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*FixtureReply) ProtoMessage() {}
|
||||||
|
func (*FixtureReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||||
|
|
||||||
|
func (m *FixtureReply) GetMessage() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Message
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*FixtureRequest)(nil), "grpc.FixtureRequest")
|
||||||
|
proto.RegisterType((*FixtureReply)(nil), "grpc.FixtureReply")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ context.Context
|
||||||
|
var _ grpc.ClientConn
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
const _ = grpc.SupportPackageIsVersion4
|
||||||
|
|
||||||
|
// Client API for Fixture service
|
||||||
|
|
||||||
|
type FixtureClient interface {
|
||||||
|
Ping(ctx context.Context, in *FixtureRequest, opts ...grpc.CallOption) (*FixtureReply, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type fixtureClient struct {
|
||||||
|
cc *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFixtureClient(cc *grpc.ClientConn) FixtureClient {
|
||||||
|
return &fixtureClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fixtureClient) Ping(ctx context.Context, in *FixtureRequest, opts ...grpc.CallOption) (*FixtureReply, error) {
|
||||||
|
out := new(FixtureReply)
|
||||||
|
err := grpc.Invoke(ctx, "/grpc.Fixture/Ping", in, out, c.cc, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server API for Fixture service
|
||||||
|
|
||||||
|
type FixtureServer interface {
|
||||||
|
Ping(context.Context, *FixtureRequest) (*FixtureReply, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterFixtureServer(s *grpc.Server, srv FixtureServer) {
|
||||||
|
s.RegisterService(&fixtureServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixturePingHandler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(FixtureRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(FixtureServer).Ping(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/grpc.Fixture/Ping",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(FixtureServer).Ping(ctx, req.(*FixtureRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixtureServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "grpc.Fixture",
|
||||||
|
HandlerType: (*FixtureServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "Ping",
|
||||||
|
Handler: fixturePingHandler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "fixtures.proto",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("fixtures.proto", fileDescriptor0) }
|
||||||
|
|
||||||
|
var fileDescriptor0 = []byte{
|
||||||
|
// 180 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4b, 0xcb, 0xac, 0x28,
|
||||||
|
0x29, 0x2d, 0x4a, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2c, 0x29, 0x4a, 0x4c,
|
||||||
|
0x4e, 0x4d, 0x2f, 0x2a, 0x48, 0x56, 0x52, 0xe1, 0xe2, 0x73, 0x83, 0x48, 0x06, 0xa5, 0x16, 0x96,
|
||||||
|
0xa6, 0x16, 0x97, 0x08, 0x09, 0x71, 0xb1, 0xe4, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a,
|
||||||
|
0x70, 0x06, 0x81, 0xd9, 0x4a, 0x1a, 0x5c, 0x3c, 0x70, 0x55, 0x05, 0x39, 0x95, 0x42, 0x12, 0x5c,
|
||||||
|
0xec, 0xb9, 0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0x30, 0x65, 0x30, 0xae, 0x91, 0x3b, 0x17, 0x3b, 0x54,
|
||||||
|
0xa5, 0x90, 0x0d, 0x17, 0x4b, 0x40, 0x66, 0x5e, 0xba, 0x90, 0xa4, 0x1e, 0xdc, 0x3a, 0x3d, 0x54,
|
||||||
|
0xbb, 0xa4, 0xc4, 0xb1, 0x49, 0x15, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0xe9, 0x70, 0x49, 0x66, 0xe6,
|
||||||
|
0xeb, 0x81, 0x65, 0x52, 0x2b, 0x12, 0x73, 0x0b, 0x72, 0x52, 0x8b, 0xf5, 0x4a, 0x52, 0x8b, 0x4b,
|
||||||
|
0x40, 0x22, 0x4e, 0xbc, 0x21, 0xa9, 0xc5, 0x25, 0xee, 0x41, 0x01, 0xce, 0x01, 0x20, 0xff, 0x04,
|
||||||
|
0x30, 0x26, 0xb1, 0x81, 0x3d, 0x66, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x68, 0x5d, 0x74, 0x4a,
|
||||||
|
0xea, 0x00, 0x00, 0x00,
|
||||||
|
}
|
118
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/grpc.go
generated
vendored
Normal file
118
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/grpc.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pass trace ids with these headers
|
||||||
|
const (
|
||||||
|
traceIDKey = "x-datadog-trace-id"
|
||||||
|
parentIDKey = "x-datadog-parent-id"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnaryServerInterceptor will trace requests to the given grpc server.
|
||||||
|
func UnaryServerInterceptor(service string, t *tracer.Tracer) grpc.UnaryServerInterceptor {
|
||||||
|
t.SetServiceInfo(service, "grpc-server", ext.AppTypeRPC)
|
||||||
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
if !t.Enabled() {
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
span := serverSpan(t, ctx, info.FullMethod, service)
|
||||||
|
resp, err := handler(tracer.ContextWithSpan(ctx, span), req)
|
||||||
|
span.FinishWithErr(err)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnaryClientInterceptor will add tracing to a gprc client.
|
||||||
|
func UnaryClientInterceptor(service string, t *tracer.Tracer) grpc.UnaryClientInterceptor {
|
||||||
|
t.SetServiceInfo(service, "grpc-client", ext.AppTypeRPC)
|
||||||
|
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||||
|
|
||||||
|
var child *tracer.Span
|
||||||
|
span, ok := tracer.SpanFromContext(ctx)
|
||||||
|
|
||||||
|
// only trace the request if this is already part of a trace.
|
||||||
|
// does this make sense?
|
||||||
|
if ok && span.Tracer() != nil {
|
||||||
|
t := span.Tracer()
|
||||||
|
child = t.NewChildSpan("grpc.client", span)
|
||||||
|
child.SetMeta("grpc.method", method)
|
||||||
|
ctx = setIDs(child, ctx)
|
||||||
|
ctx = tracer.ContextWithSpan(ctx, child)
|
||||||
|
// FIXME[matt] add the host / port information here
|
||||||
|
// https://github.com/grpc/grpc-go/issues/951
|
||||||
|
}
|
||||||
|
|
||||||
|
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||||
|
if child != nil {
|
||||||
|
child.SetMeta("grpc.code", grpc.Code(err).String())
|
||||||
|
child.FinishWithErr(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serverSpan(t *tracer.Tracer, ctx context.Context, method, service string) *tracer.Span {
|
||||||
|
span := t.NewRootSpan("grpc.server", service, method)
|
||||||
|
span.SetMeta("gprc.method", method)
|
||||||
|
span.Type = "go"
|
||||||
|
|
||||||
|
traceID, parentID := getIDs(ctx)
|
||||||
|
if traceID != 0 && parentID != 0 {
|
||||||
|
span.TraceID = traceID
|
||||||
|
span.ParentID = parentID
|
||||||
|
}
|
||||||
|
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
// setIDs will set the trace ids on the context{
|
||||||
|
func setIDs(span *tracer.Span, ctx context.Context) context.Context {
|
||||||
|
if span == nil || span.TraceID == 0 {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
md := metadata.New(map[string]string{
|
||||||
|
traceIDKey: fmt.Sprint(span.TraceID),
|
||||||
|
parentIDKey: fmt.Sprint(span.ParentID),
|
||||||
|
})
|
||||||
|
if existing, ok := metadata.FromContext(ctx); ok {
|
||||||
|
md = metadata.Join(existing, md)
|
||||||
|
}
|
||||||
|
return metadata.NewContext(ctx, md)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIDs will return ids embededd an ahe context.
|
||||||
|
func getIDs(ctx context.Context) (traceID, parentID uint64) {
|
||||||
|
if md, ok := metadata.FromContext(ctx); ok {
|
||||||
|
if id := getID(md, traceIDKey); id > 0 {
|
||||||
|
traceID = id
|
||||||
|
}
|
||||||
|
if id := getID(md, parentIDKey); id > 0 {
|
||||||
|
parentID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return traceID, parentID
|
||||||
|
}
|
||||||
|
|
||||||
|
// getID parses an id from the metadata.
|
||||||
|
func getID(md metadata.MD, name string) uint64 {
|
||||||
|
for _, str := range md[name] {
|
||||||
|
id, err := strconv.Atoi(str)
|
||||||
|
if err == nil {
|
||||||
|
return uint64(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
294
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/grpc_test.go
generated
vendored
Normal file
294
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc.v12/grpc_test.go
generated
vendored
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
rig, err := newRig(testTracer, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up rig: %s", err)
|
||||||
|
}
|
||||||
|
defer rig.Close()
|
||||||
|
client := rig.client
|
||||||
|
|
||||||
|
span := testTracer.NewRootSpan("a", "b", "c")
|
||||||
|
ctx := tracer.ContextWithSpan(context.Background(), span)
|
||||||
|
resp, err := client.Ping(ctx, &FixtureRequest{Name: "pass"})
|
||||||
|
assert.Nil(err)
|
||||||
|
span.Finish()
|
||||||
|
assert.Equal(resp.Message, "passed")
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
|
||||||
|
// A word here about what is going on: this is technically a
|
||||||
|
// distributed trace, while we're in this example in the Go world
|
||||||
|
// and within the same exec, client could know about server details.
|
||||||
|
// But this is not the general cases. So, as we only connect client
|
||||||
|
// and server through their span IDs, they can be flushed as independant
|
||||||
|
// traces. They could also be flushed at once, this is an implementation
|
||||||
|
// detail, what is important is that all of it is flushed, at some point.
|
||||||
|
if len(traces) == 0 {
|
||||||
|
assert.Fail("there should be at least one trace")
|
||||||
|
}
|
||||||
|
var spans []*tracer.Span
|
||||||
|
for _, trace := range traces {
|
||||||
|
for _, span := range trace {
|
||||||
|
spans = append(spans, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Len(spans, 3)
|
||||||
|
|
||||||
|
var sspan, cspan, tspan *tracer.Span
|
||||||
|
|
||||||
|
for _, s := range spans {
|
||||||
|
// order of traces in buffer is not garanteed
|
||||||
|
switch s.Name {
|
||||||
|
case "grpc.server":
|
||||||
|
sspan = s
|
||||||
|
case "grpc.client":
|
||||||
|
cspan = s
|
||||||
|
case "a":
|
||||||
|
tspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(sspan, "there should be a span with 'grpc.server' as Name")
|
||||||
|
|
||||||
|
assert.NotNil(cspan, "there should be a span with 'grpc.client' as Name")
|
||||||
|
assert.Equal(cspan.GetMeta("grpc.code"), "OK")
|
||||||
|
|
||||||
|
assert.NotNil(tspan, "there should be a span with 'a' as Name")
|
||||||
|
assert.Equal(cspan.TraceID, tspan.TraceID)
|
||||||
|
assert.Equal(sspan.TraceID, tspan.TraceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabled(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
testTracer.SetEnabled(false)
|
||||||
|
|
||||||
|
rig, err := newRig(testTracer, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up rig: %s", err)
|
||||||
|
}
|
||||||
|
defer rig.Close()
|
||||||
|
|
||||||
|
client := rig.client
|
||||||
|
resp, err := client.Ping(context.Background(), &FixtureRequest{Name: "disabled"})
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(resp.Message, "disabled")
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Nil(traces)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChild(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
rig, err := newRig(testTracer, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up rig: %s", err)
|
||||||
|
}
|
||||||
|
defer rig.Close()
|
||||||
|
|
||||||
|
client := rig.client
|
||||||
|
resp, err := client.Ping(context.Background(), &FixtureRequest{Name: "child"})
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(resp.Message, "child")
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
|
||||||
|
var sspan, cspan *tracer.Span
|
||||||
|
|
||||||
|
for _, s := range spans {
|
||||||
|
// order of traces in buffer is not garanteed
|
||||||
|
switch s.Name {
|
||||||
|
case "grpc.server":
|
||||||
|
sspan = s
|
||||||
|
case "child":
|
||||||
|
cspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(cspan, "there should be a span with 'child' as Name")
|
||||||
|
assert.Equal(cspan.Error, int32(0))
|
||||||
|
assert.Equal(cspan.Service, "grpc")
|
||||||
|
assert.Equal(cspan.Resource, "child")
|
||||||
|
assert.True(cspan.Duration > 0)
|
||||||
|
|
||||||
|
assert.NotNil(sspan, "there should be a span with 'grpc.server' as Name")
|
||||||
|
assert.Equal(sspan.Error, int32(0))
|
||||||
|
assert.Equal(sspan.Service, "grpc")
|
||||||
|
assert.Equal(sspan.Resource, "/grpc.Fixture/Ping")
|
||||||
|
assert.True(sspan.Duration > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPass(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
rig, err := newRig(testTracer, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up rig: %s", err)
|
||||||
|
}
|
||||||
|
defer rig.Close()
|
||||||
|
|
||||||
|
client := rig.client
|
||||||
|
resp, err := client.Ping(context.Background(), &FixtureRequest{Name: "pass"})
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(resp.Message, "passed")
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Error, int32(0))
|
||||||
|
assert.Equal(s.Name, "grpc.server")
|
||||||
|
assert.Equal(s.Service, "grpc")
|
||||||
|
assert.Equal(s.Resource, "/grpc.Fixture/Ping")
|
||||||
|
assert.Equal(s.Type, "go")
|
||||||
|
assert.True(s.Duration > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixtureServer a dummy implemenation of our grpc fixtureServer.
|
||||||
|
type fixtureServer struct{}
|
||||||
|
|
||||||
|
func newFixtureServer() *fixtureServer {
|
||||||
|
return &fixtureServer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fixtureServer) Ping(ctx context.Context, in *FixtureRequest) (*FixtureReply, error) {
|
||||||
|
switch {
|
||||||
|
case in.Name == "child":
|
||||||
|
span, ok := tracer.SpanFromContext(ctx)
|
||||||
|
if ok {
|
||||||
|
t := span.Tracer()
|
||||||
|
t.NewChildSpan("child", span).Finish()
|
||||||
|
}
|
||||||
|
return &FixtureReply{Message: "child"}, nil
|
||||||
|
case in.Name == "disabled":
|
||||||
|
_, ok := tracer.SpanFromContext(ctx)
|
||||||
|
if ok {
|
||||||
|
panic("should be disabled")
|
||||||
|
}
|
||||||
|
return &FixtureReply{Message: "disabled"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FixtureReply{Message: "passed"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure it's a fixtureServer
|
||||||
|
var _ FixtureServer = &fixtureServer{}
|
||||||
|
|
||||||
|
// rig contains all of the servers and connections we'd need for a
|
||||||
|
// grpc integration test
|
||||||
|
type rig struct {
|
||||||
|
server *grpc.Server
|
||||||
|
listener net.Listener
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
client FixtureClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rig) Close() {
|
||||||
|
r.server.Stop()
|
||||||
|
r.conn.Close()
|
||||||
|
r.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRig(t *tracer.Tracer, traceClient bool) (*rig, error) {
|
||||||
|
|
||||||
|
server := grpc.NewServer(grpc.UnaryInterceptor(UnaryServerInterceptor("grpc", t)))
|
||||||
|
|
||||||
|
RegisterFixtureServer(server, newFixtureServer())
|
||||||
|
|
||||||
|
li, err := net.Listen("tcp4", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start our test fixtureServer.
|
||||||
|
go server.Serve(li)
|
||||||
|
|
||||||
|
opts := []grpc.DialOption{
|
||||||
|
grpc.WithInsecure(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if traceClient {
|
||||||
|
opts = append(opts, grpc.WithUnaryInterceptor(UnaryClientInterceptor("grpc", t)))
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := grpc.Dial(li.Addr().String(), opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error dialing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &rig{
|
||||||
|
listener: li,
|
||||||
|
server: server,
|
||||||
|
conn: conn,
|
||||||
|
client: NewFixtureClient(conn),
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
11
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/compile.sh
generated
vendored
Executable file
11
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/compile.sh
generated
vendored
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# compiles test fixtures
|
||||||
|
set -e
|
||||||
|
protoc -I . fixtures.proto --go_out=plugins=grpc:.
|
||||||
|
|
||||||
|
# FIXME[matt] hacks to move the fixtures into the testing package
|
||||||
|
# and make it pass our lint rules. This is cheesy but very simple.
|
||||||
|
mv fixtures.pb.go fixtures_test.go
|
||||||
|
sed -i 's/_Fixture_Ping_Handler/fixturePingHandler/' fixtures_test.go
|
||||||
|
sed -i 's/_Fixture_serviceDesc/fixtureServiceDesc/' fixtures_test.go
|
22
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/fixtures.proto
generated
vendored
Normal file
22
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/fixtures.proto
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.examples.testgrpc";
|
||||||
|
option java_outer_classname = "TestGRPCProto";
|
||||||
|
|
||||||
|
package grpc;
|
||||||
|
|
||||||
|
service Fixture {
|
||||||
|
|
||||||
|
rpc Ping (FixtureRequest) returns (FixtureReply) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request message containing the user's name.
|
||||||
|
message FixtureRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing the greetings
|
||||||
|
message FixtureReply {
|
||||||
|
string message = 1;
|
||||||
|
}
|
164
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/fixtures_test.go
generated
vendored
Normal file
164
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/fixtures_test.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
// Code generated by protoc-gen-go.
|
||||||
|
// source: fixtures.proto
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package grpc is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
fixtures.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
FixtureRequest
|
||||||
|
FixtureReply
|
||||||
|
*/
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import proto "github.com/golang/protobuf/proto"
|
||||||
|
import fmt "fmt"
|
||||||
|
import math "math"
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
// The request message containing the user's name.
|
||||||
|
type FixtureRequest struct {
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FixtureRequest) Reset() { *m = FixtureRequest{} }
|
||||||
|
func (m *FixtureRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*FixtureRequest) ProtoMessage() {}
|
||||||
|
func (*FixtureRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||||
|
|
||||||
|
func (m *FixtureRequest) GetName() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing the greetings
|
||||||
|
type FixtureReply struct {
|
||||||
|
Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FixtureReply) Reset() { *m = FixtureReply{} }
|
||||||
|
func (m *FixtureReply) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*FixtureReply) ProtoMessage() {}
|
||||||
|
func (*FixtureReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||||
|
|
||||||
|
func (m *FixtureReply) GetMessage() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Message
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*FixtureRequest)(nil), "grpc.FixtureRequest")
|
||||||
|
proto.RegisterType((*FixtureReply)(nil), "grpc.FixtureReply")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ context.Context
|
||||||
|
var _ grpc.ClientConn
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
const _ = grpc.SupportPackageIsVersion4
|
||||||
|
|
||||||
|
// Client API for Fixture service
|
||||||
|
|
||||||
|
type FixtureClient interface {
|
||||||
|
Ping(ctx context.Context, in *FixtureRequest, opts ...grpc.CallOption) (*FixtureReply, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type fixtureClient struct {
|
||||||
|
cc *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFixtureClient(cc *grpc.ClientConn) FixtureClient {
|
||||||
|
return &fixtureClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fixtureClient) Ping(ctx context.Context, in *FixtureRequest, opts ...grpc.CallOption) (*FixtureReply, error) {
|
||||||
|
out := new(FixtureReply)
|
||||||
|
err := grpc.Invoke(ctx, "/grpc.Fixture/Ping", in, out, c.cc, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server API for Fixture service
|
||||||
|
|
||||||
|
type FixtureServer interface {
|
||||||
|
Ping(context.Context, *FixtureRequest) (*FixtureReply, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterFixtureServer(s *grpc.Server, srv FixtureServer) {
|
||||||
|
s.RegisterService(&fixtureServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixturePingHandler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(FixtureRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(FixtureServer).Ping(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/grpc.Fixture/Ping",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(FixtureServer).Ping(ctx, req.(*FixtureRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixtureServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "grpc.Fixture",
|
||||||
|
HandlerType: (*FixtureServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "Ping",
|
||||||
|
Handler: fixturePingHandler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "fixtures.proto",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("fixtures.proto", fileDescriptor0) }
|
||||||
|
|
||||||
|
var fileDescriptor0 = []byte{
|
||||||
|
// 180 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4b, 0xcb, 0xac, 0x28,
|
||||||
|
0x29, 0x2d, 0x4a, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2c, 0x29, 0x4a, 0x4c,
|
||||||
|
0x4e, 0x4d, 0x2f, 0x2a, 0x48, 0x56, 0x52, 0xe1, 0xe2, 0x73, 0x83, 0x48, 0x06, 0xa5, 0x16, 0x96,
|
||||||
|
0xa6, 0x16, 0x97, 0x08, 0x09, 0x71, 0xb1, 0xe4, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a,
|
||||||
|
0x70, 0x06, 0x81, 0xd9, 0x4a, 0x1a, 0x5c, 0x3c, 0x70, 0x55, 0x05, 0x39, 0x95, 0x42, 0x12, 0x5c,
|
||||||
|
0xec, 0xb9, 0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0x30, 0x65, 0x30, 0xae, 0x91, 0x3b, 0x17, 0x3b, 0x54,
|
||||||
|
0xa5, 0x90, 0x0d, 0x17, 0x4b, 0x40, 0x66, 0x5e, 0xba, 0x90, 0xa4, 0x1e, 0xdc, 0x3a, 0x3d, 0x54,
|
||||||
|
0xbb, 0xa4, 0xc4, 0xb1, 0x49, 0x15, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0xe9, 0x70, 0x49, 0x66, 0xe6,
|
||||||
|
0xeb, 0x81, 0x65, 0x52, 0x2b, 0x12, 0x73, 0x0b, 0x72, 0x52, 0x8b, 0xf5, 0x4a, 0x52, 0x8b, 0x4b,
|
||||||
|
0x40, 0x22, 0x4e, 0xbc, 0x21, 0xa9, 0xc5, 0x25, 0xee, 0x41, 0x01, 0xce, 0x01, 0x20, 0xff, 0x04,
|
||||||
|
0x30, 0x26, 0xb1, 0x81, 0x3d, 0x66, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x68, 0x5d, 0x74, 0x4a,
|
||||||
|
0xea, 0x00, 0x00, 0x00,
|
||||||
|
}
|
118
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/grpc.go
generated
vendored
Normal file
118
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/grpc.go
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pass trace ids with these headers
|
||||||
|
const (
|
||||||
|
traceIDKey = "x-datadog-trace-id"
|
||||||
|
parentIDKey = "x-datadog-parent-id"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnaryServerInterceptor will trace requests to the given grpc server.
|
||||||
|
func UnaryServerInterceptor(service string, t *tracer.Tracer) grpc.UnaryServerInterceptor {
|
||||||
|
t.SetServiceInfo(service, "grpc-server", ext.AppTypeRPC)
|
||||||
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
if !t.Enabled() {
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
span := serverSpan(t, ctx, info.FullMethod, service)
|
||||||
|
resp, err := handler(tracer.ContextWithSpan(ctx, span), req)
|
||||||
|
span.FinishWithErr(err)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnaryClientInterceptor will add tracing to a gprc client.
|
||||||
|
func UnaryClientInterceptor(service string, t *tracer.Tracer) grpc.UnaryClientInterceptor {
|
||||||
|
t.SetServiceInfo(service, "grpc-client", ext.AppTypeRPC)
|
||||||
|
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||||
|
|
||||||
|
var child *tracer.Span
|
||||||
|
span, ok := tracer.SpanFromContext(ctx)
|
||||||
|
|
||||||
|
// only trace the request if this is already part of a trace.
|
||||||
|
// does this make sense?
|
||||||
|
if ok && span.Tracer() != nil {
|
||||||
|
t := span.Tracer()
|
||||||
|
child = t.NewChildSpan("grpc.client", span)
|
||||||
|
child.SetMeta("grpc.method", method)
|
||||||
|
ctx = setIDs(child, ctx)
|
||||||
|
ctx = tracer.ContextWithSpan(ctx, child)
|
||||||
|
// FIXME[matt] add the host / port information here
|
||||||
|
// https://github.com/grpc/grpc-go/issues/951
|
||||||
|
}
|
||||||
|
|
||||||
|
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||||
|
if child != nil {
|
||||||
|
child.SetMeta("grpc.code", grpc.Code(err).String())
|
||||||
|
child.FinishWithErr(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serverSpan(t *tracer.Tracer, ctx context.Context, method, service string) *tracer.Span {
|
||||||
|
span := t.NewRootSpan("grpc.server", service, method)
|
||||||
|
span.SetMeta("gprc.method", method)
|
||||||
|
span.Type = "go"
|
||||||
|
|
||||||
|
traceID, parentID := getIDs(ctx)
|
||||||
|
if traceID != 0 && parentID != 0 {
|
||||||
|
span.TraceID = traceID
|
||||||
|
span.ParentID = parentID
|
||||||
|
}
|
||||||
|
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
// setIDs will set the trace ids on the context{
|
||||||
|
func setIDs(span *tracer.Span, ctx context.Context) context.Context {
|
||||||
|
if span == nil || span.TraceID == 0 {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
md := metadata.New(map[string]string{
|
||||||
|
traceIDKey: fmt.Sprint(span.TraceID),
|
||||||
|
parentIDKey: fmt.Sprint(span.ParentID),
|
||||||
|
})
|
||||||
|
if existing, ok := metadata.FromIncomingContext(ctx); ok {
|
||||||
|
md = metadata.Join(existing, md)
|
||||||
|
}
|
||||||
|
return metadata.NewOutgoingContext(ctx, md)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIDs will return ids embededd an ahe context.
|
||||||
|
func getIDs(ctx context.Context) (traceID, parentID uint64) {
|
||||||
|
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||||
|
if id := getID(md, traceIDKey); id > 0 {
|
||||||
|
traceID = id
|
||||||
|
}
|
||||||
|
if id := getID(md, parentIDKey); id > 0 {
|
||||||
|
parentID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return traceID, parentID
|
||||||
|
}
|
||||||
|
|
||||||
|
// getID parses an id from the metadata.
|
||||||
|
func getID(md metadata.MD, name string) uint64 {
|
||||||
|
for _, str := range md[name] {
|
||||||
|
id, err := strconv.Atoi(str)
|
||||||
|
if err == nil {
|
||||||
|
return uint64(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
294
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/grpc_test.go
generated
vendored
Normal file
294
vendor/github.com/DataDog/dd-trace-go/contrib/google.golang.org/grpc/grpc_test.go
generated
vendored
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
rig, err := newRig(testTracer, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up rig: %s", err)
|
||||||
|
}
|
||||||
|
defer rig.Close()
|
||||||
|
client := rig.client
|
||||||
|
|
||||||
|
span := testTracer.NewRootSpan("a", "b", "c")
|
||||||
|
ctx := tracer.ContextWithSpan(context.Background(), span)
|
||||||
|
resp, err := client.Ping(ctx, &FixtureRequest{Name: "pass"})
|
||||||
|
assert.Nil(err)
|
||||||
|
span.Finish()
|
||||||
|
assert.Equal(resp.Message, "passed")
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
|
||||||
|
// A word here about what is going on: this is technically a
|
||||||
|
// distributed trace, while we're in this example in the Go world
|
||||||
|
// and within the same exec, client could know about server details.
|
||||||
|
// But this is not the general cases. So, as we only connect client
|
||||||
|
// and server through their span IDs, they can be flushed as independant
|
||||||
|
// traces. They could also be flushed at once, this is an implementation
|
||||||
|
// detail, what is important is that all of it is flushed, at some point.
|
||||||
|
if len(traces) == 0 {
|
||||||
|
assert.Fail("there should be at least one trace")
|
||||||
|
}
|
||||||
|
var spans []*tracer.Span
|
||||||
|
for _, trace := range traces {
|
||||||
|
for _, span := range trace {
|
||||||
|
spans = append(spans, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Len(spans, 3)
|
||||||
|
|
||||||
|
var sspan, cspan, tspan *tracer.Span
|
||||||
|
|
||||||
|
for _, s := range spans {
|
||||||
|
// order of traces in buffer is not garanteed
|
||||||
|
switch s.Name {
|
||||||
|
case "grpc.server":
|
||||||
|
sspan = s
|
||||||
|
case "grpc.client":
|
||||||
|
cspan = s
|
||||||
|
case "a":
|
||||||
|
tspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(sspan, "there should be a span with 'grpc.server' as Name")
|
||||||
|
|
||||||
|
assert.NotNil(cspan, "there should be a span with 'grpc.client' as Name")
|
||||||
|
assert.Equal(cspan.GetMeta("grpc.code"), "OK")
|
||||||
|
|
||||||
|
assert.NotNil(tspan, "there should be a span with 'a' as Name")
|
||||||
|
assert.Equal(cspan.TraceID, tspan.TraceID)
|
||||||
|
assert.Equal(sspan.TraceID, tspan.TraceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabled(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
testTracer.SetEnabled(false)
|
||||||
|
|
||||||
|
rig, err := newRig(testTracer, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up rig: %s", err)
|
||||||
|
}
|
||||||
|
defer rig.Close()
|
||||||
|
|
||||||
|
client := rig.client
|
||||||
|
resp, err := client.Ping(context.Background(), &FixtureRequest{Name: "disabled"})
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(resp.Message, "disabled")
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Nil(traces)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChild(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
rig, err := newRig(testTracer, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up rig: %s", err)
|
||||||
|
}
|
||||||
|
defer rig.Close()
|
||||||
|
|
||||||
|
client := rig.client
|
||||||
|
resp, err := client.Ping(context.Background(), &FixtureRequest{Name: "child"})
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(resp.Message, "child")
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
|
||||||
|
var sspan, cspan *tracer.Span
|
||||||
|
|
||||||
|
for _, s := range spans {
|
||||||
|
// order of traces in buffer is not garanteed
|
||||||
|
switch s.Name {
|
||||||
|
case "grpc.server":
|
||||||
|
sspan = s
|
||||||
|
case "child":
|
||||||
|
cspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(cspan, "there should be a span with 'child' as Name")
|
||||||
|
assert.Equal(cspan.Error, int32(0))
|
||||||
|
assert.Equal(cspan.Service, "grpc")
|
||||||
|
assert.Equal(cspan.Resource, "child")
|
||||||
|
assert.True(cspan.Duration > 0)
|
||||||
|
|
||||||
|
assert.NotNil(sspan, "there should be a span with 'grpc.server' as Name")
|
||||||
|
assert.Equal(sspan.Error, int32(0))
|
||||||
|
assert.Equal(sspan.Service, "grpc")
|
||||||
|
assert.Equal(sspan.Resource, "/grpc.Fixture/Ping")
|
||||||
|
assert.True(sspan.Duration > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPass(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
rig, err := newRig(testTracer, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up rig: %s", err)
|
||||||
|
}
|
||||||
|
defer rig.Close()
|
||||||
|
|
||||||
|
client := rig.client
|
||||||
|
resp, err := client.Ping(context.Background(), &FixtureRequest{Name: "pass"})
|
||||||
|
assert.Nil(err)
|
||||||
|
assert.Equal(resp.Message, "passed")
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Error, int32(0))
|
||||||
|
assert.Equal(s.Name, "grpc.server")
|
||||||
|
assert.Equal(s.Service, "grpc")
|
||||||
|
assert.Equal(s.Resource, "/grpc.Fixture/Ping")
|
||||||
|
assert.Equal(s.Type, "go")
|
||||||
|
assert.True(s.Duration > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixtureServer a dummy implemenation of our grpc fixtureServer.
|
||||||
|
type fixtureServer struct{}
|
||||||
|
|
||||||
|
func newFixtureServer() *fixtureServer {
|
||||||
|
return &fixtureServer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fixtureServer) Ping(ctx context.Context, in *FixtureRequest) (*FixtureReply, error) {
|
||||||
|
switch {
|
||||||
|
case in.Name == "child":
|
||||||
|
span, ok := tracer.SpanFromContext(ctx)
|
||||||
|
if ok {
|
||||||
|
t := span.Tracer()
|
||||||
|
t.NewChildSpan("child", span).Finish()
|
||||||
|
}
|
||||||
|
return &FixtureReply{Message: "child"}, nil
|
||||||
|
case in.Name == "disabled":
|
||||||
|
_, ok := tracer.SpanFromContext(ctx)
|
||||||
|
if ok {
|
||||||
|
panic("should be disabled")
|
||||||
|
}
|
||||||
|
return &FixtureReply{Message: "disabled"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FixtureReply{Message: "passed"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure it's a fixtureServer
|
||||||
|
var _ FixtureServer = &fixtureServer{}
|
||||||
|
|
||||||
|
// rig contains all of the servers and connections we'd need for a
|
||||||
|
// grpc integration test
|
||||||
|
type rig struct {
|
||||||
|
server *grpc.Server
|
||||||
|
listener net.Listener
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
client FixtureClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rig) Close() {
|
||||||
|
r.server.Stop()
|
||||||
|
r.conn.Close()
|
||||||
|
r.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRig(t *tracer.Tracer, traceClient bool) (*rig, error) {
|
||||||
|
|
||||||
|
server := grpc.NewServer(grpc.UnaryInterceptor(UnaryServerInterceptor("grpc", t)))
|
||||||
|
|
||||||
|
RegisterFixtureServer(server, newFixtureServer())
|
||||||
|
|
||||||
|
li, err := net.Listen("tcp4", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start our test fixtureServer.
|
||||||
|
go server.Serve(li)
|
||||||
|
|
||||||
|
opts := []grpc.DialOption{
|
||||||
|
grpc.WithInsecure(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if traceClient {
|
||||||
|
opts = append(opts, grpc.WithUnaryInterceptor(UnaryClientInterceptor("grpc", t)))
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := grpc.Dial(li.Addr().String(), opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error dialing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &rig{
|
||||||
|
listener: li,
|
||||||
|
server: server,
|
||||||
|
conn: conn,
|
||||||
|
client: NewFixtureClient(conn),
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
31
vendor/github.com/DataDog/dd-trace-go/contrib/gorilla/mux/example_test.go
generated
vendored
Normal file
31
vendor/github.com/DataDog/dd-trace-go/contrib/gorilla/mux/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package mux_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
muxtrace "github.com/DataDog/dd-trace-go/contrib/gorilla/mux"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handler is a simple handlerFunc that logs some data from the span
|
||||||
|
// that is injected into the requests' context.
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
span := tracer.SpanFromContextDefault(r.Context())
|
||||||
|
fmt.Printf("tracing service:%s resource:%s", span.Service, span.Resource)
|
||||||
|
w.Write([]byte("hello world"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
router := mux.NewRouter()
|
||||||
|
muxTracer := muxtrace.NewMuxTracer("my-web-app", tracer.DefaultTracer)
|
||||||
|
|
||||||
|
// Add traced routes directly.
|
||||||
|
muxTracer.HandleFunc(router, "/users", handler)
|
||||||
|
|
||||||
|
// and subroutes as well.
|
||||||
|
subrouter := router.PathPrefix("/user").Subrouter()
|
||||||
|
muxTracer.HandleFunc(subrouter, "/view", handler)
|
||||||
|
muxTracer.HandleFunc(subrouter, "/create", handler)
|
||||||
|
}
|
127
vendor/github.com/DataDog/dd-trace-go/contrib/gorilla/mux/mux.go
generated
vendored
Normal file
127
vendor/github.com/DataDog/dd-trace-go/contrib/gorilla/mux/mux.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
// Package mux provides tracing functions for the Gorilla Mux framework.
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MuxTracer is used to trace requests in a mux server.
|
||||||
|
type MuxTracer struct {
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
service string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMuxTracer creates a MuxTracer for the given service and tracer.
|
||||||
|
func NewMuxTracer(service string, t *tracer.Tracer) *MuxTracer {
|
||||||
|
t.SetServiceInfo(service, "gorilla", ext.AppTypeWeb)
|
||||||
|
return &MuxTracer{
|
||||||
|
tracer: t,
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceHandleFunc will return a HandlerFunc that will wrap tracing around the
|
||||||
|
// given handler func.
|
||||||
|
func (m *MuxTracer) TraceHandleFunc(handler http.HandlerFunc) http.HandlerFunc {
|
||||||
|
|
||||||
|
return func(writer http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
// bail our if tracing isn't enabled.
|
||||||
|
if !m.tracer.Enabled() {
|
||||||
|
handler(writer, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// trace the request
|
||||||
|
tracedRequest, span := m.trace(req)
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
// trace the response
|
||||||
|
tracedWriter := newTracedResponseWriter(span, writer)
|
||||||
|
|
||||||
|
// run the request
|
||||||
|
handler(tracedWriter, tracedRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFunc will add a traced version of the given handler to the router.
|
||||||
|
func (m *MuxTracer) HandleFunc(router *mux.Router, pattern string, handler http.HandlerFunc) *mux.Route {
|
||||||
|
return router.HandleFunc(pattern, m.TraceHandleFunc(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
// span will create a span for the given request.
|
||||||
|
func (m *MuxTracer) trace(req *http.Request) (*http.Request, *tracer.Span) {
|
||||||
|
route := mux.CurrentRoute(req)
|
||||||
|
path, err := route.GetPathTemplate()
|
||||||
|
if err != nil {
|
||||||
|
// when route doesn't define a path
|
||||||
|
path = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource := req.Method + " " + path
|
||||||
|
|
||||||
|
span := m.tracer.NewRootSpan("mux.request", m.service, resource)
|
||||||
|
span.Type = ext.HTTPType
|
||||||
|
span.SetMeta(ext.HTTPMethod, req.Method)
|
||||||
|
span.SetMeta(ext.HTTPURL, path)
|
||||||
|
|
||||||
|
// patch the span onto the request context.
|
||||||
|
treq := SetRequestSpan(req, span)
|
||||||
|
return treq, span
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracedResponseWriter is a small wrapper around an http response writer that will
|
||||||
|
// intercept and store the status of a request.
|
||||||
|
type tracedResponseWriter struct {
|
||||||
|
span *tracer.Span
|
||||||
|
w http.ResponseWriter
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTracedResponseWriter(span *tracer.Span, w http.ResponseWriter) *tracedResponseWriter {
|
||||||
|
return &tracedResponseWriter{
|
||||||
|
span: span,
|
||||||
|
w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracedResponseWriter) Header() http.Header {
|
||||||
|
return t.w.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracedResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
if t.status == 0 {
|
||||||
|
t.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
return t.w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracedResponseWriter) WriteHeader(status int) {
|
||||||
|
t.w.WriteHeader(status)
|
||||||
|
t.status = status
|
||||||
|
t.span.SetMeta(ext.HTTPCode, strconv.Itoa(status))
|
||||||
|
if status >= 500 && status < 600 {
|
||||||
|
t.span.Error = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequestSpan sets the span on the request's context.
|
||||||
|
func SetRequestSpan(r *http.Request, span *tracer.Span) *http.Request {
|
||||||
|
if r == nil || span == nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := tracer.ContextWithSpan(r.Context(), span)
|
||||||
|
return r.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestSpan will return the span associated with the given request. It
|
||||||
|
// will return nil/false if it doesn't exist.
|
||||||
|
func GetRequestSpan(r *http.Request) (*tracer.Span, bool) {
|
||||||
|
span, ok := tracer.SpanFromContext(r.Context())
|
||||||
|
return span, ok
|
||||||
|
}
|
206
vendor/github.com/DataDog/dd-trace-go/contrib/gorilla/mux/mux_test.go
generated
vendored
Normal file
206
vendor/github.com/DataDog/dd-trace-go/contrib/gorilla/mux/mux_test.go
generated
vendored
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMuxTracerDisabled(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
testTracer, testTransport, muxTracer := getTestTracer("disabled-service")
|
||||||
|
router := mux.NewRouter()
|
||||||
|
muxTracer.HandleFunc(router, "/disabled", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte("disabled!"))
|
||||||
|
assert.Nil(err)
|
||||||
|
// Ensure we have no tracing context.
|
||||||
|
span, ok := tracer.SpanFromContext(r.Context())
|
||||||
|
assert.Nil(span)
|
||||||
|
assert.False(ok)
|
||||||
|
})
|
||||||
|
testTracer.SetEnabled(false) // the key line in this test.
|
||||||
|
|
||||||
|
// make the request
|
||||||
|
req := httptest.NewRequest("GET", "/disabled", nil)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(writer, req)
|
||||||
|
assert.Equal(writer.Code, 200)
|
||||||
|
assert.Equal(writer.Body.String(), "disabled!")
|
||||||
|
|
||||||
|
// assert nothing was traced.
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxTracerSubrequest(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// Send and verify a 200 request
|
||||||
|
for _, url := range []string{"/sub/child1", "/sub/child2"} {
|
||||||
|
tracer, transport, router := setup(t)
|
||||||
|
req := httptest.NewRequest("GET", url, nil)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(writer, req)
|
||||||
|
assert.Equal(writer.Code, 200)
|
||||||
|
assert.Equal(writer.Body.String(), "200!")
|
||||||
|
|
||||||
|
// ensure properly traced
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Name, "mux.request")
|
||||||
|
assert.Equal(s.Service, "my-service")
|
||||||
|
assert.Equal(s.Resource, "GET "+url)
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "200")
|
||||||
|
assert.Equal(s.GetMeta("http.method"), "GET")
|
||||||
|
assert.Equal(s.GetMeta("http.url"), url)
|
||||||
|
assert.Equal(s.Error, int32(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxTracer200(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// setup
|
||||||
|
tracer, transport, router := setup(t)
|
||||||
|
|
||||||
|
// Send and verify a 200 request
|
||||||
|
url := "/200"
|
||||||
|
req := httptest.NewRequest("GET", url, nil)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(writer, req)
|
||||||
|
assert.Equal(writer.Code, 200)
|
||||||
|
assert.Equal(writer.Body.String(), "200!")
|
||||||
|
|
||||||
|
// ensure properly traced
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Name, "mux.request")
|
||||||
|
assert.Equal(s.Service, "my-service")
|
||||||
|
assert.Equal(s.Resource, "GET "+url)
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "200")
|
||||||
|
assert.Equal(s.GetMeta("http.method"), "GET")
|
||||||
|
assert.Equal(s.GetMeta("http.url"), url)
|
||||||
|
assert.Equal(s.Error, int32(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxTracer500(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// setup
|
||||||
|
tracer, transport, router := setup(t)
|
||||||
|
|
||||||
|
// SEnd and verify a 200 request
|
||||||
|
url := "/500"
|
||||||
|
req := httptest.NewRequest("GET", url, nil)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(writer, req)
|
||||||
|
assert.Equal(writer.Code, 500)
|
||||||
|
assert.Equal(writer.Body.String(), "500!\n")
|
||||||
|
|
||||||
|
// ensure properly traced
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Name, "mux.request")
|
||||||
|
assert.Equal(s.Service, "my-service")
|
||||||
|
assert.Equal(s.Resource, "GET "+url)
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "500")
|
||||||
|
assert.Equal(s.GetMeta("http.method"), "GET")
|
||||||
|
assert.Equal(s.GetMeta("http.url"), url)
|
||||||
|
assert.Equal(s.Error, int32(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// test handlers
|
||||||
|
|
||||||
|
func handler200(t *testing.T) http.HandlerFunc {
|
||||||
|
assert := assert.New(t)
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte("200!"))
|
||||||
|
assert.Nil(err)
|
||||||
|
span := tracer.SpanFromContextDefault(r.Context())
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Duration, int64(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler500(t *testing.T) http.HandlerFunc {
|
||||||
|
assert := assert.New(t)
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "500!", http.StatusInternalServerError)
|
||||||
|
span := tracer.SpanFromContextDefault(r.Context())
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Duration, int64(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(t *testing.T) (*tracer.Tracer, *dummyTransport, *mux.Router) {
|
||||||
|
tracer, transport, mt := getTestTracer("my-service")
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
h200 := handler200(t)
|
||||||
|
h500 := handler500(t)
|
||||||
|
|
||||||
|
// Ensure we can use HandleFunc and it returns a route
|
||||||
|
mt.HandleFunc(r, "/200", h200).Methods("Get")
|
||||||
|
// And we can allso handle a bare func
|
||||||
|
r.HandleFunc("/500", mt.TraceHandleFunc(h500))
|
||||||
|
|
||||||
|
// do a subrouter (one in each way)
|
||||||
|
sub := r.PathPrefix("/sub").Subrouter()
|
||||||
|
sub.HandleFunc("/child1", mt.TraceHandleFunc(h200))
|
||||||
|
mt.HandleFunc(sub, "/child2", h200)
|
||||||
|
|
||||||
|
return tracer, transport, r
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer(service string) (*tracer.Tracer, *dummyTransport, *MuxTracer) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
muxTracer := NewMuxTracer(service, tracer)
|
||||||
|
return tracer, transport, muxTracer
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
23
vendor/github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx/example_test.go
generated
vendored
Normal file
23
vendor/github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package sqlx_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
|
||||||
|
sqlxtrace "github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The API to trace sqlx calls is the same as sqltraced.
|
||||||
|
// See https://godoc.org/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced for more information on how to use it.
|
||||||
|
func Example() {
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sqlx.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
db, _ := sqlxtrace.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
|
||||||
|
// All calls through sqlx API will then be traced.
|
||||||
|
query, args, _ := sqlx.In("SELECT * FROM users WHERE level IN (?);", []int{4, 6, 7})
|
||||||
|
query = db.Rebind(query)
|
||||||
|
rows, _ := db.Query(query, args...)
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
41
vendor/github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx/mysql_test.go
generated
vendored
Normal file
41
vendor/github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx/mysql_test.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package sqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/sqltest"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMySQL(t *testing.T) {
|
||||||
|
trc, transport := tracertest.GetTestTracer()
|
||||||
|
dbx, err := OpenTraced(&mysql.MySQLDriver{}, "test:test@tcp(127.0.0.1:53306)/test", "mysql-test", trc)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dbx.Close()
|
||||||
|
|
||||||
|
testDB := &sqltest.DB{
|
||||||
|
DB: dbx.DB,
|
||||||
|
Tracer: trc,
|
||||||
|
Transport: transport,
|
||||||
|
DriverName: "mysql",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSpan := &tracer.Span{
|
||||||
|
Name: "mysql.query",
|
||||||
|
Service: "mysql-test",
|
||||||
|
Type: "sql",
|
||||||
|
}
|
||||||
|
expectedSpan.Meta = map[string]string{
|
||||||
|
"db.user": "test",
|
||||||
|
"out.host": "127.0.0.1",
|
||||||
|
"out.port": "53306",
|
||||||
|
"db.name": "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
sqltest.AllSQLTests(t, testDB, expectedSpan)
|
||||||
|
}
|
41
vendor/github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx/pq_test.go
generated
vendored
Normal file
41
vendor/github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx/pq_test.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package sqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/sqltest"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPostgres(t *testing.T) {
|
||||||
|
trc, transport := tracertest.GetTestTracer()
|
||||||
|
dbx, err := OpenTraced(&pq.Driver{}, "postgres://postgres:postgres@127.0.0.1:55432/postgres?sslmode=disable", "postgres-test", trc)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer dbx.Close()
|
||||||
|
|
||||||
|
testDB := &sqltest.DB{
|
||||||
|
DB: dbx.DB,
|
||||||
|
Tracer: trc,
|
||||||
|
Transport: transport,
|
||||||
|
DriverName: "postgres",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSpan := &tracer.Span{
|
||||||
|
Name: "postgres.query",
|
||||||
|
Service: "postgres-test",
|
||||||
|
Type: "sql",
|
||||||
|
}
|
||||||
|
expectedSpan.Meta = map[string]string{
|
||||||
|
"db.user": "postgres",
|
||||||
|
"out.host": "127.0.0.1",
|
||||||
|
"out.port": "55432",
|
||||||
|
"db.name": "postgres",
|
||||||
|
}
|
||||||
|
|
||||||
|
sqltest.AllSQLTests(t, testDB, expectedSpan)
|
||||||
|
}
|
35
vendor/github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx/sql.go
generated
vendored
Normal file
35
vendor/github.com/DataDog/dd-trace-go/contrib/jmoiron/sqlx/sql.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Package sqlxtraced provides a traced version of the "jmoiron/sqlx" package
|
||||||
|
// For more information about the API, see https://godoc.org/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced.
|
||||||
|
package sqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/sqlutils"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenTraced will first register the traced version of the `driver` if not yet registered and will then open a connection with it.
|
||||||
|
// This is usually the only function to use when there is no need for the granularity offered by Register and Open.
|
||||||
|
// The last argument is optional and allows you to pass a custom tracer.
|
||||||
|
func OpenTraced(driver driver.Driver, dataSourceName, service string, trcv ...*tracer.Tracer) (*sqlx.DB, error) {
|
||||||
|
driverName := sqlutils.GetDriverName(driver)
|
||||||
|
Register(driverName, driver, trcv...)
|
||||||
|
return Open(driverName, dataSourceName, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a traced version of `driver`.
|
||||||
|
func Register(driverName string, driver driver.Driver, trcv ...*tracer.Tracer) {
|
||||||
|
sqltraced.Register(driverName, driver, trcv...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns a traced version of *sqlx.DB.
|
||||||
|
func Open(driverName, dataSourceName, service string) (*sqlx.DB, error) {
|
||||||
|
db, err := sqltraced.Open(driverName, dataSourceName, service)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sqlx.NewDb(db, driverName), err
|
||||||
|
}
|
17
vendor/github.com/DataDog/dd-trace-go/contrib/net/http/example_test.go
generated
vendored
Normal file
17
vendor/github.com/DataDog/dd-trace-go/contrib/net/http/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package http_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
httptrace "github.com/DataDog/dd-trace-go/contrib/net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("Hello World!\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
mux := httptrace.NewServeMux("web-service", nil)
|
||||||
|
mux.HandleFunc("/", handler)
|
||||||
|
http.ListenAndServe(":8080", mux)
|
||||||
|
}
|
93
vendor/github.com/DataDog/dd-trace-go/contrib/net/http/http.go
generated
vendored
Normal file
93
vendor/github.com/DataDog/dd-trace-go/contrib/net/http/http.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServeMux is an HTTP request multiplexer that traces all the incoming requests.
|
||||||
|
type ServeMux struct {
|
||||||
|
*http.ServeMux
|
||||||
|
*tracer.Tracer
|
||||||
|
service string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServeMux allocates and returns a new ServeMux.
|
||||||
|
func NewServeMux(service string, t *tracer.Tracer) *ServeMux {
|
||||||
|
if t == nil {
|
||||||
|
t = tracer.DefaultTracer
|
||||||
|
}
|
||||||
|
t.SetServiceInfo(service, "net/http", ext.AppTypeWeb)
|
||||||
|
return &ServeMux{http.NewServeMux(), t, service}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP dispatches the request to the handler whose
|
||||||
|
// pattern most closely matches the request URL.
|
||||||
|
// We only needed to rewrite this method to be able to trace the multiplexer.
|
||||||
|
func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// bail out if tracing isn't enabled
|
||||||
|
if !mux.Tracer.Enabled() {
|
||||||
|
mux.ServeMux.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the route associated to this request
|
||||||
|
_, route := mux.Handler(r)
|
||||||
|
|
||||||
|
// create a new span
|
||||||
|
resource := r.Method + " " + route
|
||||||
|
span := mux.Tracer.NewRootSpan("http.request", mux.service, resource)
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
span.Type = ext.HTTPType
|
||||||
|
span.SetMeta(ext.HTTPMethod, r.Method)
|
||||||
|
span.SetMeta(ext.HTTPURL, r.URL.Path)
|
||||||
|
|
||||||
|
// pass the span through the request context
|
||||||
|
ctx := span.Context(r.Context())
|
||||||
|
traceRequest := r.WithContext(ctx)
|
||||||
|
|
||||||
|
// trace the response to get the status code
|
||||||
|
traceWriter := NewResponseWriter(w, span)
|
||||||
|
|
||||||
|
// serve the request to the underlying multiplexer
|
||||||
|
mux.ServeMux.ServeHTTP(traceWriter, traceRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseWriter is a small wrapper around an http response writer that will
|
||||||
|
// intercept and store the status of a request.
|
||||||
|
// It implements the ResponseWriter interface.
|
||||||
|
type ResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
span *tracer.Span
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New ResponseWriter allocateds and returns a new ResponseWriter.
|
||||||
|
func NewResponseWriter(w http.ResponseWriter, span *tracer.Span) *ResponseWriter {
|
||||||
|
return &ResponseWriter{w, span, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the data to the connection as part of an HTTP reply.
|
||||||
|
// We explicitely call WriteHeader with the 200 status code
|
||||||
|
// in order to get it reported into the span.
|
||||||
|
func (w *ResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
if w.status == 0 {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
return w.ResponseWriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader sends an HTTP response header with status code.
|
||||||
|
// It also sets the status code to the span.
|
||||||
|
func (w *ResponseWriter) WriteHeader(status int) {
|
||||||
|
w.ResponseWriter.WriteHeader(status)
|
||||||
|
w.status = status
|
||||||
|
w.span.SetMeta(ext.HTTPCode, strconv.Itoa(status))
|
||||||
|
if status >= 500 && status < 600 {
|
||||||
|
w.span.Error = 1
|
||||||
|
}
|
||||||
|
}
|
134
vendor/github.com/DataDog/dd-trace-go/contrib/net/http/http_test.go
generated
vendored
Normal file
134
vendor/github.com/DataDog/dd-trace-go/contrib/net/http/http_test.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHttpTracerDisabled(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
testTracer, testTransport := tracertest.GetTestTracer()
|
||||||
|
testTracer.SetEnabled(false)
|
||||||
|
|
||||||
|
mux := NewServeMux("my-service", testTracer)
|
||||||
|
mux.HandleFunc("/disabled", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte("disabled!"))
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
// Ensure we have no tracing context
|
||||||
|
span, ok := tracer.SpanFromContext(r.Context())
|
||||||
|
assert.Nil(span)
|
||||||
|
assert.False(ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Make the request
|
||||||
|
r := httptest.NewRequest("GET", "/disabled", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(w, r)
|
||||||
|
assert.Equal(200, w.Code)
|
||||||
|
assert.Equal("disabled!", w.Body.String())
|
||||||
|
|
||||||
|
// Assert nothing was traced
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Equal(0, len(traces))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpTracer200(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
tracer, transport, router := setup(t)
|
||||||
|
|
||||||
|
// Send and verify a 200 request
|
||||||
|
url := "/200"
|
||||||
|
r := httptest.NewRequest("GET", url, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
assert.Equal(200, w.Code)
|
||||||
|
assert.Equal("200!\n", w.Body.String())
|
||||||
|
|
||||||
|
// Ensure the request is properly traced
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Equal(1, len(traces))
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal(1, len(spans))
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal("http.request", s.Name)
|
||||||
|
assert.Equal("my-service", s.Service)
|
||||||
|
assert.Equal("GET "+url, s.Resource)
|
||||||
|
assert.Equal("200", s.GetMeta("http.status_code"))
|
||||||
|
assert.Equal("GET", s.GetMeta("http.method"))
|
||||||
|
assert.Equal(url, s.GetMeta("http.url"))
|
||||||
|
assert.Equal(int32(0), s.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpTracer500(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
tracer, transport, router := setup(t)
|
||||||
|
|
||||||
|
// Send and verify a 500 request
|
||||||
|
url := "/500"
|
||||||
|
r := httptest.NewRequest("GET", url, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
assert.Equal(500, w.Code)
|
||||||
|
assert.Equal("500!\n", w.Body.String())
|
||||||
|
|
||||||
|
// Ensure the request is properly traced
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Equal(1, len(traces))
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal(1, len(spans))
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal("http.request", s.Name)
|
||||||
|
assert.Equal("my-service", s.Service)
|
||||||
|
assert.Equal("GET "+url, s.Resource)
|
||||||
|
assert.Equal("500", s.GetMeta("http.status_code"))
|
||||||
|
assert.Equal("GET", s.GetMeta("http.method"))
|
||||||
|
assert.Equal(url, s.GetMeta("http.url"))
|
||||||
|
assert.Equal(int32(1), s.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(t *testing.T) (*tracer.Tracer, *tracertest.DummyTransport, http.Handler) {
|
||||||
|
h200 := handler200(t)
|
||||||
|
h500 := handler500(t)
|
||||||
|
tracer, transport := tracertest.GetTestTracer()
|
||||||
|
|
||||||
|
mux := NewServeMux("my-service", tracer)
|
||||||
|
mux.HandleFunc("/200", h200)
|
||||||
|
mux.HandleFunc("/500", h500)
|
||||||
|
|
||||||
|
return tracer, transport, mux
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler200(t *testing.T) http.HandlerFunc {
|
||||||
|
assert := assert.New(t)
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte("200!\n"))
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
span := tracer.SpanFromContextDefault(r.Context())
|
||||||
|
assert.Equal("my-service", span.Service)
|
||||||
|
assert.Equal(int64(0), span.Duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler500(t *testing.T) http.HandlerFunc {
|
||||||
|
assert := assert.New(t)
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "500!", http.StatusInternalServerError)
|
||||||
|
span := tracer.SpanFromContextDefault(r.Context())
|
||||||
|
|
||||||
|
assert.Equal("my-service", span.Service)
|
||||||
|
assert.Equal(int64(0), span.Duration)
|
||||||
|
}
|
||||||
|
}
|
86
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/elastictrace.go
generated
vendored
Normal file
86
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/elastictrace.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Package elastictrace provides tracing for the Elastic Elasticsearch client.
|
||||||
|
// Supports v3 (gopkg.in/olivere/elastic.v3), v5 (gopkg.in/olivere/elastic.v5)
|
||||||
|
// but with v3 you must use `DoC` on all requests to capture the request context.
|
||||||
|
package elastictrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxContentLength is the maximum content length for which we'll read and capture
|
||||||
|
// the contents of the request body. Anything larger will still be traced but the
|
||||||
|
// body will not be captured as trace metadata.
|
||||||
|
const MaxContentLength = 500 * 1024
|
||||||
|
|
||||||
|
// TracedTransport is a traced HTTP transport that captures Elasticsearch spans.
|
||||||
|
type TracedTransport struct {
|
||||||
|
service string
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
*http.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip satisfies the RoundTripper interface, wraps the sub Transport and
|
||||||
|
// captures a span of the Elasticsearch request.
|
||||||
|
func (t *TracedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
span := t.tracer.NewChildSpanFromContext("elasticsearch.query", req.Context())
|
||||||
|
span.Service = t.service
|
||||||
|
span.Type = ext.AppTypeDB
|
||||||
|
defer span.Finish()
|
||||||
|
span.SetMeta("elasticsearch.method", req.Method)
|
||||||
|
span.SetMeta("elasticsearch.url", req.URL.Path)
|
||||||
|
span.SetMeta("elasticsearch.params", req.URL.Query().Encode())
|
||||||
|
|
||||||
|
contentLength, _ := strconv.Atoi(req.Header.Get("Content-Length"))
|
||||||
|
if req.Body != nil && contentLength < MaxContentLength {
|
||||||
|
buf, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
span.SetMeta("elasticsearch.body", string(buf))
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the request using the standard transport.
|
||||||
|
res, err := t.Transport.RoundTrip(req)
|
||||||
|
if res != nil {
|
||||||
|
span.SetMeta(ext.HTTPCode, strconv.Itoa(res.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
} else if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||||
|
buf, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
// Status text is best we can do if if we can't read the body.
|
||||||
|
span.SetError(errors.New(http.StatusText(res.StatusCode)))
|
||||||
|
} else {
|
||||||
|
span.SetError(errors.New(string(buf)))
|
||||||
|
}
|
||||||
|
res.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
|
||||||
|
}
|
||||||
|
Quantize(span)
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTracedHTTPClient returns a new TracedTransport that traces HTTP requests.
|
||||||
|
func NewTracedHTTPClient(service string, tracer *tracer.Tracer) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &TracedTransport{service, tracer, &http.Transport{}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTracedHTTPClientWithTransport returns a new TracedTransport that traces HTTP requests
|
||||||
|
// and takes in a Transport to use something other than the default.
|
||||||
|
func NewTracedHTTPClientWithTransport(service string, tracer *tracer.Tracer, transport *http.Transport) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &TracedTransport{service, tracer, transport},
|
||||||
|
}
|
||||||
|
}
|
193
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/elastictrace_test.go
generated
vendored
Normal file
193
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/elastictrace_test.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package elastictrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
elasticv3 "gopkg.in/olivere/elastic.v3"
|
||||||
|
elasticv5 "gopkg.in/olivere/elastic.v5"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClientV5(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
tc := NewTracedHTTPClient("my-es-service", testTracer)
|
||||||
|
client, err := elasticv5.NewClient(
|
||||||
|
elasticv5.SetURL("http://127.0.0.1:59200"),
|
||||||
|
elasticv5.SetHttpClient(tc),
|
||||||
|
elasticv5.SetSniff(false),
|
||||||
|
elasticv5.SetHealthcheck(false),
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, err = client.Index().
|
||||||
|
Index("twitter").Id("1").
|
||||||
|
Type("tweet").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
Do(context.TODO())
|
||||||
|
assert.NoError(err)
|
||||||
|
checkPUTTrace(assert, testTracer, testTransport)
|
||||||
|
|
||||||
|
_, err = client.Get().Index("twitter").Type("tweet").
|
||||||
|
Id("1").Do(context.TODO())
|
||||||
|
assert.NoError(err)
|
||||||
|
checkGETTrace(assert, testTracer, testTransport)
|
||||||
|
|
||||||
|
_, err = client.Get().Index("not-real-index").
|
||||||
|
Id("1").Do(context.TODO())
|
||||||
|
assert.Error(err)
|
||||||
|
checkErrTrace(assert, testTracer, testTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientV3(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
tc := NewTracedHTTPClient("my-es-service", testTracer)
|
||||||
|
client, err := elasticv3.NewClient(
|
||||||
|
elasticv3.SetURL("http://127.0.0.1:59201"),
|
||||||
|
elasticv3.SetHttpClient(tc),
|
||||||
|
elasticv3.SetSniff(false),
|
||||||
|
elasticv3.SetHealthcheck(false),
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, err = client.Index().
|
||||||
|
Index("twitter").Id("1").
|
||||||
|
Type("tweet").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
DoC(context.TODO())
|
||||||
|
assert.NoError(err)
|
||||||
|
checkPUTTrace(assert, testTracer, testTransport)
|
||||||
|
|
||||||
|
_, err = client.Get().Index("twitter").Type("tweet").
|
||||||
|
Id("1").DoC(context.TODO())
|
||||||
|
assert.NoError(err)
|
||||||
|
checkGETTrace(assert, testTracer, testTransport)
|
||||||
|
|
||||||
|
_, err = client.Get().Index("not-real-index").
|
||||||
|
Id("1").DoC(context.TODO())
|
||||||
|
assert.Error(err)
|
||||||
|
checkErrTrace(assert, testTracer, testTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientV3Failure(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
tc := NewTracedHTTPClient("my-es-service", testTracer)
|
||||||
|
client, err := elasticv3.NewClient(
|
||||||
|
// not existing service, it must fail
|
||||||
|
elasticv3.SetURL("http://127.0.0.1:29201"),
|
||||||
|
elasticv3.SetHttpClient(tc),
|
||||||
|
elasticv3.SetSniff(false),
|
||||||
|
elasticv3.SetHealthcheck(false),
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, err = client.Index().
|
||||||
|
Index("twitter").Id("1").
|
||||||
|
Type("tweet").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
DoC(context.TODO())
|
||||||
|
assert.Error(err)
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("PUT /twitter/tweet/?", spans[0].Resource)
|
||||||
|
assert.Equal("/twitter/tweet/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.Equal("PUT", spans[0].GetMeta("elasticsearch.method"))
|
||||||
|
|
||||||
|
assert.NotEmpty(spans[0].GetMeta("error.msg"))
|
||||||
|
assert.Equal("*net.OpError", spans[0].GetMeta("error.type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientV5Failure(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
tc := NewTracedHTTPClient("my-es-service", testTracer)
|
||||||
|
client, err := elasticv5.NewClient(
|
||||||
|
// not existing service, it must fail
|
||||||
|
elasticv5.SetURL("http://127.0.0.1:29200"),
|
||||||
|
elasticv5.SetHttpClient(tc),
|
||||||
|
elasticv5.SetSniff(false),
|
||||||
|
elasticv5.SetHealthcheck(false),
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, err = client.Index().
|
||||||
|
Index("twitter").Id("1").
|
||||||
|
Type("tweet").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
Do(context.TODO())
|
||||||
|
assert.Error(err)
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("PUT /twitter/tweet/?", spans[0].Resource)
|
||||||
|
assert.Equal("/twitter/tweet/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.Equal("PUT", spans[0].GetMeta("elasticsearch.method"))
|
||||||
|
|
||||||
|
assert.NotEmpty(spans[0].GetMeta("error.msg"))
|
||||||
|
assert.Equal("*net.OpError", spans[0].GetMeta("error.type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPUTTrace(assert *assert.Assertions, tracer *tracer.Tracer, transport *tracertest.DummyTransport) {
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("PUT /twitter/tweet/?", spans[0].Resource)
|
||||||
|
assert.Equal("/twitter/tweet/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.Equal("PUT", spans[0].GetMeta("elasticsearch.method"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkGETTrace(assert *assert.Assertions, tracer *tracer.Tracer, transport *tracertest.DummyTransport) {
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("GET /twitter/tweet/?", spans[0].Resource)
|
||||||
|
assert.Equal("/twitter/tweet/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.Equal("GET", spans[0].GetMeta("elasticsearch.method"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkErrTrace(assert *assert.Assertions, tracer *tracer.Tracer, transport *tracertest.DummyTransport) {
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("GET /not-real-index/_all/?", spans[0].Resource)
|
||||||
|
assert.Equal("/not-real-index/_all/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.NotEmpty(spans[0].GetMeta("error.msg"))
|
||||||
|
assert.Equal("*errors.errorString", spans[0].GetMeta("error.type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *tracertest.DummyTransport) {
|
||||||
|
transport := &tracertest.DummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
57
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/example_test.go
generated
vendored
Normal file
57
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package elastictrace_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
elastictrace "github.com/DataDog/dd-trace-go/contrib/olivere/elastic"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
elasticv3 "gopkg.in/olivere/elastic.v3"
|
||||||
|
elasticv5 "gopkg.in/olivere/elastic.v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To start tracing elastic.v5 requests, create a new TracedHTTPClient that you will
|
||||||
|
// use when initializing the elastic.Client.
|
||||||
|
func Example_v5() {
|
||||||
|
tc := elastictrace.NewTracedHTTPClient("my-elasticsearch-service", tracer.DefaultTracer)
|
||||||
|
client, _ := elasticv5.NewClient(
|
||||||
|
elasticv5.SetURL("http://127.0.0.1:9200"),
|
||||||
|
elasticv5.SetHttpClient(tc),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Spans are emitted for all
|
||||||
|
client.Index().
|
||||||
|
Index("twitter").Type("tweet").Index("1").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
Do(context.Background())
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/tweet/1")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
client.Get().
|
||||||
|
Index("twitter").Type("tweet").Index("1").
|
||||||
|
Do(ctx)
|
||||||
|
root.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// To trace elastic.v3 you create a TracedHTTPClient in the same way but all requests must use
|
||||||
|
// the DoC() call to pass the request context.
|
||||||
|
func Example_v3() {
|
||||||
|
tc := elastictrace.NewTracedHTTPClient("my-elasticsearch-service", tracer.DefaultTracer)
|
||||||
|
client, _ := elasticv3.NewClient(
|
||||||
|
elasticv3.SetURL("http://127.0.0.1:9200"),
|
||||||
|
elasticv3.SetHttpClient(tc),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Spans are emitted for all
|
||||||
|
client.Index().
|
||||||
|
Index("twitter").Type("tweet").Index("1").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
DoC(context.Background())
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/tweet/1")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
client.Get().
|
||||||
|
Index("twitter").Type("tweet").Index("1").
|
||||||
|
DoC(ctx)
|
||||||
|
root.Finish()
|
||||||
|
}
|
26
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/quantize.go
generated
vendored
Normal file
26
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/quantize.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package elastictrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
IdRegexp = regexp.MustCompile("/([0-9]+)([/\\?]|$)")
|
||||||
|
IdPlaceholder = []byte("/?$2")
|
||||||
|
IndexRegexp = regexp.MustCompile("[0-9]{2,}")
|
||||||
|
IndexPlaceholder = []byte("?")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Quantize quantizes an Elasticsearch to extract a meaningful resource from the request.
|
||||||
|
// We quantize based on the method+url with some cleanup applied to the URL.
|
||||||
|
// URLs with an ID will be generalized as will (potential) timestamped indices.
|
||||||
|
func Quantize(span *tracer.Span) {
|
||||||
|
url := span.GetMeta("elasticsearch.url")
|
||||||
|
method := span.GetMeta("elasticsearch.method")
|
||||||
|
|
||||||
|
quantizedURL := IdRegexp.ReplaceAll([]byte(url), IdPlaceholder)
|
||||||
|
quantizedURL = IndexRegexp.ReplaceAll(quantizedURL, IndexPlaceholder)
|
||||||
|
span.Resource = fmt.Sprintf("%s %s", method, quantizedURL)
|
||||||
|
}
|
43
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/quantize_test.go
generated
vendored
Normal file
43
vendor/github.com/DataDog/dd-trace-go/contrib/olivere/elastic/quantize_test.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package elastictrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQuantize(t *testing.T) {
|
||||||
|
tr := tracer.NewTracer()
|
||||||
|
for _, tc := range []struct {
|
||||||
|
url, method string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
url: "/twitter/tweets",
|
||||||
|
method: "POST",
|
||||||
|
expected: "POST /twitter/tweets",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/logs_2016_05/event/_search",
|
||||||
|
method: "GET",
|
||||||
|
expected: "GET /logs_?_?/event/_search",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/twitter/tweets/123",
|
||||||
|
method: "GET",
|
||||||
|
expected: "GET /twitter/tweets/?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/logs_2016_05/event/123",
|
||||||
|
method: "PUT",
|
||||||
|
expected: "PUT /logs_?_?/event/?",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
span := tracer.NewSpan("name", "elasticsearch", "", 0, 0, 0, tr)
|
||||||
|
span.SetMeta("elasticsearch.url", tc.url)
|
||||||
|
span.SetMeta("elasticsearch.method", tc.method)
|
||||||
|
Quantize(span)
|
||||||
|
assert.Equal(t, tc.expected, span.Resource)
|
||||||
|
}
|
||||||
|
}
|
33
vendor/github.com/DataDog/dd-trace-go/tasks/benchmarks.rb
generated
vendored
Normal file
33
vendor/github.com/DataDog/dd-trace-go/tasks/benchmarks.rb
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
require_relative 'common'
|
||||||
|
|
||||||
|
namespace :collect do
|
||||||
|
desc 'Run client benchmarks'
|
||||||
|
task :benchmarks do
|
||||||
|
# TODO: benchmarks must be done for different Tracer versions
|
||||||
|
# so that we can retrieve the diff and return an exit code != 0
|
||||||
|
Tasks::Common.get_go_packages.each do |pkg|
|
||||||
|
sh "go test -run=NONE -bench=. #{pkg}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Run pprof to collect profiles'
|
||||||
|
task :profiles do
|
||||||
|
# initializes the folder to collect profiles
|
||||||
|
FileUtils.mkdir_p 'profiles'
|
||||||
|
filename = "#{Tasks::Common::PROFILES}/tracer"
|
||||||
|
|
||||||
|
# generate a profile for the Tracer based on benchmarks
|
||||||
|
sh %{
|
||||||
|
go test -run=NONE -bench=.
|
||||||
|
-cpuprofile=#{filename}-cpu.out
|
||||||
|
-memprofile=#{filename}-mem.out
|
||||||
|
-blockprofile=#{filename}-block.out
|
||||||
|
#{Tasks::Common::TRACER_PACKAGE}
|
||||||
|
}.gsub(/\s+/, ' ').strip
|
||||||
|
|
||||||
|
# export profiles
|
||||||
|
sh "go tool pprof -text -nodecount=10 -cum ./tracer.test #{filename}-cpu.out"
|
||||||
|
sh "go tool pprof -text -nodecount=10 -cum -inuse_space ./tracer.test #{filename}-mem.out"
|
||||||
|
sh "go tool pprof -text -nodecount=10 -cum ./tracer.test #{filename}-block.out"
|
||||||
|
end
|
||||||
|
end
|
12
vendor/github.com/DataDog/dd-trace-go/tasks/common.rb
generated
vendored
Normal file
12
vendor/github.com/DataDog/dd-trace-go/tasks/common.rb
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module Tasks
|
||||||
|
module Common
|
||||||
|
PROFILES = './profiles'
|
||||||
|
TRACER_PACKAGE = 'github.com/DataDog/dd-trace-go/tracer'
|
||||||
|
COVERAGE_FILE = 'code.cov'
|
||||||
|
|
||||||
|
# returns a list of Go packages
|
||||||
|
def self.get_go_packages
|
||||||
|
`go list ./opentracing ./tracer ./contrib/...`.split("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
41
vendor/github.com/DataDog/dd-trace-go/tasks/testing.rb
generated
vendored
Normal file
41
vendor/github.com/DataDog/dd-trace-go/tasks/testing.rb
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
require 'tempfile'
|
||||||
|
require_relative 'common'
|
||||||
|
|
||||||
|
namespace :test do
|
||||||
|
desc 'Run linting on the repository'
|
||||||
|
task :lint do
|
||||||
|
# enable-gc is required because with a full linting process we may finish workers memory
|
||||||
|
# fast is used temporarily for a faster CI
|
||||||
|
sh 'gometalinter --deadline 60s --fast --enable-gc --errors --vendor ./opentracing ./tracer ./contrib/...'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Test all packages'
|
||||||
|
task :all do
|
||||||
|
sh 'go test ./opentracing ./tracer ./contrib/...'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Test all packages with -race flag'
|
||||||
|
task :race do
|
||||||
|
sh 'go test -race ./opentracing ./tracer ./contrib/...'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Run test coverage'
|
||||||
|
task :coverage do
|
||||||
|
# collect global profiles in this file
|
||||||
|
sh "echo \"mode: count\" > #{Tasks::Common::COVERAGE_FILE}"
|
||||||
|
|
||||||
|
# for each package collect and append the profile
|
||||||
|
Tasks::Common.get_go_packages.each do |pkg|
|
||||||
|
begin
|
||||||
|
f = Tempfile.new('profile')
|
||||||
|
# run code coverage
|
||||||
|
sh "go test -short -covermode=count -coverprofile=#{f.path} #{pkg}"
|
||||||
|
sh "cat #{f.path} | tail -n +2 >> #{Tasks::Common::COVERAGE_FILE}"
|
||||||
|
ensure
|
||||||
|
File.delete(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sh "go tool cover -func #{Tasks::Common::COVERAGE_FILE}"
|
||||||
|
end
|
||||||
|
end
|
23
vendor/github.com/DataDog/dd-trace-go/tasks/vendors.rb
generated
vendored
Normal file
23
vendor/github.com/DataDog/dd-trace-go/tasks/vendors.rb
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
desc 'Initialize the development environment'
|
||||||
|
task :init do
|
||||||
|
sh 'go get -u github.com/golang/dep/cmd/dep'
|
||||||
|
sh 'go get -u github.com/alecthomas/gometalinter'
|
||||||
|
sh 'gometalinter --install'
|
||||||
|
|
||||||
|
# TODO:bertrand remove this
|
||||||
|
# It is only a short-term workaround, we should find a proper way to handle
|
||||||
|
# multiple versions of the same dependency
|
||||||
|
sh 'go get -d google.golang.org/grpc'
|
||||||
|
gopath = ENV["GOPATH"].split(":")[0]
|
||||||
|
sh "cd #{gopath}/src/google.golang.org/grpc/ && git checkout v1.5.2 && cd -"
|
||||||
|
sh "go get -t -v ./contrib/..."
|
||||||
|
sh "go get -v github.com/opentracing/opentracing-go"
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :vendors do
|
||||||
|
desc "Update the vendors list"
|
||||||
|
task :update do
|
||||||
|
# download and update our vendors
|
||||||
|
sh 'dep ensure'
|
||||||
|
end
|
||||||
|
end
|
3
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/README.md
generated
vendored
Normal file
3
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/README.md
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# [DEPRECATED] Libraries supported for tracing
|
||||||
|
|
||||||
|
This folder will be dropped on the medium-term. It's now located at [`/contrib`](https://github.com/DataDog/dd-trace-go/tree/master/contrib).
|
86
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/elastictraced.go
generated
vendored
Normal file
86
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/elastictraced.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Package elastictraced provides tracing for the Elastic Elasticsearch client.
|
||||||
|
// Supports v3 (gopkg.in/olivere/elastic.v3), v5 (gopkg.in/olivere/elastic.v5)
|
||||||
|
// but with v3 you must use `DoC` on all requests to capture the request context.
|
||||||
|
package elastictraced
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxContentLength is the maximum content length for which we'll read and capture
|
||||||
|
// the contents of the request body. Anything larger will still be traced but the
|
||||||
|
// body will not be captured as trace metadata.
|
||||||
|
const MaxContentLength = 500 * 1024
|
||||||
|
|
||||||
|
// TracedTransport is a traced HTTP transport that captures Elasticsearch spans.
|
||||||
|
type TracedTransport struct {
|
||||||
|
service string
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
*http.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip satisfies the RoundTripper interface, wraps the sub Transport and
|
||||||
|
// captures a span of the Elasticsearch request.
|
||||||
|
func (t *TracedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
span := t.tracer.NewChildSpanFromContext("elasticsearch.query", req.Context())
|
||||||
|
span.Service = t.service
|
||||||
|
span.Type = ext.AppTypeDB
|
||||||
|
defer span.Finish()
|
||||||
|
span.SetMeta("elasticsearch.method", req.Method)
|
||||||
|
span.SetMeta("elasticsearch.url", req.URL.Path)
|
||||||
|
span.SetMeta("elasticsearch.params", req.URL.Query().Encode())
|
||||||
|
|
||||||
|
contentLength, _ := strconv.Atoi(req.Header.Get("Content-Length"))
|
||||||
|
if req.Body != nil && contentLength < MaxContentLength {
|
||||||
|
buf, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
span.SetMeta("elasticsearch.body", string(buf))
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the request using the standard transport.
|
||||||
|
res, err := t.Transport.RoundTrip(req)
|
||||||
|
if res != nil {
|
||||||
|
span.SetMeta(ext.HTTPCode, strconv.Itoa(res.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
} else if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||||
|
buf, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
// Status text is best we can do if if we can't read the body.
|
||||||
|
span.SetError(errors.New(http.StatusText(res.StatusCode)))
|
||||||
|
} else {
|
||||||
|
span.SetError(errors.New(string(buf)))
|
||||||
|
}
|
||||||
|
res.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
|
||||||
|
}
|
||||||
|
Quantize(span)
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTracedHTTPClient returns a new TracedTransport that traces HTTP requests.
|
||||||
|
func NewTracedHTTPClient(service string, tracer *tracer.Tracer) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &TracedTransport{service, tracer, &http.Transport{}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTracedHTTPClientWithTransport returns a new TracedTransport that traces HTTP requests
|
||||||
|
// and takes in a Transport to use something other than the default.
|
||||||
|
func NewTracedHTTPClientWithTransport(service string, tracer *tracer.Tracer, transport *http.Transport) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &TracedTransport{service, tracer, transport},
|
||||||
|
}
|
||||||
|
}
|
193
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/elastictraced_test.go
generated
vendored
Normal file
193
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/elastictraced_test.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package elastictraced
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
elasticv3 "gopkg.in/olivere/elastic.v3"
|
||||||
|
elasticv5 "gopkg.in/olivere/elastic.v5"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClientV5(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
tc := NewTracedHTTPClient("my-es-service", testTracer)
|
||||||
|
client, err := elasticv5.NewClient(
|
||||||
|
elasticv5.SetURL("http://127.0.0.1:59200"),
|
||||||
|
elasticv5.SetHttpClient(tc),
|
||||||
|
elasticv5.SetSniff(false),
|
||||||
|
elasticv5.SetHealthcheck(false),
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, err = client.Index().
|
||||||
|
Index("twitter").Id("1").
|
||||||
|
Type("tweet").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
Do(context.TODO())
|
||||||
|
assert.NoError(err)
|
||||||
|
checkPUTTrace(assert, testTracer, testTransport)
|
||||||
|
|
||||||
|
_, err = client.Get().Index("twitter").Type("tweet").
|
||||||
|
Id("1").Do(context.TODO())
|
||||||
|
assert.NoError(err)
|
||||||
|
checkGETTrace(assert, testTracer, testTransport)
|
||||||
|
|
||||||
|
_, err = client.Get().Index("not-real-index").
|
||||||
|
Id("1").Do(context.TODO())
|
||||||
|
assert.Error(err)
|
||||||
|
checkErrTrace(assert, testTracer, testTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientV3(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
tc := NewTracedHTTPClient("my-es-service", testTracer)
|
||||||
|
client, err := elasticv3.NewClient(
|
||||||
|
elasticv3.SetURL("http://127.0.0.1:59201"),
|
||||||
|
elasticv3.SetHttpClient(tc),
|
||||||
|
elasticv3.SetSniff(false),
|
||||||
|
elasticv3.SetHealthcheck(false),
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, err = client.Index().
|
||||||
|
Index("twitter").Id("1").
|
||||||
|
Type("tweet").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
DoC(context.TODO())
|
||||||
|
assert.NoError(err)
|
||||||
|
checkPUTTrace(assert, testTracer, testTransport)
|
||||||
|
|
||||||
|
_, err = client.Get().Index("twitter").Type("tweet").
|
||||||
|
Id("1").DoC(context.TODO())
|
||||||
|
assert.NoError(err)
|
||||||
|
checkGETTrace(assert, testTracer, testTransport)
|
||||||
|
|
||||||
|
_, err = client.Get().Index("not-real-index").
|
||||||
|
Id("1").DoC(context.TODO())
|
||||||
|
assert.Error(err)
|
||||||
|
checkErrTrace(assert, testTracer, testTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientV3Failure(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
tc := NewTracedHTTPClient("my-es-service", testTracer)
|
||||||
|
client, err := elasticv3.NewClient(
|
||||||
|
// not existing service, it must fail
|
||||||
|
elasticv3.SetURL("http://127.0.0.1:29201"),
|
||||||
|
elasticv3.SetHttpClient(tc),
|
||||||
|
elasticv3.SetSniff(false),
|
||||||
|
elasticv3.SetHealthcheck(false),
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, err = client.Index().
|
||||||
|
Index("twitter").Id("1").
|
||||||
|
Type("tweet").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
DoC(context.TODO())
|
||||||
|
assert.Error(err)
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("PUT /twitter/tweet/?", spans[0].Resource)
|
||||||
|
assert.Equal("/twitter/tweet/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.Equal("PUT", spans[0].GetMeta("elasticsearch.method"))
|
||||||
|
|
||||||
|
assert.NotEmpty(spans[0].GetMeta("error.msg"))
|
||||||
|
assert.Equal("*net.OpError", spans[0].GetMeta("error.type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientV5Failure(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
tc := NewTracedHTTPClient("my-es-service", testTracer)
|
||||||
|
client, err := elasticv5.NewClient(
|
||||||
|
// not existing service, it must fail
|
||||||
|
elasticv5.SetURL("http://127.0.0.1:29200"),
|
||||||
|
elasticv5.SetHttpClient(tc),
|
||||||
|
elasticv5.SetSniff(false),
|
||||||
|
elasticv5.SetHealthcheck(false),
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
_, err = client.Index().
|
||||||
|
Index("twitter").Id("1").
|
||||||
|
Type("tweet").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
Do(context.TODO())
|
||||||
|
assert.Error(err)
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("PUT /twitter/tweet/?", spans[0].Resource)
|
||||||
|
assert.Equal("/twitter/tweet/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.Equal("PUT", spans[0].GetMeta("elasticsearch.method"))
|
||||||
|
|
||||||
|
assert.NotEmpty(spans[0].GetMeta("error.msg"))
|
||||||
|
assert.Equal("*net.OpError", spans[0].GetMeta("error.type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPUTTrace(assert *assert.Assertions, tracer *tracer.Tracer, transport *tracertest.DummyTransport) {
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("PUT /twitter/tweet/?", spans[0].Resource)
|
||||||
|
assert.Equal("/twitter/tweet/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.Equal("PUT", spans[0].GetMeta("elasticsearch.method"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkGETTrace(assert *assert.Assertions, tracer *tracer.Tracer, transport *tracertest.DummyTransport) {
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("GET /twitter/tweet/?", spans[0].Resource)
|
||||||
|
assert.Equal("/twitter/tweet/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.Equal("GET", spans[0].GetMeta("elasticsearch.method"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkErrTrace(assert *assert.Assertions, tracer *tracer.Tracer, transport *tracertest.DummyTransport) {
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Equal("my-es-service", spans[0].Service)
|
||||||
|
assert.Equal("GET /not-real-index/_all/?", spans[0].Resource)
|
||||||
|
assert.Equal("/not-real-index/_all/1", spans[0].GetMeta("elasticsearch.url"))
|
||||||
|
assert.NotEmpty(spans[0].GetMeta("error.msg"))
|
||||||
|
assert.Equal("*errors.errorString", spans[0].GetMeta("error.type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *tracertest.DummyTransport) {
|
||||||
|
transport := &tracertest.DummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
57
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/example_test.go
generated
vendored
Normal file
57
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package elastictraced_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced"
|
||||||
|
elasticv3 "gopkg.in/olivere/elastic.v3"
|
||||||
|
elasticv5 "gopkg.in/olivere/elastic.v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To start tracing elastic.v5 requests, create a new TracedHTTPClient that you will
|
||||||
|
// use when initializing the elastic.Client.
|
||||||
|
func Example_v5() {
|
||||||
|
tc := elastictraced.NewTracedHTTPClient("my-elasticsearch-service", tracer.DefaultTracer)
|
||||||
|
client, _ := elasticv5.NewClient(
|
||||||
|
elasticv5.SetURL("http://127.0.0.1:9200"),
|
||||||
|
elasticv5.SetHttpClient(tc),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Spans are emitted for all
|
||||||
|
client.Index().
|
||||||
|
Index("twitter").Type("tweet").Index("1").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
Do(context.Background())
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/tweet/1")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
client.Get().
|
||||||
|
Index("twitter").Type("tweet").Index("1").
|
||||||
|
Do(ctx)
|
||||||
|
root.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// To trace elastic.v3 you create a TracedHTTPClient in the same way but all requests must use
|
||||||
|
// the DoC() call to pass the request context.
|
||||||
|
func Example_v3() {
|
||||||
|
tc := elastictraced.NewTracedHTTPClient("my-elasticsearch-service", tracer.DefaultTracer)
|
||||||
|
client, _ := elasticv3.NewClient(
|
||||||
|
elasticv3.SetURL("http://127.0.0.1:9200"),
|
||||||
|
elasticv3.SetHttpClient(tc),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Spans are emitted for all
|
||||||
|
client.Index().
|
||||||
|
Index("twitter").Type("tweet").Index("1").
|
||||||
|
BodyString(`{"user": "test", "message": "hello"}`).
|
||||||
|
DoC(context.Background())
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/tweet/1")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
client.Get().
|
||||||
|
Index("twitter").Type("tweet").Index("1").
|
||||||
|
DoC(ctx)
|
||||||
|
root.Finish()
|
||||||
|
}
|
26
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/quantize.go
generated
vendored
Normal file
26
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/quantize.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package elastictraced
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
IdRegexp = regexp.MustCompile("/([0-9]+)([/\\?]|$)")
|
||||||
|
IdPlaceholder = []byte("/?$2")
|
||||||
|
IndexRegexp = regexp.MustCompile("[0-9]{2,}")
|
||||||
|
IndexPlaceholder = []byte("?")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Quantize quantizes an Elasticsearch to extract a meaningful resource from the request.
|
||||||
|
// We quantize based on the method+url with some cleanup applied to the URL.
|
||||||
|
// URLs with an ID will be generalized as will (potential) timestamped indices.
|
||||||
|
func Quantize(span *tracer.Span) {
|
||||||
|
url := span.GetMeta("elasticsearch.url")
|
||||||
|
method := span.GetMeta("elasticsearch.method")
|
||||||
|
|
||||||
|
quantizedURL := IdRegexp.ReplaceAll([]byte(url), IdPlaceholder)
|
||||||
|
quantizedURL = IndexRegexp.ReplaceAll(quantizedURL, IndexPlaceholder)
|
||||||
|
span.Resource = fmt.Sprintf("%s %s", method, quantizedURL)
|
||||||
|
}
|
43
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/quantize_test.go
generated
vendored
Normal file
43
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/elastictraced/quantize_test.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package elastictraced
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQuantize(t *testing.T) {
|
||||||
|
tr := tracer.NewTracer()
|
||||||
|
for _, tc := range []struct {
|
||||||
|
url, method string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
url: "/twitter/tweets",
|
||||||
|
method: "POST",
|
||||||
|
expected: "POST /twitter/tweets",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/logs_2016_05/event/_search",
|
||||||
|
method: "GET",
|
||||||
|
expected: "GET /logs_?_?/event/_search",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/twitter/tweets/123",
|
||||||
|
method: "GET",
|
||||||
|
expected: "GET /twitter/tweets/?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/logs_2016_05/event/123",
|
||||||
|
method: "PUT",
|
||||||
|
expected: "PUT /logs_?_?/event/?",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
span := tracer.NewSpan("name", "elasticsearch", "", 0, 0, 0, tr)
|
||||||
|
span.SetMeta("elasticsearch.url", tc.url)
|
||||||
|
span.SetMeta("elasticsearch.method", tc.method)
|
||||||
|
Quantize(span)
|
||||||
|
assert.Equal(t, tc.expected, span.Resource)
|
||||||
|
}
|
||||||
|
}
|
59
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace/example_test.go
generated
vendored
Normal file
59
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package gintrace_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To start tracing requests, add the trace middleware to your Gin router.
|
||||||
|
func Example() {
|
||||||
|
|
||||||
|
// Create your router and use the middleware.
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(gintrace.Middleware("my-web-app"))
|
||||||
|
|
||||||
|
r.GET("/hello", func(c *gin.Context) {
|
||||||
|
c.String(200, "hello world!")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Profit!
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleHTML() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(gintrace.Middleware("my-web-app"))
|
||||||
|
r.LoadHTMLGlob("templates/*")
|
||||||
|
|
||||||
|
r.GET("/index", func(c *gin.Context) {
|
||||||
|
// This will render the html and trace the execution time.
|
||||||
|
gintrace.HTML(c, 200, "index.tmpl", gin.H{
|
||||||
|
"title": "Main website",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSpanDefault() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(gintrace.Middleware("image-encoder"))
|
||||||
|
|
||||||
|
r.GET("/image/encode", func(c *gin.Context) {
|
||||||
|
// The middleware patches a span to the request. Let's add some metadata,
|
||||||
|
// and create a child span.
|
||||||
|
span := gintrace.SpanDefault(c)
|
||||||
|
span.SetMeta("user.handle", "admin")
|
||||||
|
span.SetMeta("user.id", "1234")
|
||||||
|
|
||||||
|
encodeSpan := tracer.NewChildSpan("image.encode", span)
|
||||||
|
// encode a image
|
||||||
|
encodeSpan.Finish()
|
||||||
|
|
||||||
|
uploadSpan := tracer.NewChildSpan("image.upload", span)
|
||||||
|
// upload the image
|
||||||
|
uploadSpan.Finish()
|
||||||
|
|
||||||
|
c.String(200, "ok!")
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
143
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace/gintrace.go
generated
vendored
Normal file
143
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace/gintrace.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
// Package gintrace provides tracing middleware for the Gin web framework.
|
||||||
|
package gintrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// key is the string that we'll use to store spans in the tracer.
|
||||||
|
var key = "datadog_trace_span"
|
||||||
|
|
||||||
|
// Middleware returns middleware that will trace requests with the default
|
||||||
|
// tracer.
|
||||||
|
func Middleware(service string) gin.HandlerFunc {
|
||||||
|
return MiddlewareTracer(service, tracer.DefaultTracer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiddlewareTracer returns middleware that will trace requests with the given
|
||||||
|
// tracer.
|
||||||
|
func MiddlewareTracer(service string, t *tracer.Tracer) gin.HandlerFunc {
|
||||||
|
t.SetServiceInfo(service, "gin-gonic", ext.AppTypeWeb)
|
||||||
|
mw := newMiddleware(service, t)
|
||||||
|
return mw.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// middleware implements gin middleware.
|
||||||
|
type middleware struct {
|
||||||
|
service string
|
||||||
|
trc *tracer.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMiddleware(service string, trc *tracer.Tracer) *middleware {
|
||||||
|
return &middleware{
|
||||||
|
service: service,
|
||||||
|
trc: trc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle is a gin HandlerFunc that will add tracing to the given request.
|
||||||
|
func (m *middleware) Handle(c *gin.Context) {
|
||||||
|
|
||||||
|
// bail if not enabled
|
||||||
|
if !m.trc.Enabled() {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME[matt] the handler name is a bit unwieldy and uses reflection
|
||||||
|
// under the hood. might be better to tackle this task and do it right
|
||||||
|
// so we can end up with "user/:user/whatever" instead of
|
||||||
|
// "github.com/foobar/blah"
|
||||||
|
//
|
||||||
|
// See here: https://github.com/gin-gonic/gin/issues/649
|
||||||
|
resource := c.HandlerName()
|
||||||
|
|
||||||
|
// Create our span and patch it to the context for downstream.
|
||||||
|
span := m.trc.NewRootSpan("gin.request", m.service, resource)
|
||||||
|
c.Set(key, span)
|
||||||
|
|
||||||
|
// Pass along the request.
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// Set http tags.
|
||||||
|
span.SetMeta(ext.HTTPCode, strconv.Itoa(c.Writer.Status()))
|
||||||
|
span.SetMeta(ext.HTTPMethod, c.Request.Method)
|
||||||
|
span.SetMeta(ext.HTTPURL, c.Request.URL.Path)
|
||||||
|
|
||||||
|
// Set any error information.
|
||||||
|
var err error
|
||||||
|
if len(c.Errors) > 0 {
|
||||||
|
span.SetMeta("gin.errors", c.Errors.String()) // set all errors
|
||||||
|
err = c.Errors[0] // but use the first for standard fields
|
||||||
|
}
|
||||||
|
span.FinishWithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span returns the Span stored in the given Context and true. If it doesn't exist,
|
||||||
|
// it will returns (nil, false)
|
||||||
|
func Span(c *gin.Context) (*tracer.Span, bool) {
|
||||||
|
if c == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := c.Get(key)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
switch span := s.(type) {
|
||||||
|
case *tracer.Span:
|
||||||
|
return span, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpanDefault returns the span stored in the given Context. If none exists,
|
||||||
|
// it will return an empty span.
|
||||||
|
func SpanDefault(c *gin.Context) *tracer.Span {
|
||||||
|
span, ok := Span(c)
|
||||||
|
if !ok {
|
||||||
|
return &tracer.Span{}
|
||||||
|
}
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChildSpan will create a span that is the child of the span stored in
|
||||||
|
// the context.
|
||||||
|
func NewChildSpan(name string, c *gin.Context) *tracer.Span {
|
||||||
|
span, ok := Span(c)
|
||||||
|
if !ok {
|
||||||
|
return &tracer.Span{}
|
||||||
|
}
|
||||||
|
return span.Tracer().NewChildSpan(name, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML will trace the rendering of the template as a child of the span in the
|
||||||
|
// given context.
|
||||||
|
func HTML(c *gin.Context, code int, name string, obj interface{}) {
|
||||||
|
span, _ := Span(c)
|
||||||
|
if span == nil {
|
||||||
|
c.HTML(code, name, obj)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
child := span.Tracer().NewChildSpan("gin.render.html", span)
|
||||||
|
child.SetMeta("go.template", name)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err := fmt.Errorf("error rendering tmpl:%s: %s", name, r)
|
||||||
|
child.FinishWithErr(err)
|
||||||
|
panic(r)
|
||||||
|
} else {
|
||||||
|
child.Finish()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// render
|
||||||
|
c.HTML(code, name, obj)
|
||||||
|
}
|
250
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace/gintrace_test.go
generated
vendored
Normal file
250
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace/gintrace_test.go
generated
vendored
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
package gintrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gin.SetMode(gin.ReleaseMode) // silence annoying log msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChildSpan(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, _ := getTestTracer()
|
||||||
|
|
||||||
|
middleware := newMiddleware("foobar", testTracer)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
router.GET("/user/:id", func(c *gin.Context) {
|
||||||
|
span, ok := tracer.SpanFromContext(c)
|
||||||
|
assert.True(ok)
|
||||||
|
assert.NotNil(span)
|
||||||
|
})
|
||||||
|
|
||||||
|
r := httptest.NewRequest("GET", "/user/123", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrace200(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
|
||||||
|
middleware := newMiddleware("foobar", testTracer)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
router.GET("/user/:id", func(c *gin.Context) {
|
||||||
|
// assert we patch the span on the request context.
|
||||||
|
span := SpanDefault(c)
|
||||||
|
span.SetMeta("test.gin", "ginny")
|
||||||
|
assert.Equal(span.Service, "foobar")
|
||||||
|
id := c.Param("id")
|
||||||
|
c.Writer.Write([]byte(id))
|
||||||
|
})
|
||||||
|
|
||||||
|
r := httptest.NewRequest("GET", "/user/123", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// do and verify the request
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 200)
|
||||||
|
|
||||||
|
// verify traces look good
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
if len(spans) < 1 {
|
||||||
|
t.Fatalf("no spans")
|
||||||
|
}
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Service, "foobar")
|
||||||
|
assert.Equal(s.Name, "gin.request")
|
||||||
|
// FIXME[matt] would be much nicer to have "/user/:id" here
|
||||||
|
assert.True(strings.Contains(s.Resource, "gintrace.TestTrace200"))
|
||||||
|
assert.Equal(s.GetMeta("test.gin"), "ginny")
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "200")
|
||||||
|
assert.Equal(s.GetMeta("http.method"), "GET")
|
||||||
|
assert.Equal(s.GetMeta("http.url"), "/user/123")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabled(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetEnabled(false)
|
||||||
|
|
||||||
|
middleware := newMiddleware("foobar", testTracer)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
|
span, ok := Span(c)
|
||||||
|
assert.Nil(span)
|
||||||
|
assert.False(ok)
|
||||||
|
c.Writer.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
|
||||||
|
r := httptest.NewRequest("GET", "/ping", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// do and verify the request
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 200)
|
||||||
|
|
||||||
|
// verify traces look good
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
spans := testTransport.Traces()
|
||||||
|
assert.Len(spans, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
|
||||||
|
// setup
|
||||||
|
middleware := newMiddleware("foobar", testTracer)
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
|
||||||
|
// a handler with an error and make the requests
|
||||||
|
router.GET("/err", func(c *gin.Context) {
|
||||||
|
c.AbortWithError(500, errors.New("oh no"))
|
||||||
|
})
|
||||||
|
r := httptest.NewRequest("GET", "/err", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 500)
|
||||||
|
|
||||||
|
// verify the errors and status are correct
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
if len(spans) < 1 {
|
||||||
|
t.Fatalf("no spans")
|
||||||
|
}
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Service, "foobar")
|
||||||
|
assert.Equal(s.Name, "gin.request")
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "500")
|
||||||
|
assert.Equal(s.GetMeta(ext.ErrorMsg), "oh no")
|
||||||
|
assert.Equal(s.Error, int32(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTML(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
|
||||||
|
// setup
|
||||||
|
middleware := newMiddleware("tmplservice", testTracer)
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middleware.Handle)
|
||||||
|
|
||||||
|
// add a template
|
||||||
|
tmpl := template.Must(template.New("hello").Parse("hello {{.}}"))
|
||||||
|
router.SetHTMLTemplate(tmpl)
|
||||||
|
|
||||||
|
// a handler with an error and make the requests
|
||||||
|
router.GET("/hello", func(c *gin.Context) {
|
||||||
|
HTML(c, 200, "hello", "world")
|
||||||
|
})
|
||||||
|
r := httptest.NewRequest("GET", "/hello", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 200)
|
||||||
|
assert.Equal("hello world", w.Body.String())
|
||||||
|
|
||||||
|
// verify the errors and status are correct
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
for _, s := range spans {
|
||||||
|
assert.Equal(s.Service, "tmplservice")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tspan *tracer.Span
|
||||||
|
for _, s := range spans {
|
||||||
|
// we need to pick up the span we're searching for, as the
|
||||||
|
// order is not garanteed within the buffer
|
||||||
|
if s.Name == "gin.render.html" {
|
||||||
|
tspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotNil(tspan, "we should have found a span with name gin.render.html")
|
||||||
|
assert.Equal(tspan.GetMeta("go.template"), "hello")
|
||||||
|
fmt.Println(spans)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSpanNotInstrumented(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
router := gin.New()
|
||||||
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
|
// Assert we don't have a span on the context.
|
||||||
|
s, ok := Span(c)
|
||||||
|
assert.False(ok)
|
||||||
|
assert.Nil(s)
|
||||||
|
// and the default span is empty
|
||||||
|
s = SpanDefault(c)
|
||||||
|
assert.Equal(s.Service, "")
|
||||||
|
c.Writer.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
r := httptest.NewRequest("GET", "/ping", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
response := w.Result()
|
||||||
|
assert.Equal(response.StatusCode, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
81
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/go-redis/example_test.go
generated
vendored
Normal file
81
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/go-redis/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package goredistrace_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/gin-gonic/gintrace"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/go-redis"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
redis "github.com/go-redis/redis"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To start tracing Redis commands, use the NewTracedClient function to create a traced Redis clienty,
|
||||||
|
// passing in a service name of choice.
|
||||||
|
func Example() {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:6379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
c := goredistrace.NewTracedClient(opts, tracer.DefaultTracer, "my-redis-backend")
|
||||||
|
// Emit spans per command by using your Redis connection as usual
|
||||||
|
c.Set("test_key", "test_value", 0)
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// When set with a context, the traced client will emit a span inheriting from 'parent.request'
|
||||||
|
c.SetContext(ctx)
|
||||||
|
c.Set("food", "cheese", 0)
|
||||||
|
root.Finish()
|
||||||
|
|
||||||
|
// Contexts can be easily passed between Datadog integrations
|
||||||
|
r := gin.Default()
|
||||||
|
r.Use(gintrace.Middleware("web-admin"))
|
||||||
|
client := goredistrace.NewTracedClient(opts, tracer.DefaultTracer, "redis-img-backend")
|
||||||
|
|
||||||
|
r.GET("/user/settings/:id", func(ctx *gin.Context) {
|
||||||
|
// create a span that is a child of your http request
|
||||||
|
client.SetContext(ctx)
|
||||||
|
client.Get(fmt.Sprintf("cached_user_details_%s", ctx.Param("id")))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can also trace Redis Pipelines
|
||||||
|
func Example_pipeline() {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:6379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
c := goredistrace.NewTracedClient(opts, tracer.DefaultTracer, "my-redis-backend")
|
||||||
|
// pipe is a TracedPipeliner
|
||||||
|
pipe := c.Pipeline()
|
||||||
|
pipe.Incr("pipeline_counter")
|
||||||
|
pipe.Expire("pipeline_counter", time.Hour)
|
||||||
|
|
||||||
|
pipe.Exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewTracedClient() {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:6379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
c := goredistrace.NewTracedClient(opts, tracer.DefaultTracer, "my-redis-backend")
|
||||||
|
// Emit spans per command by using your Redis connection as usual
|
||||||
|
c.Set("test_key", "test_value", 0)
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// When set with a context, the traced client will emit a span inheriting from 'parent.request'
|
||||||
|
c.SetContext(ctx)
|
||||||
|
c.Set("food", "cheese", 0)
|
||||||
|
root.Finish()
|
||||||
|
}
|
151
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/go-redis/tracedredis.go
generated
vendored
Normal file
151
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/go-redis/tracedredis.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
// Package goredistrace provides tracing for the go-redis Redis client (https://github.com/go-redis/redis)
|
||||||
|
package goredistrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TracedClient is used to trace requests to a redis server.
|
||||||
|
type TracedClient struct {
|
||||||
|
*redis.Client
|
||||||
|
traceParams traceParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracedPipeline is used to trace pipelines executed on a redis server.
|
||||||
|
type TracedPipeliner struct {
|
||||||
|
redis.Pipeliner
|
||||||
|
traceParams traceParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type traceParams struct {
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
db string
|
||||||
|
service string
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTracedClient takes a Client returned by redis.NewClient and configures it to emit spans under the given service name
|
||||||
|
func NewTracedClient(opt *redis.Options, t *tracer.Tracer, service string) *TracedClient {
|
||||||
|
var host, port string
|
||||||
|
addr := strings.Split(opt.Addr, ":")
|
||||||
|
if len(addr) == 2 && addr[1] != "" {
|
||||||
|
port = addr[1]
|
||||||
|
} else {
|
||||||
|
port = "6379"
|
||||||
|
}
|
||||||
|
host = addr[0]
|
||||||
|
db := strconv.Itoa(opt.DB)
|
||||||
|
|
||||||
|
client := redis.NewClient(opt)
|
||||||
|
t.SetServiceInfo(service, "redis", ext.AppTypeDB)
|
||||||
|
tc := &TracedClient{
|
||||||
|
client,
|
||||||
|
traceParams{
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
db,
|
||||||
|
service,
|
||||||
|
t},
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.Client.WrapProcess(createWrapperFromClient(tc))
|
||||||
|
return tc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipeline creates a TracedPipeline from a TracedClient
|
||||||
|
func (c *TracedClient) Pipeline() *TracedPipeliner {
|
||||||
|
return &TracedPipeliner{
|
||||||
|
c.Client.Pipeline(),
|
||||||
|
c.traceParams,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecWithContext calls Pipeline.Exec(). It ensures that the resulting Redis calls
|
||||||
|
// are traced, and that emitted spans are children of the given Context
|
||||||
|
func (c *TracedPipeliner) ExecWithContext(ctx context.Context) ([]redis.Cmder, error) {
|
||||||
|
span := c.traceParams.tracer.NewChildSpanFromContext("redis.command", ctx)
|
||||||
|
span.Service = c.traceParams.service
|
||||||
|
|
||||||
|
span.SetMeta("out.host", c.traceParams.host)
|
||||||
|
span.SetMeta("out.port", c.traceParams.port)
|
||||||
|
span.SetMeta("out.db", c.traceParams.db)
|
||||||
|
|
||||||
|
cmds, err := c.Pipeliner.Exec()
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
span.Resource = String(cmds)
|
||||||
|
span.SetMeta("redis.pipeline_length", strconv.Itoa(len(cmds)))
|
||||||
|
span.Finish()
|
||||||
|
return cmds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec calls Pipeline.Exec() ensuring that the resulting Redis calls are traced
|
||||||
|
func (c *TracedPipeliner) Exec() ([]redis.Cmder, error) {
|
||||||
|
span := c.traceParams.tracer.NewRootSpan("redis.command", c.traceParams.service, "redis")
|
||||||
|
|
||||||
|
span.SetMeta("out.host", c.traceParams.host)
|
||||||
|
span.SetMeta("out.port", c.traceParams.port)
|
||||||
|
span.SetMeta("out.db", c.traceParams.db)
|
||||||
|
|
||||||
|
cmds, err := c.Pipeliner.Exec()
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
span.Resource = String(cmds)
|
||||||
|
span.SetMeta("redis.pipeline_length", strconv.Itoa(len(cmds)))
|
||||||
|
span.Finish()
|
||||||
|
return cmds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of a slice of redis Commands, separated by newlines
|
||||||
|
func String(cmds []redis.Cmder) string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
b.WriteString(cmd.String())
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext sets a context on a TracedClient. Use it to ensure that emitted spans have the correct parent
|
||||||
|
func (c *TracedClient) SetContext(ctx context.Context) {
|
||||||
|
c.Client = c.Client.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createWrapperFromClient wraps tracing into redis.Process().
|
||||||
|
func createWrapperFromClient(tc *TracedClient) func(oldProcess func(cmd redis.Cmder) error) func(cmd redis.Cmder) error {
|
||||||
|
return func(oldProcess func(cmd redis.Cmder) error) func(cmd redis.Cmder) error {
|
||||||
|
return func(cmd redis.Cmder) error {
|
||||||
|
ctx := tc.Client.Context()
|
||||||
|
|
||||||
|
var resource string
|
||||||
|
resource = strings.Split(cmd.String(), " ")[0]
|
||||||
|
args_length := len(strings.Split(cmd.String(), " ")) - 1
|
||||||
|
span := tc.traceParams.tracer.NewChildSpanFromContext("redis.command", ctx)
|
||||||
|
|
||||||
|
span.Service = tc.traceParams.service
|
||||||
|
span.Resource = resource
|
||||||
|
|
||||||
|
span.SetMeta("redis.raw_command", cmd.String())
|
||||||
|
span.SetMeta("redis.args_length", strconv.Itoa(args_length))
|
||||||
|
span.SetMeta("out.host", tc.traceParams.host)
|
||||||
|
span.SetMeta("out.port", tc.traceParams.port)
|
||||||
|
span.SetMeta("out.db", tc.traceParams.db)
|
||||||
|
|
||||||
|
err := oldProcess(cmd)
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
span.Finish()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
228
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/go-redis/tracedredis_test.go
generated
vendored
Normal file
228
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/go-redis/tracedredis_test.go
generated
vendored
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
package goredistrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
client.Set("test_key", "test_value", 0)
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
span := spans[0]
|
||||||
|
assert.Equal(span.Service, "my-redis")
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "set test_key test_value: ")
|
||||||
|
assert.Equal(span.GetMeta("redis.args_length"), "3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPipeline(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default db
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
pipeline := client.Pipeline()
|
||||||
|
pipeline.Expire("pipeline_counter", time.Hour)
|
||||||
|
|
||||||
|
// Exec with context test
|
||||||
|
pipeline.ExecWithContext(context.Background())
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
span := spans[0]
|
||||||
|
assert.Equal(span.Service, "my-redis")
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.pipeline_length"), "1")
|
||||||
|
assert.Equal(span.Resource, "expire pipeline_counter 3600: false\n")
|
||||||
|
|
||||||
|
pipeline.Expire("pipeline_counter", time.Hour)
|
||||||
|
pipeline.Expire("pipeline_counter_1", time.Minute)
|
||||||
|
|
||||||
|
// Rewriting Exec
|
||||||
|
pipeline.Exec()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces = testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans = traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
span = spans[0]
|
||||||
|
assert.Equal(span.Service, "my-redis")
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.GetMeta("redis.pipeline_length"), "2")
|
||||||
|
assert.Equal(span.Resource, "expire pipeline_counter 3600: false\nexpire pipeline_counter_1 60: false\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChildSpan(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
// Parent span
|
||||||
|
ctx := context.Background()
|
||||||
|
parent_span := testTracer.NewChildSpanFromContext("parent_span", ctx)
|
||||||
|
ctx = tracer.ContextWithSpan(ctx, parent_span)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
client.SetContext(ctx)
|
||||||
|
|
||||||
|
client.Set("test_key", "test_value", 0)
|
||||||
|
parent_span.Finish()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
|
||||||
|
var child_span, pspan *tracer.Span
|
||||||
|
for _, s := range spans {
|
||||||
|
// order of traces in buffer is not garanteed
|
||||||
|
switch s.Name {
|
||||||
|
case "redis.command":
|
||||||
|
child_span = s
|
||||||
|
case "parent_span":
|
||||||
|
pspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotNil(child_span, "there should be a child redis.command span")
|
||||||
|
assert.NotNil(child_span, "there should be a parent span")
|
||||||
|
|
||||||
|
assert.Equal(child_span.ParentID, pspan.SpanID)
|
||||||
|
assert.Equal(child_span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(child_span.GetMeta("out.port"), "56379")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleCommands(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
client.Set("test_key", "test_value", 0)
|
||||||
|
client.Get("test_key")
|
||||||
|
client.Incr("int_key")
|
||||||
|
client.ClientList()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 4)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
// Checking all commands were recorded
|
||||||
|
var commands [4]string
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
commands[i] = traces[i][0].GetMeta("redis.raw_command")
|
||||||
|
}
|
||||||
|
assert.Contains(commands, "set test_key test_value: ")
|
||||||
|
assert.Contains(commands, "get test_key: ")
|
||||||
|
assert.Contains(commands, "incr int_key: 0")
|
||||||
|
assert.Contains(commands, "client list: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
opts := &redis.Options{
|
||||||
|
Addr: "127.0.0.1:56379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
}
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
client := NewTracedClient(opts, testTracer, "my-redis")
|
||||||
|
err := client.Get("non_existent_key")
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
|
||||||
|
assert.Equal(int32(span.Error), int32(1))
|
||||||
|
assert.Equal(span.GetMeta("error.msg"), err.Err().Error())
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "get non_existent_key: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
27
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gocql/example_test.go
generated
vendored
Normal file
27
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gocql/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package gocqltrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To trace Cassandra commands, use our query wrapper TraceQuery.
|
||||||
|
func Example() {
|
||||||
|
|
||||||
|
// Initialise a Cassandra session as usual, create a query.
|
||||||
|
cluster := gocql.NewCluster("127.0.0.1")
|
||||||
|
session, _ := cluster.CreateSession()
|
||||||
|
query := session.Query("CREATE KEYSPACE if not exists trace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor': 1}")
|
||||||
|
|
||||||
|
// Use context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// Wrap the query to trace it and pass the context for inheritance
|
||||||
|
tracedQuery := TraceQuery("ServiceName", tracer.DefaultTracer, query)
|
||||||
|
tracedQuery.WithContext(ctx)
|
||||||
|
|
||||||
|
// Execute your query as usual
|
||||||
|
tracedQuery.Exec()
|
||||||
|
}
|
146
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gocql/gocqltrace.go
generated
vendored
Normal file
146
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gocql/gocqltrace.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
// Package gocqltrace provides tracing for the Cassandra Gocql client (https://github.com/gocql/gocql)
|
||||||
|
package gocqltrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TracedQuery inherits from gocql.Query, it keeps the tracer and the context.
|
||||||
|
type TracedQuery struct {
|
||||||
|
*gocql.Query
|
||||||
|
p traceParams
|
||||||
|
traceContext context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracedIter inherits from gocql.Iter and contains a span.
|
||||||
|
type TracedIter struct {
|
||||||
|
*gocql.Iter
|
||||||
|
span *tracer.Span
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceParams containes fields and metadata useful for command tracing
|
||||||
|
type traceParams struct {
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
service string
|
||||||
|
keyspace string
|
||||||
|
paginated string
|
||||||
|
consistancy string
|
||||||
|
query string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceQuery wraps a gocql.Query into a TracedQuery
|
||||||
|
func TraceQuery(service string, tracer *tracer.Tracer, q *gocql.Query) *TracedQuery {
|
||||||
|
stringQuery := `"` + strings.SplitN(q.String(), "\"", 3)[1] + `"`
|
||||||
|
stringQuery, err := strconv.Unquote(stringQuery)
|
||||||
|
if err != nil {
|
||||||
|
// An invalid string, so that the trace is not dropped
|
||||||
|
// due to having an empty resource
|
||||||
|
stringQuery = "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
tq := &TracedQuery{q, traceParams{tracer, service, "", "false", strconv.Itoa(int(q.GetConsistency())), stringQuery}, context.Background()}
|
||||||
|
tracer.SetServiceInfo(service, ext.CassandraType, ext.AppTypeDB)
|
||||||
|
return tq
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext rewrites the original function so that ctx can be used for inheritance
|
||||||
|
func (tq *TracedQuery) WithContext(ctx context.Context) *TracedQuery {
|
||||||
|
tq.traceContext = ctx
|
||||||
|
tq.Query.WithContext(ctx)
|
||||||
|
return tq
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageState rewrites the original function so that spans are aware of the change.
|
||||||
|
func (tq *TracedQuery) PageState(state []byte) *TracedQuery {
|
||||||
|
tq.p.paginated = "true"
|
||||||
|
tq.Query = tq.Query.PageState(state)
|
||||||
|
return tq
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChildSpan creates a new span from the traceParams and the context.
|
||||||
|
func (tq *TracedQuery) NewChildSpan(ctx context.Context) *tracer.Span {
|
||||||
|
span := tq.p.tracer.NewChildSpanFromContext(ext.CassandraQuery, ctx)
|
||||||
|
span.Type = ext.CassandraType
|
||||||
|
span.Service = tq.p.service
|
||||||
|
span.Resource = tq.p.query
|
||||||
|
span.SetMeta(ext.CassandraPaginated, tq.p.paginated)
|
||||||
|
span.SetMeta(ext.CassandraKeyspace, tq.p.keyspace)
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec is rewritten so that it passes by our custom Iter
|
||||||
|
func (tq *TracedQuery) Exec() error {
|
||||||
|
return tq.Iter().Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapScan wraps in a span query.MapScan call.
|
||||||
|
func (tq *TracedQuery) MapScan(m map[string]interface{}) error {
|
||||||
|
span := tq.NewChildSpan(tq.traceContext)
|
||||||
|
defer span.Finish()
|
||||||
|
err := tq.Query.MapScan(m)
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan wraps in a span query.Scan call.
|
||||||
|
func (tq *TracedQuery) Scan(dest ...interface{}) error {
|
||||||
|
span := tq.NewChildSpan(tq.traceContext)
|
||||||
|
defer span.Finish()
|
||||||
|
err := tq.Query.Scan(dest...)
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScanCAS wraps in a span query.ScanCAS call.
|
||||||
|
func (tq *TracedQuery) ScanCAS(dest ...interface{}) (applied bool, err error) {
|
||||||
|
span := tq.NewChildSpan(tq.traceContext)
|
||||||
|
defer span.Finish()
|
||||||
|
applied, err = tq.Query.ScanCAS(dest...)
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
return applied, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iter starts a new span at query.Iter call.
|
||||||
|
func (tq *TracedQuery) Iter() *TracedIter {
|
||||||
|
span := tq.NewChildSpan(tq.traceContext)
|
||||||
|
iter := tq.Query.Iter()
|
||||||
|
span.SetMeta(ext.CassandraRowCount, strconv.Itoa(iter.NumRows()))
|
||||||
|
span.SetMeta(ext.CassandraConsistencyLevel, strconv.Itoa(int(tq.GetConsistency())))
|
||||||
|
|
||||||
|
columns := iter.Columns()
|
||||||
|
if len(columns) > 0 {
|
||||||
|
span.SetMeta(ext.CassandraKeyspace, columns[0].Keyspace)
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
tIter := &TracedIter{iter, span}
|
||||||
|
if tIter.Host() != nil {
|
||||||
|
tIter.span.SetMeta(ext.TargetHost, tIter.Iter.Host().HostID())
|
||||||
|
tIter.span.SetMeta(ext.TargetPort, strconv.Itoa(tIter.Iter.Host().Port()))
|
||||||
|
tIter.span.SetMeta(ext.CassandraCluster, tIter.Iter.Host().DataCenter())
|
||||||
|
|
||||||
|
}
|
||||||
|
return tIter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the TracedIter and finish the span created on Iter call.
|
||||||
|
func (tIter *TracedIter) Close() error {
|
||||||
|
err := tIter.Iter.Close()
|
||||||
|
if err != nil {
|
||||||
|
tIter.span.SetError(err)
|
||||||
|
}
|
||||||
|
tIter.span.Finish()
|
||||||
|
return err
|
||||||
|
}
|
132
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gocql/gocqltrace_test.go
generated
vendored
Normal file
132
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gocql/gocqltrace_test.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package gocqltrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain sets up the Keyspace and table if they do not exist
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
cluster := gocql.NewCluster("127.0.0.1:59042")
|
||||||
|
session, _ := cluster.CreateSession()
|
||||||
|
|
||||||
|
// Ensures test keyspace and table person exists.
|
||||||
|
session.Query("CREATE KEYSPACE if not exists trace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor': 1}").Exec()
|
||||||
|
session.Query("CREATE TABLE if not exists trace.person (name text PRIMARY KEY, age int, description text)").Exec()
|
||||||
|
session.Query("INSERT INTO trace.person (name, age, description) VALUES ('Cassandra', 100, 'A cruel mistress')").Exec()
|
||||||
|
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorWrapper(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
cluster := gocql.NewCluster("127.0.0.1:59042")
|
||||||
|
session, _ := cluster.CreateSession()
|
||||||
|
q := session.Query("CREATE KEYSPACE trace WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'datacenter1' : 1 };")
|
||||||
|
err := TraceQuery("ServiceName", testTracer, q).Exec()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
|
||||||
|
assert.Equal(int32(span.Error), int32(1))
|
||||||
|
assert.Equal(span.GetMeta("error.msg"), err.Error())
|
||||||
|
assert.Equal(span.Name, ext.CassandraQuery)
|
||||||
|
assert.Equal(span.Resource, "CREATE KEYSPACE trace WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'datacenter1' : 1 };")
|
||||||
|
assert.Equal(span.Service, "ServiceName")
|
||||||
|
assert.Equal(span.GetMeta(ext.CassandraConsistencyLevel), "4")
|
||||||
|
assert.Equal(span.GetMeta(ext.CassandraPaginated), "false")
|
||||||
|
|
||||||
|
// Not added in case of an error
|
||||||
|
assert.Equal(span.GetMeta(ext.TargetHost), "")
|
||||||
|
assert.Equal(span.GetMeta(ext.TargetPort), "")
|
||||||
|
assert.Equal(span.GetMeta(ext.CassandraCluster), "")
|
||||||
|
assert.Equal(span.GetMeta(ext.CassandraKeyspace), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChildWrapperSpan(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
// Parent span
|
||||||
|
ctx := context.Background()
|
||||||
|
parentSpan := testTracer.NewChildSpanFromContext("parentSpan", ctx)
|
||||||
|
ctx = tracer.ContextWithSpan(ctx, parentSpan)
|
||||||
|
|
||||||
|
cluster := gocql.NewCluster("127.0.0.1:59042")
|
||||||
|
session, _ := cluster.CreateSession()
|
||||||
|
q := session.Query("SELECT * from trace.person")
|
||||||
|
tq := TraceQuery("TestServiceName", testTracer, q)
|
||||||
|
tq.WithContext(ctx).Exec()
|
||||||
|
parentSpan.Finish()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
|
||||||
|
var childSpan, pSpan *tracer.Span
|
||||||
|
if spans[0].ParentID == spans[1].SpanID {
|
||||||
|
childSpan = spans[0]
|
||||||
|
pSpan = spans[1]
|
||||||
|
} else {
|
||||||
|
childSpan = spans[1]
|
||||||
|
pSpan = spans[0]
|
||||||
|
}
|
||||||
|
assert.Equal(pSpan.Name, "parentSpan")
|
||||||
|
assert.Equal(childSpan.ParentID, pSpan.SpanID)
|
||||||
|
assert.Equal(childSpan.Name, ext.CassandraQuery)
|
||||||
|
assert.Equal(childSpan.Resource, "SELECT * from trace.person")
|
||||||
|
assert.Equal(childSpan.GetMeta(ext.CassandraKeyspace), "trace")
|
||||||
|
assert.Equal(childSpan.GetMeta(ext.TargetPort), "59042")
|
||||||
|
assert.Equal(childSpan.GetMeta(ext.TargetHost), "127.0.0.1")
|
||||||
|
assert.Equal(childSpan.GetMeta(ext.CassandraCluster), "datacenter1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
31
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gorilla/muxtrace/example_test.go
generated
vendored
Normal file
31
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gorilla/muxtrace/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package muxtrace_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/gorilla/muxtrace"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handler is a simple handlerFunc that logs some data from the span
|
||||||
|
// that is injected into the requests' context.
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
span := tracer.SpanFromContextDefault(r.Context())
|
||||||
|
fmt.Printf("tracing service:%s resource:%s", span.Service, span.Resource)
|
||||||
|
w.Write([]byte("hello world"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
router := mux.NewRouter()
|
||||||
|
muxTracer := muxtrace.NewMuxTracer("my-web-app", tracer.DefaultTracer)
|
||||||
|
|
||||||
|
// Add traced routes directly.
|
||||||
|
muxTracer.HandleFunc(router, "/users", handler)
|
||||||
|
|
||||||
|
// and subroutes as well.
|
||||||
|
subrouter := router.PathPrefix("/user").Subrouter()
|
||||||
|
muxTracer.HandleFunc(subrouter, "/view", handler)
|
||||||
|
muxTracer.HandleFunc(subrouter, "/create", handler)
|
||||||
|
}
|
127
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gorilla/muxtrace/muxtrace.go
generated
vendored
Normal file
127
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gorilla/muxtrace/muxtrace.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
// Package muxtrace provides tracing functions for the Gorilla Mux framework.
|
||||||
|
package muxtrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MuxTracer is used to trace requests in a mux server.
|
||||||
|
type MuxTracer struct {
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
service string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMuxTracer creates a MuxTracer for the given service and tracer.
|
||||||
|
func NewMuxTracer(service string, t *tracer.Tracer) *MuxTracer {
|
||||||
|
t.SetServiceInfo(service, "gorilla", ext.AppTypeWeb)
|
||||||
|
return &MuxTracer{
|
||||||
|
tracer: t,
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceHandleFunc will return a HandlerFunc that will wrap tracing around the
|
||||||
|
// given handler func.
|
||||||
|
func (m *MuxTracer) TraceHandleFunc(handler http.HandlerFunc) http.HandlerFunc {
|
||||||
|
|
||||||
|
return func(writer http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
// bail our if tracing isn't enabled.
|
||||||
|
if !m.tracer.Enabled() {
|
||||||
|
handler(writer, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// trace the request
|
||||||
|
tracedRequest, span := m.trace(req)
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
// trace the response
|
||||||
|
tracedWriter := newTracedResponseWriter(span, writer)
|
||||||
|
|
||||||
|
// run the request
|
||||||
|
handler(tracedWriter, tracedRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFunc will add a traced version of the given handler to the router.
|
||||||
|
func (m *MuxTracer) HandleFunc(router *mux.Router, pattern string, handler http.HandlerFunc) *mux.Route {
|
||||||
|
return router.HandleFunc(pattern, m.TraceHandleFunc(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
// span will create a span for the given request.
|
||||||
|
func (m *MuxTracer) trace(req *http.Request) (*http.Request, *tracer.Span) {
|
||||||
|
route := mux.CurrentRoute(req)
|
||||||
|
path, err := route.GetPathTemplate()
|
||||||
|
if err != nil {
|
||||||
|
// when route doesn't define a path
|
||||||
|
path = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource := req.Method + " " + path
|
||||||
|
|
||||||
|
span := m.tracer.NewRootSpan("mux.request", m.service, resource)
|
||||||
|
span.Type = ext.HTTPType
|
||||||
|
span.SetMeta(ext.HTTPMethod, req.Method)
|
||||||
|
span.SetMeta(ext.HTTPURL, path)
|
||||||
|
|
||||||
|
// patch the span onto the request context.
|
||||||
|
treq := SetRequestSpan(req, span)
|
||||||
|
return treq, span
|
||||||
|
}
|
||||||
|
|
||||||
|
// tracedResponseWriter is a small wrapper around an http response writer that will
|
||||||
|
// intercept and store the status of a request.
|
||||||
|
type tracedResponseWriter struct {
|
||||||
|
span *tracer.Span
|
||||||
|
w http.ResponseWriter
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTracedResponseWriter(span *tracer.Span, w http.ResponseWriter) *tracedResponseWriter {
|
||||||
|
return &tracedResponseWriter{
|
||||||
|
span: span,
|
||||||
|
w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracedResponseWriter) Header() http.Header {
|
||||||
|
return t.w.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracedResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
if t.status == 0 {
|
||||||
|
t.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
return t.w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracedResponseWriter) WriteHeader(status int) {
|
||||||
|
t.w.WriteHeader(status)
|
||||||
|
t.status = status
|
||||||
|
t.span.SetMeta(ext.HTTPCode, strconv.Itoa(status))
|
||||||
|
if status >= 500 && status < 600 {
|
||||||
|
t.span.Error = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequestSpan sets the span on the request's context.
|
||||||
|
func SetRequestSpan(r *http.Request, span *tracer.Span) *http.Request {
|
||||||
|
if r == nil || span == nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := tracer.ContextWithSpan(r.Context(), span)
|
||||||
|
return r.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestSpan will return the span associated with the given request. It
|
||||||
|
// will return nil/false if it doesn't exist.
|
||||||
|
func GetRequestSpan(r *http.Request) (*tracer.Span, bool) {
|
||||||
|
span, ok := tracer.SpanFromContext(r.Context())
|
||||||
|
return span, ok
|
||||||
|
}
|
206
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gorilla/muxtrace/muxtrace_test.go
generated
vendored
Normal file
206
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/gorilla/muxtrace/muxtrace_test.go
generated
vendored
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
package muxtrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMuxTracerDisabled(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
testTracer, testTransport, muxTracer := getTestTracer("disabled-service")
|
||||||
|
router := mux.NewRouter()
|
||||||
|
muxTracer.HandleFunc(router, "/disabled", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte("disabled!"))
|
||||||
|
assert.Nil(err)
|
||||||
|
// Ensure we have no tracing context.
|
||||||
|
span, ok := tracer.SpanFromContext(r.Context())
|
||||||
|
assert.Nil(span)
|
||||||
|
assert.False(ok)
|
||||||
|
})
|
||||||
|
testTracer.SetEnabled(false) // the key line in this test.
|
||||||
|
|
||||||
|
// make the request
|
||||||
|
req := httptest.NewRequest("GET", "/disabled", nil)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(writer, req)
|
||||||
|
assert.Equal(writer.Code, 200)
|
||||||
|
assert.Equal(writer.Body.String(), "disabled!")
|
||||||
|
|
||||||
|
// assert nothing was traced.
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxTracerSubrequest(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// Send and verify a 200 request
|
||||||
|
for _, url := range []string{"/sub/child1", "/sub/child2"} {
|
||||||
|
tracer, transport, router := setup(t)
|
||||||
|
req := httptest.NewRequest("GET", url, nil)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(writer, req)
|
||||||
|
assert.Equal(writer.Code, 200)
|
||||||
|
assert.Equal(writer.Body.String(), "200!")
|
||||||
|
|
||||||
|
// ensure properly traced
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Name, "mux.request")
|
||||||
|
assert.Equal(s.Service, "my-service")
|
||||||
|
assert.Equal(s.Resource, "GET "+url)
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "200")
|
||||||
|
assert.Equal(s.GetMeta("http.method"), "GET")
|
||||||
|
assert.Equal(s.GetMeta("http.url"), url)
|
||||||
|
assert.Equal(s.Error, int32(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxTracer200(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// setup
|
||||||
|
tracer, transport, router := setup(t)
|
||||||
|
|
||||||
|
// Send and verify a 200 request
|
||||||
|
url := "/200"
|
||||||
|
req := httptest.NewRequest("GET", url, nil)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(writer, req)
|
||||||
|
assert.Equal(writer.Code, 200)
|
||||||
|
assert.Equal(writer.Body.String(), "200!")
|
||||||
|
|
||||||
|
// ensure properly traced
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Name, "mux.request")
|
||||||
|
assert.Equal(s.Service, "my-service")
|
||||||
|
assert.Equal(s.Resource, "GET "+url)
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "200")
|
||||||
|
assert.Equal(s.GetMeta("http.method"), "GET")
|
||||||
|
assert.Equal(s.GetMeta("http.url"), url)
|
||||||
|
assert.Equal(s.Error, int32(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxTracer500(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// setup
|
||||||
|
tracer, transport, router := setup(t)
|
||||||
|
|
||||||
|
// SEnd and verify a 200 request
|
||||||
|
url := "/500"
|
||||||
|
req := httptest.NewRequest("GET", url, nil)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(writer, req)
|
||||||
|
assert.Equal(writer.Code, 500)
|
||||||
|
assert.Equal(writer.Body.String(), "500!\n")
|
||||||
|
|
||||||
|
// ensure properly traced
|
||||||
|
tracer.ForceFlush()
|
||||||
|
traces := transport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
|
||||||
|
s := spans[0]
|
||||||
|
assert.Equal(s.Name, "mux.request")
|
||||||
|
assert.Equal(s.Service, "my-service")
|
||||||
|
assert.Equal(s.Resource, "GET "+url)
|
||||||
|
assert.Equal(s.GetMeta("http.status_code"), "500")
|
||||||
|
assert.Equal(s.GetMeta("http.method"), "GET")
|
||||||
|
assert.Equal(s.GetMeta("http.url"), url)
|
||||||
|
assert.Equal(s.Error, int32(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// test handlers
|
||||||
|
|
||||||
|
func handler200(t *testing.T) http.HandlerFunc {
|
||||||
|
assert := assert.New(t)
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte("200!"))
|
||||||
|
assert.Nil(err)
|
||||||
|
span := tracer.SpanFromContextDefault(r.Context())
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Duration, int64(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler500(t *testing.T) http.HandlerFunc {
|
||||||
|
assert := assert.New(t)
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "500!", http.StatusInternalServerError)
|
||||||
|
span := tracer.SpanFromContextDefault(r.Context())
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Duration, int64(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(t *testing.T) (*tracer.Tracer, *dummyTransport, *mux.Router) {
|
||||||
|
tracer, transport, mt := getTestTracer("my-service")
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
h200 := handler200(t)
|
||||||
|
h500 := handler500(t)
|
||||||
|
|
||||||
|
// Ensure we can use HandleFunc and it returns a route
|
||||||
|
mt.HandleFunc(r, "/200", h200).Methods("Get")
|
||||||
|
// And we can allso handle a bare func
|
||||||
|
r.HandleFunc("/500", mt.TraceHandleFunc(h500))
|
||||||
|
|
||||||
|
// do a subrouter (one in each way)
|
||||||
|
sub := r.PathPrefix("/sub").Subrouter()
|
||||||
|
sub.HandleFunc("/child1", mt.TraceHandleFunc(h200))
|
||||||
|
mt.HandleFunc(sub, "/child2", h200)
|
||||||
|
|
||||||
|
return tracer, transport, r
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer(service string) (*tracer.Tracer, *dummyTransport, *MuxTracer) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
muxTracer := NewMuxTracer(service, tracer)
|
||||||
|
return tracer, transport, muxTracer
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
59
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/redigo/example_test.go
generated
vendored
Normal file
59
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/redigo/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package redigotrace_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
redigotrace "github.com/DataDog/dd-trace-go/tracer/contrib/redigo"
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To start tracing Redis commands, use the TracedDial function to create a connection,
|
||||||
|
// passing in a service name of choice.
|
||||||
|
func Example() {
|
||||||
|
c, _ := redigotrace.TracedDial("my-redis-backend", tracer.DefaultTracer, "tcp", "127.0.0.1:6379")
|
||||||
|
|
||||||
|
// Emit spans per command by using your Redis connection as usual
|
||||||
|
c.Do("SET", "vehicle", "truck")
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// When passed a context as the final argument, c.Do will emit a span inheriting from 'parent.request'
|
||||||
|
c.Do("SET", "food", "cheese", ctx)
|
||||||
|
root.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleTracedConn() {
|
||||||
|
c, _ := redigotrace.TracedDial("my-redis-backend", tracer.DefaultTracer, "tcp", "127.0.0.1:6379")
|
||||||
|
|
||||||
|
// Emit spans per command by using your Redis connection as usual
|
||||||
|
c.Do("SET", "vehicle", "truck")
|
||||||
|
|
||||||
|
// Use a context to pass information down the call chain
|
||||||
|
root := tracer.NewRootSpan("parent.request", "web", "/home")
|
||||||
|
ctx := root.Context(context.Background())
|
||||||
|
|
||||||
|
// When passed a context as the final argument, c.Do will emit a span inheriting from 'parent.request'
|
||||||
|
c.Do("SET", "food", "cheese", ctx)
|
||||||
|
root.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternatively, provide a redis URL to the TracedDialURL function
|
||||||
|
func Example_dialURL() {
|
||||||
|
c, _ := redigotrace.TracedDialURL("my-redis-backend", tracer.DefaultTracer, "redis://127.0.0.1:6379")
|
||||||
|
c.Do("SET", "vehicle", "truck")
|
||||||
|
}
|
||||||
|
|
||||||
|
// When using a redigo Pool, set your Dial function to return a traced connection
|
||||||
|
func Example_pool() {
|
||||||
|
pool := &redis.Pool{
|
||||||
|
Dial: func() (redis.Conn, error) {
|
||||||
|
return redigotrace.TracedDial("my-redis-backend", tracer.DefaultTracer, "tcp", "127.0.0.1:6379")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c := pool.Get()
|
||||||
|
|
||||||
|
c.Do("SET", " whiskey", " glass")
|
||||||
|
}
|
131
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/redigo/redigotrace.go
generated
vendored
Normal file
131
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/redigo/redigotrace.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Package redigotrace provides tracing for the Redigo Redis client (https://github.com/garyburd/redigo)
|
||||||
|
package redigotrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/ext"
|
||||||
|
redis "github.com/garyburd/redigo/redis"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TracedConn is an implementation of the redis.Conn interface that supports tracing
|
||||||
|
type TracedConn struct {
|
||||||
|
redis.Conn
|
||||||
|
p traceParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceParams contains fields and metadata useful for command tracing
|
||||||
|
type traceParams struct {
|
||||||
|
tracer *tracer.Tracer
|
||||||
|
service string
|
||||||
|
network string
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracedDial takes a Conn returned by redis.Dial and configures it to emit spans with the given service name
|
||||||
|
func TracedDial(service string, tracer *tracer.Tracer, network, address string, options ...redis.DialOption) (redis.Conn, error) {
|
||||||
|
c, err := redis.Dial(network, address, options...)
|
||||||
|
addr := strings.Split(address, ":")
|
||||||
|
var host, port string
|
||||||
|
if len(addr) == 2 && addr[1] != "" {
|
||||||
|
port = addr[1]
|
||||||
|
} else {
|
||||||
|
port = "6379"
|
||||||
|
}
|
||||||
|
host = addr[0]
|
||||||
|
tracer.SetServiceInfo(service, "redis", ext.AppTypeDB)
|
||||||
|
tc := TracedConn{c, traceParams{tracer, service, network, host, port}}
|
||||||
|
return tc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracedDialURL takes a Conn returned by redis.DialURL and configures it to emit spans with the given service name
|
||||||
|
func TracedDialURL(service string, tracer *tracer.Tracer, rawurl string, options ...redis.DialOption) (redis.Conn, error) {
|
||||||
|
u, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return TracedConn{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting host and port, usind code from https://github.com/garyburd/redigo/blob/master/redis/conn.go#L226
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
host = u.Host
|
||||||
|
port = "6379"
|
||||||
|
}
|
||||||
|
if host == "" {
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
// Set in redis.DialUrl source code
|
||||||
|
network := "tcp"
|
||||||
|
c, err := redis.DialURL(rawurl, options...)
|
||||||
|
tc := TracedConn{c, traceParams{tracer, service, network, host, port}}
|
||||||
|
return tc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChildSpan creates a span inheriting from the given context. It adds to the span useful metadata about the traced Redis connection
|
||||||
|
func (tc TracedConn) NewChildSpan(ctx context.Context) *tracer.Span {
|
||||||
|
span := tc.p.tracer.NewChildSpanFromContext("redis.command", ctx)
|
||||||
|
span.Service = tc.p.service
|
||||||
|
span.SetMeta("out.network", tc.p.network)
|
||||||
|
span.SetMeta("out.port", tc.p.port)
|
||||||
|
span.SetMeta("out.host", tc.p.host)
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do wraps redis.Conn.Do. It sends a command to the Redis server and returns the received reply.
|
||||||
|
// In the process it emits a span containing key information about the command sent.
|
||||||
|
// When passed a context.Context as the final argument, Do will ensure that any span created
|
||||||
|
// inherits from this context. The rest of the arguments are passed through to the Redis server unchanged
|
||||||
|
func (tc TracedConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||||
|
var ctx context.Context
|
||||||
|
var ok bool
|
||||||
|
if len(args) > 0 {
|
||||||
|
ctx, ok = args[len(args)-1].(context.Context)
|
||||||
|
if ok {
|
||||||
|
args = args[:len(args)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span := tc.NewChildSpan(ctx)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
span.SetError(err)
|
||||||
|
}
|
||||||
|
span.Finish()
|
||||||
|
}()
|
||||||
|
|
||||||
|
span.SetMeta("redis.args_length", strconv.Itoa(len(args)))
|
||||||
|
|
||||||
|
if len(commandName) > 0 {
|
||||||
|
span.Resource = commandName
|
||||||
|
} else {
|
||||||
|
// When the command argument to the Do method is "", then the Do method will flush the output buffer
|
||||||
|
// See https://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining
|
||||||
|
span.Resource = "redigo.Conn.Flush"
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.WriteString(commandName)
|
||||||
|
for _, arg := range args {
|
||||||
|
b.WriteString(" ")
|
||||||
|
switch arg := arg.(type) {
|
||||||
|
case string:
|
||||||
|
b.WriteString(arg)
|
||||||
|
case int:
|
||||||
|
b.WriteString(strconv.Itoa(arg))
|
||||||
|
case int32:
|
||||||
|
b.WriteString(strconv.FormatInt(int64(arg), 10))
|
||||||
|
case int64:
|
||||||
|
b.WriteString(strconv.FormatInt(arg, 10))
|
||||||
|
case fmt.Stringer:
|
||||||
|
b.WriteString(arg.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span.SetMeta("redis.raw_command", b.String())
|
||||||
|
return tc.Conn.Do(commandName, args...)
|
||||||
|
}
|
214
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/redigo/redigotrace_test.go
generated
vendored
Normal file
214
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/redigo/redigotrace_test.go
generated
vendored
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
package redigotrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
c, _ := TracedDial("my-service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
c.Do("SET", 1, "truck")
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Resource, "SET")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "SET 1 truck")
|
||||||
|
assert.Equal(span.GetMeta("redis.args_length"), "2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandError(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
c, _ := TracedDial("my-service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
_, err := c.Do("NOT_A_COMMAND", context.Background())
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
|
||||||
|
assert.Equal(int32(span.Error), int32(1))
|
||||||
|
assert.Equal(span.GetMeta("error.msg"), err.Error())
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Resource, "NOT_A_COMMAND")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "NOT_A_COMMAND")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectionError(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, _ := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
_, err := TracedDial("redis-service", testTracer, "tcp", "127.0.0.1:1000")
|
||||||
|
|
||||||
|
assert.Contains(err.Error(), "dial tcp 127.0.0.1:1000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInheritance(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
// Parent span
|
||||||
|
ctx := context.Background()
|
||||||
|
parent_span := testTracer.NewChildSpanFromContext("parent_span", ctx)
|
||||||
|
ctx = tracer.ContextWithSpan(ctx, parent_span)
|
||||||
|
client, _ := TracedDial("my_service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
client.Do("SET", "water", "bottle", ctx)
|
||||||
|
parent_span.Finish()
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 2)
|
||||||
|
|
||||||
|
var child_span, pspan *tracer.Span
|
||||||
|
for _, s := range spans {
|
||||||
|
// order of traces in buffer is not garanteed
|
||||||
|
switch s.Name {
|
||||||
|
case "redis.command":
|
||||||
|
child_span = s
|
||||||
|
case "parent_span":
|
||||||
|
pspan = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotNil(child_span, "there should be a child redis.command span")
|
||||||
|
assert.NotNil(child_span, "there should be a parent span")
|
||||||
|
|
||||||
|
assert.Equal(child_span.ParentID, pspan.SpanID)
|
||||||
|
assert.Equal(child_span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(child_span.GetMeta("out.port"), "56379")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandsToSring(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
stringify_test := TestStruct{Cpython: 57, Cgo: 8}
|
||||||
|
c, _ := TracedDial("my-service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
c.Do("SADD", "testSet", "a", int(0), int32(1), int64(2), stringify_test, context.Background())
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
|
||||||
|
assert.Equal(span.Name, "redis.command")
|
||||||
|
assert.Equal(span.Service, "my-service")
|
||||||
|
assert.Equal(span.Resource, "SADD")
|
||||||
|
assert.Equal(span.GetMeta("out.host"), "127.0.0.1")
|
||||||
|
assert.Equal(span.GetMeta("out.port"), "56379")
|
||||||
|
assert.Equal(span.GetMeta("redis.raw_command"), "SADD testSet a 0 1 2 [57, 8]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPool(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
|
||||||
|
pool := &redis.Pool{
|
||||||
|
MaxIdle: 2,
|
||||||
|
MaxActive: 3,
|
||||||
|
IdleTimeout: 23,
|
||||||
|
Wait: true,
|
||||||
|
Dial: func() (redis.Conn, error) {
|
||||||
|
return TracedDial("my-service", testTracer, "tcp", "127.0.0.1:56379")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := pool.Get()
|
||||||
|
pc.Do("SET", " whiskey", " glass", context.Background())
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
spans := traces[0]
|
||||||
|
assert.Len(spans, 1)
|
||||||
|
span := spans[0]
|
||||||
|
assert.Equal(span.GetMeta("out.network"), "tcp")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTracingDialUrl(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
testTracer, testTransport := getTestTracer()
|
||||||
|
testTracer.SetDebugLogging(debug)
|
||||||
|
url := "redis://127.0.0.1:56379"
|
||||||
|
client, _ := TracedDialURL("redis-service", testTracer, url)
|
||||||
|
client.Do("SET", "ONE", " TWO", context.Background())
|
||||||
|
|
||||||
|
testTracer.ForceFlush()
|
||||||
|
traces := testTransport.Traces()
|
||||||
|
assert.Len(traces, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStruct implements String interface
|
||||||
|
type TestStruct struct {
|
||||||
|
Cpython int
|
||||||
|
Cgo int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts TestStruct) String() string {
|
||||||
|
return fmt.Sprintf("[%d, %d]", ts.Cpython, ts.Cgo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTestTracer returns a Tracer with a DummyTransport
|
||||||
|
func getTestTracer() (*tracer.Tracer, *dummyTransport) {
|
||||||
|
transport := &dummyTransport{}
|
||||||
|
tracer := tracer.NewTracerTransport(transport)
|
||||||
|
return tracer, transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummyTransport is a transport that just buffers spans and encoding
|
||||||
|
type dummyTransport struct {
|
||||||
|
traces [][]*tracer.Span
|
||||||
|
services map[string]tracer.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendTraces(traces [][]*tracer.Span) (*http.Response, error) {
|
||||||
|
t.traces = append(t.traces, traces...)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) SendServices(services map[string]tracer.Service) (*http.Response, error) {
|
||||||
|
t.services = services
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *dummyTransport) Traces() [][]*tracer.Span {
|
||||||
|
traces := t.traces
|
||||||
|
t.traces = nil
|
||||||
|
return traces
|
||||||
|
}
|
||||||
|
func (t *dummyTransport) SetHeader(key, value string) {}
|
169
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/example_test.go
generated
vendored
Normal file
169
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
package sqltraced_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced"
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// To trace the sql calls, you just need to open your sql.DB with OpenTraced.
|
||||||
|
// All calls through this sql.DB object will then be traced.
|
||||||
|
func Example() {
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sql.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
db, err := sqltraced.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All calls through the database/sql API will then be traced.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you want to link your db calls with existing traces, you need to use
|
||||||
|
// the context version of the database/sql API.
|
||||||
|
// Just make sure you are passing the parent span within the context.
|
||||||
|
func Example_context() {
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sql.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
db, err := sqltraced.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create a parent span and put it within the context.
|
||||||
|
span := tracer.NewRootSpan("postgres.parent", "web-backend", "query-parent")
|
||||||
|
ctx := tracer.ContextWithSpan(context.Background(), span)
|
||||||
|
|
||||||
|
// We need to use the context version of the database/sql API
|
||||||
|
// in order to link this call with the parent span.
|
||||||
|
db.PingContext(ctx)
|
||||||
|
rows, _ := db.QueryContext(ctx, "SELECT * FROM city LIMIT 5")
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
stmt, _ := db.PrepareContext(ctx, "INSERT INTO city(name) VALUES($1)")
|
||||||
|
stmt.Exec("New York")
|
||||||
|
stmt, _ = db.PrepareContext(ctx, "SELECT name FROM city LIMIT $1")
|
||||||
|
rows, _ = stmt.Query(1)
|
||||||
|
rows.Close()
|
||||||
|
stmt.Close()
|
||||||
|
|
||||||
|
tx, _ := db.BeginTx(ctx, nil)
|
||||||
|
tx.ExecContext(ctx, "INSERT INTO city(name) VALUES('New York')")
|
||||||
|
rows, _ = tx.QueryContext(ctx, "SELECT * FROM city LIMIT 5")
|
||||||
|
rows.Close()
|
||||||
|
stmt, _ = tx.PrepareContext(ctx, "SELECT name FROM city LIMIT $1")
|
||||||
|
rows, _ = stmt.Query(1)
|
||||||
|
rows.Close()
|
||||||
|
stmt.Close()
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
|
// Calling span.Finish() will send the span into the tracer's buffer
|
||||||
|
// and then being processed.
|
||||||
|
span.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can trace all drivers implementing the database/sql/driver interface.
|
||||||
|
// For example, you can trace the go-sql-driver/mysql with the following code.
|
||||||
|
func Example_mySQL() {
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sql.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
db, err := sqltraced.OpenTraced(&mysql.MySQLDriver{}, "user:password@/dbname", "web-backend")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All calls through the database/sql API will then be traced.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenTraced will first register a traced version of the driver and then will return the sql.DB object
|
||||||
|
// that holds the connection with the database.
|
||||||
|
func ExampleOpenTraced() {
|
||||||
|
// The first argument is a reference to the driver to trace.
|
||||||
|
// The second argument is the dataSourceName.
|
||||||
|
// The third argument is used to specify the name of the service under which traces will appear in the Datadog app.
|
||||||
|
// The last argument allows you to specify a custom tracer to use for tracing.
|
||||||
|
db, err := sqltraced.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the database/sql API as usual and see traces appear in the Datadog app.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can use a custom tracer by passing it through the optional last argument of OpenTraced.
|
||||||
|
func ExampleOpenTraced_tracer() {
|
||||||
|
// Create and customize a new tracer that will forward 50% of generated traces to the agent.
|
||||||
|
// (useful to manage resource usage in high-throughput environments)
|
||||||
|
trc := tracer.NewTracer()
|
||||||
|
trc.SetSampleRate(0.5)
|
||||||
|
|
||||||
|
// Pass your custom tracer through the last argument of OpenTraced to trace your db calls with it.
|
||||||
|
db, err := sqltraced.OpenTraced(&pq.Driver{}, "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend", trc)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the database/sql API as usual and see traces appear in the Datadog app.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you need more granularity, you can register the traced driver seperately from the Open call.
|
||||||
|
func ExampleRegister() {
|
||||||
|
// Register a traced version of your driver.
|
||||||
|
sqltraced.Register("postgres", &pq.Driver{})
|
||||||
|
|
||||||
|
// Returns a sql.DB object that holds the traced connection to the database.
|
||||||
|
// Note: the sql.DB object returned by sql.Open will not be traced so make sure to use sqltraced.Open.
|
||||||
|
db, _ := sqltraced.Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Use the database/sql API as usual and see traces appear in the Datadog app.
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can use a custom tracer by passing it through the optional last argument of Register.
|
||||||
|
func ExampleRegister_tracer() {
|
||||||
|
// Create and customize a new tracer that will forward 50% of generated traces to the agent.
|
||||||
|
// (useful to manage resource usage in high-throughput environments)
|
||||||
|
trc := tracer.NewTracer()
|
||||||
|
trc.SetSampleRate(0.5)
|
||||||
|
|
||||||
|
// Register a traced version of your driver and specify to use the previous tracer
|
||||||
|
// to send the traces to the agent.
|
||||||
|
sqltraced.Register("postgres", &pq.Driver{}, trc)
|
||||||
|
|
||||||
|
// Returns a sql.DB object that holds the traced connection to the database.
|
||||||
|
// Note: the sql.DB object returned by sql.Open will not be traced so make sure to use sqltraced.Open.
|
||||||
|
db, _ := sqltraced.Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable", "web-backend")
|
||||||
|
defer db.Close()
|
||||||
|
}
|
41
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/mysql_test.go
generated
vendored
Normal file
41
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/mysql_test.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package sqltraced
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/sqltest"
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/tracertest"
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMySQL(t *testing.T) {
|
||||||
|
trc, transport := tracertest.GetTestTracer()
|
||||||
|
db, err := OpenTraced(&mysql.MySQLDriver{}, "test:test@tcp(127.0.0.1:53306)/test", "mysql-test", trc)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
testDB := &sqltest.DB{
|
||||||
|
DB: db,
|
||||||
|
Tracer: trc,
|
||||||
|
Transport: transport,
|
||||||
|
DriverName: "mysql",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSpan := &tracer.Span{
|
||||||
|
Name: "mysql.query",
|
||||||
|
Service: "mysql-test",
|
||||||
|
Type: "sql",
|
||||||
|
}
|
||||||
|
expectedSpan.Meta = map[string]string{
|
||||||
|
"db.user": "test",
|
||||||
|
"out.host": "127.0.0.1",
|
||||||
|
"out.port": "53306",
|
||||||
|
"db.name": "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
sqltest.AllSQLTests(t, testDB, expectedSpan)
|
||||||
|
}
|
42
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parse.go
generated
vendored
Normal file
42
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parse.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package sqltraced
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parsedsn"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseDSN returns all information passed through the DSN:
|
||||||
|
func parseDSN(driverName, dsn string) (meta map[string]string, err error) {
|
||||||
|
switch driverName {
|
||||||
|
case "mysql":
|
||||||
|
meta, err = parsedsn.MySQL(dsn)
|
||||||
|
case "postgres":
|
||||||
|
meta, err = parsedsn.Postgres(dsn)
|
||||||
|
}
|
||||||
|
meta = normalize(meta)
|
||||||
|
return meta, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalize(meta map[string]string) map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for k, v := range meta {
|
||||||
|
if nk, ok := normalizeKey(k); ok {
|
||||||
|
m[nk] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeKey(k string) (string, bool) {
|
||||||
|
switch k {
|
||||||
|
case "user":
|
||||||
|
return "db.user", true
|
||||||
|
case "application_name":
|
||||||
|
return "db.application", true
|
||||||
|
case "dbname":
|
||||||
|
return "db.name", true
|
||||||
|
case "host", "port":
|
||||||
|
return "out." + k, true
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
44
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parse_test.go
generated
vendored
Normal file
44
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parse_test.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package sqltraced
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseDSN(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
expected := map[string]string{
|
||||||
|
"db.user": "bob",
|
||||||
|
"out.host": "1.2.3.4",
|
||||||
|
"out.port": "5432",
|
||||||
|
"db.name": "mydb",
|
||||||
|
}
|
||||||
|
m, err := parseDSN("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
|
||||||
|
expected = map[string]string{
|
||||||
|
"db.user": "bob",
|
||||||
|
"out.host": "1.2.3.4",
|
||||||
|
"out.port": "5432",
|
||||||
|
"db.name": "mydb",
|
||||||
|
}
|
||||||
|
m, err = parseDSN("mysql", "bob:secret@tcp(1.2.3.4:5432)/mydb")
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
|
||||||
|
expected = map[string]string{
|
||||||
|
"out.port": "5433",
|
||||||
|
"out.host": "master-db-master-active.postgres.service.consul",
|
||||||
|
"db.name": "dogdatastaging",
|
||||||
|
"db.application": "trace-api",
|
||||||
|
"db.user": "dog",
|
||||||
|
}
|
||||||
|
dsn := "connect_timeout=0 binary_parameters=no password=zMWmQz26GORmgVVKEbEl dbname=dogdatastaging application_name=trace-api port=5433 sslmode=disable host=master-db-master-active.postgres.service.consul user=dog"
|
||||||
|
m, err = parseDSN("postgres", dsn)
|
||||||
|
assert.Equal(nil, err)
|
||||||
|
assert.True(reflect.DeepEqual(expected, m))
|
||||||
|
}
|
25
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parsedsn/mysql/collations.go
generated
vendored
Normal file
25
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parsedsn/mysql/collations.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
const defaultCollation = "utf8_general_ci"
|
||||||
|
|
||||||
|
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||||
|
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
||||||
|
var unsafeCollations = map[string]bool{
|
||||||
|
"big5_chinese_ci": true,
|
||||||
|
"sjis_japanese_ci": true,
|
||||||
|
"gbk_chinese_ci": true,
|
||||||
|
"big5_bin": true,
|
||||||
|
"gb2312_bin": true,
|
||||||
|
"gbk_bin": true,
|
||||||
|
"sjis_bin": true,
|
||||||
|
"cp932_japanese_ci": true,
|
||||||
|
"cp932_bin": true,
|
||||||
|
}
|
148
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parsedsn/mysql/dsn.go
generated
vendored
Normal file
148
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parsedsn/mysql/dsn.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?")
|
||||||
|
errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)")
|
||||||
|
errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name")
|
||||||
|
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a configuration parsed from a DSN string
|
||||||
|
type Config struct {
|
||||||
|
User string // Username
|
||||||
|
Passwd string // Password (requires User)
|
||||||
|
Net string // Network type
|
||||||
|
Addr string // Network address (requires Net)
|
||||||
|
DBName string // Database name
|
||||||
|
Params map[string]string // Connection parameters
|
||||||
|
Collation string // Connection collation
|
||||||
|
Loc *time.Location // Location for time.Time values
|
||||||
|
MaxAllowedPacket int // Max packet size allowed
|
||||||
|
TLSConfig string // TLS configuration name
|
||||||
|
tls *tls.Config // TLS configuration
|
||||||
|
Timeout time.Duration // Dial timeout
|
||||||
|
ReadTimeout time.Duration // I/O read timeout
|
||||||
|
WriteTimeout time.Duration // I/O write timeout
|
||||||
|
|
||||||
|
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
|
||||||
|
AllowCleartextPasswords bool // Allows the cleartext client side plugin
|
||||||
|
AllowNativePasswords bool // Allows the native password authentication method
|
||||||
|
AllowOldPasswords bool // Allows the old insecure password method
|
||||||
|
ClientFoundRows bool // Return number of matching rows instead of rows changed
|
||||||
|
ColumnsWithAlias bool // Prepend table alias to column names
|
||||||
|
InterpolateParams bool // Interpolate placeholders into query string
|
||||||
|
MultiStatements bool // Allow multiple statements in one query
|
||||||
|
ParseTime bool // Parse time values to time.Time
|
||||||
|
Strict bool // Return warnings as errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDSN parses the DSN string to a Config
|
||||||
|
func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
|
// New config with some default values
|
||||||
|
cfg = &Config{
|
||||||
|
Loc: time.UTC,
|
||||||
|
Collation: defaultCollation,
|
||||||
|
}
|
||||||
|
|
||||||
|
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||||
|
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||||
|
foundSlash := false
|
||||||
|
for i := len(dsn) - 1; i >= 0; i-- {
|
||||||
|
if dsn[i] == '/' {
|
||||||
|
foundSlash = true
|
||||||
|
var j, k int
|
||||||
|
|
||||||
|
// left part is empty if i <= 0
|
||||||
|
if i > 0 {
|
||||||
|
// [username[:password]@][protocol[(address)]]
|
||||||
|
// Find the last '@' in dsn[:i]
|
||||||
|
for j = i; j >= 0; j-- {
|
||||||
|
if dsn[j] == '@' {
|
||||||
|
// username[:password]
|
||||||
|
// Find the first ':' in dsn[:j]
|
||||||
|
for k = 0; k < j; k++ {
|
||||||
|
if dsn[k] == ':' {
|
||||||
|
cfg.Passwd = dsn[k+1 : j]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.User = dsn[:k]
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [protocol[(address)]]
|
||||||
|
// Find the first '(' in dsn[j+1:i]
|
||||||
|
for k = j + 1; k < i; k++ {
|
||||||
|
if dsn[k] == '(' {
|
||||||
|
// dsn[i-1] must be == ')' if an address is specified
|
||||||
|
if dsn[i-1] != ')' {
|
||||||
|
if strings.ContainsRune(dsn[k+1:i], ')') {
|
||||||
|
return nil, errInvalidDSNUnescaped
|
||||||
|
}
|
||||||
|
return nil, errInvalidDSNAddr
|
||||||
|
}
|
||||||
|
cfg.Addr = dsn[k+1 : i-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.Net = dsn[j+1 : k]
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbname[?param1=value1&...¶mN=valueN]
|
||||||
|
// Find the first '?' in dsn[i+1:]
|
||||||
|
for j = i + 1; j < len(dsn); j++ {
|
||||||
|
if dsn[j] == '?' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.DBName = dsn[i+1 : j]
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundSlash && len(dsn) > 0 {
|
||||||
|
return nil, errInvalidDSNNoSlash
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
||||||
|
return nil, errInvalidDSNUnsafeCollation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default network if empty
|
||||||
|
if cfg.Net == "" {
|
||||||
|
cfg.Net = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default address if empty
|
||||||
|
if cfg.Addr == "" {
|
||||||
|
switch cfg.Net {
|
||||||
|
case "tcp":
|
||||||
|
cfg.Addr = "127.0.0.1:3306"
|
||||||
|
case "unix":
|
||||||
|
cfg.Addr = "/tmp/mysql.sock"
|
||||||
|
default:
|
||||||
|
return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
3
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parsedsn/mysql/mysql.go
generated
vendored
Normal file
3
vendor/github.com/DataDog/dd-trace-go/tracer/contrib/sqltraced/parsedsn/mysql/mysql.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Package mysql is the minimal fork of go-sql-driver/mysql so we can use their code
|
||||||
|
// to parse the mysql DSNs
|
||||||
|
package mysql
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue