Skip to content

Commit

Permalink
contrib/gomodule/redigo: copied from contrib/garyburd/redigo (#422)
Browse files Browse the repository at this point in the history
github.com/garyburd/redigo has been archived and moved to
github.com/gomodule/redigo .

Fixes #421
  • Loading branch information
Hendra-Huang authored and gbbr committed Apr 2, 2019
1 parent 2555c24 commit 8c8aefe
Show file tree
Hide file tree
Showing 4 changed files with 505 additions and 0 deletions.
79 changes: 79 additions & 0 deletions contrib/gomodule/redigo/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package redigo_test

import (
"context"
"log"
"time"

"github.com/gomodule/redigo/redis"
redigotrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gomodule/redigo"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

// To start tracing Redis commands, use the TracedDial function to create a connection,
// passing in a service name of choice.
func Example() {
c, err := redigotrace.Dial("tcp", "127.0.0.1:6379")
if err != nil {
log.Fatal(err)
}

// 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, ctx := tracer.StartSpanFromContext(context.Background(), "parent.request",
tracer.ServiceName("web"),
tracer.ResourceName("/home"),
)

// 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 Example_tracedConn() {
c, err := redigotrace.Dial("tcp", "127.0.0.1:6379",
redigotrace.WithServiceName("my-redis-backend"),
redis.DialKeepAlive(time.Minute),
)
if err != nil {
log.Fatal(err)
}

// 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, ctx := tracer.StartSpanFromContext(context.Background(), "parent.request",
tracer.ServiceName("web"),
tracer.ResourceName("/home"),
)

// 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, err := redigotrace.DialURL("redis://127.0.0.1:6379")
if err != nil {
log.Fatal(err)
}
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.Dial("tcp", "127.0.0.1:6379",
redigotrace.WithServiceName("my-redis-backend"),
)
},
}

c := pool.Get()
c.Do("SET", " whiskey", " glass")
}
37 changes: 37 additions & 0 deletions contrib/gomodule/redigo/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package redigo // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/gomodule/redigo"

type dialConfig struct {
serviceName string
analyticsRate float64
}

// DialOption represents an option that can be passed to Dial.
type DialOption func(*dialConfig)

func defaults(cfg *dialConfig) {
cfg.serviceName = "redis.conn"
// cfg.analyticsRate = globalconfig.AnalyticsRate()
}

// WithServiceName sets the given service name for the dialled connection.
func WithServiceName(name string) DialOption {
return func(cfg *dialConfig) {
cfg.serviceName = name
}
}

// WithAnalytics enables Trace Analytics for all started spans.
func WithAnalytics(on bool) DialOption {
if on {
return WithAnalyticsRate(1.0)
}
return WithAnalyticsRate(0.0)
}

// WithAnalyticsRate sets the sampling rate for Trace Analytics events
// correlated to started spans.
func WithAnalyticsRate(rate float64) DialOption {
return func(cfg *dialConfig) {
cfg.analyticsRate = rate
}
}
156 changes: 156 additions & 0 deletions contrib/gomodule/redigo/redigo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Package redigo provides functions to trace the gomodule/redigo package (https://github.com/gomodule/redigo).
package redigo

import (
"bytes"
"context"
"fmt"
"net"
"net/url"
"strconv"

redis "github.com/gomodule/redigo/redis"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

// Conn is an implementation of the redis.Conn interface that supports tracing
type Conn struct {
redis.Conn
*params
}

// params contains fields and metadata useful for command tracing
type params struct {
config *dialConfig
network string
host string
port string
}

// parseOptions parses a set of arbitrary options (which can be of type redis.DialOption
// or the local DialOption) and returns the corresponding redis.DialOption set as well as
// a configured dialConfig.
func parseOptions(options ...interface{}) ([]redis.DialOption, *dialConfig) {
dialOpts := []redis.DialOption{}
cfg := new(dialConfig)
defaults(cfg)
for _, opt := range options {
switch o := opt.(type) {
case redis.DialOption:
dialOpts = append(dialOpts, o)
case DialOption:
o(cfg)
}
}
return dialOpts, cfg
}

// Dial dials into the network address and returns a traced redis.Conn.
// The set of supported options must be either of type redis.DialOption or this package's DialOption.
func Dial(network, address string, options ...interface{}) (redis.Conn, error) {
dialOpts, cfg := parseOptions(options...)
c, err := redis.Dial(network, address, dialOpts...)
if err != nil {
return nil, err
}
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
tc := Conn{c, &params{cfg, network, host, port}}
return tc, nil
}

// DialURL connects to a Redis server at the given URL using the Redis
// URI scheme. URLs should follow the draft IANA specification for the
// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
// The returned redis.Conn is traced.
func DialURL(rawurl string, options ...interface{}) (redis.Conn, error) {
dialOpts, cfg := parseOptions(options...)
u, err := url.Parse(rawurl)
if err != nil {
return Conn{}, err
}
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
host = u.Host
port = "6379"
}
if host == "" {
host = "localhost"
}
network := "tcp"
c, err := redis.DialURL(rawurl, dialOpts...)
tc := Conn{c, &params{cfg, 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 Conn) newChildSpan(ctx context.Context) ddtrace.Span {
p := tc.params
opts := []ddtrace.StartSpanOption{
tracer.SpanType(ext.SpanTypeRedis),
tracer.ServiceName(p.config.serviceName),
}
if rate := p.config.analyticsRate; rate > 0 {
opts = append(opts, tracer.Tag(ext.EventSampleRate, rate))
}
span, _ := tracer.StartSpanFromContext(ctx, "redis.command", opts...)
span.SetTag("out.network", p.network)
span.SetTag(ext.TargetPort, p.port)
span.SetTag(ext.TargetHost, 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 Conn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
var (
ctx context.Context
ok bool
)
if n := len(args); n > 0 {
ctx, ok = args[n-1].(context.Context)
if ok {
args = args[:n-1]
}
}

span := tc.newChildSpan(ctx)
defer func() {
span.Finish(tracer.WithError(err))
}()

span.SetTag("redis.args_length", strconv.Itoa(len(args)))

if len(commandName) > 0 {
span.SetTag(ext.ResourceName, 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/gomodule/redigo/redis#hdr-Pipelining
span.SetTag(ext.ResourceName, "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.SetTag("redis.raw_command", b.String())
return tc.Conn.Do(commandName, args...)
}
Loading

0 comments on commit 8c8aefe

Please sign in to comment.