Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

contrib/go-redis/redis.v8: support go-redis.v8 #727

Merged
merged 4 commits into from
Oct 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions contrib/go-redis/redis.v8/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package redis_test

import (
"context"
"time"

"github.com/go-redis/redis/v8"
redistrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis.v8"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

// To start tracing Redis, simply create a new client using the library and continue
// using as you normally would.
func Example() {
ctx := context.Background()
// create a new Client
opts := &redis.Options{Addr: "127.0.0.1", Password: "", DB: 0}
c := redistrace.NewClient(opts)

// any action emits a span
c.Set(ctx, "test_key", "test_value", 0)

// optionally, create a new root span
root, ctx := tracer.StartSpanFromContext(context.Background(), "parent.request",
tracer.SpanType(ext.SpanTypeRedis),
tracer.ServiceName("web"),
tracer.ResourceName("/home"),
)

// commit further commands, which will inherit from the parent in the context.
c.Set(ctx, "food", "cheese", 0)
root.Finish()
}

// You can also trace Redis Pipelines. Simply use as usual and the traces will be
// automatically picked up by the underlying implementation.
func Example_pipeliner() {
ctx := context.Background()
// create a client
opts := &redis.Options{Addr: "127.0.0.1", Password: "", DB: 0}
c := redistrace.NewClient(opts, redistrace.WithServiceName("my-redis-service"))

// open the pipeline
pipe := c.Pipeline()

// submit some commands
pipe.Incr(ctx, "pipeline_counter")
pipe.Expire(ctx, "pipeline_counter", time.Hour)

// execute with trace
pipe.Exec(ctx)
}
60 changes: 60 additions & 0 deletions contrib/go-redis/redis.v8/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package redis

import (
"math"

"gopkg.in/DataDog/dd-trace-go.v1/internal"
)

type clientConfig struct {
serviceName string
analyticsRate float64
}

// ClientOption represents an option that can be used to create or wrap a client.
type ClientOption func(*clientConfig)

func defaults(cfg *clientConfig) {
cfg.serviceName = "redis.client"
// cfg.analyticsRate = globalconfig.AnalyticsRate()
if internal.BoolEnv("DD_TRACE_REDIS_ANALYTICS_ENABLED", false) {
cfg.analyticsRate = 1.0
} else {
cfg.analyticsRate = math.NaN()
}
}

// WithServiceName sets the given service name for the client.
func WithServiceName(name string) ClientOption {
return func(cfg *clientConfig) {
cfg.serviceName = name
}
}

// WithAnalytics enables Trace Analytics for all started spans.
func WithAnalytics(on bool) ClientOption {
return func(cfg *clientConfig) {
if on {
cfg.analyticsRate = 1.0
} else {
cfg.analyticsRate = math.NaN()
}
}
}

// WithAnalyticsRate sets the sampling rate for Trace Analytics events
// correlated to started spans.
func WithAnalyticsRate(rate float64) ClientOption {
return func(cfg *clientConfig) {
if rate >= 0.0 && rate <= 1.0 {
cfg.analyticsRate = rate
} else {
cfg.analyticsRate = math.NaN()
}
}
}
142 changes: 142 additions & 0 deletions contrib/go-redis/redis.v8/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

// Package redis provides tracing functions for tracing the go-redis/redis package (https://github.com/go-redis/redis).
// This package supports versions up to go-redis 6.15.
package redis

import (
"bytes"
"context"

"math"
"net"
"strconv"
"strings"

"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"

"github.com/go-redis/redis/v8"
)

type datadogHook struct {
*params
}
sfroment marked this conversation as resolved.
Show resolved Hide resolved

// params holds the tracer and a set of parameters which are recorded with every trace.
type params struct {
host string
port string
db string
config *clientConfig
}

// NewClient returns a new Client that is traced with the default tracer under
// the service name "redis".
func NewClient(opt *redis.Options, opts ...ClientOption) redis.UniversalClient {
cfg := new(clientConfig)
defaults(cfg)
for _, fn := range opts {
fn(cfg)
}
host, port, err := net.SplitHostPort(opt.Addr)
if err != nil {
host = opt.Addr
port = "6379"
}
params := &params{
host: host,
port: port,
db: strconv.Itoa(opt.DB),
config: cfg,
}
client := redis.NewClient(opt)
client.AddHook(&datadogHook{params: params})
return client
}

func (ddh *datadogHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
raw := cmd.String()
parts := strings.Split(raw, " ")
length := len(parts) - 1
p := ddh.params
opts := []ddtrace.StartSpanOption{
tracer.SpanType(ext.SpanTypeRedis),
tracer.ServiceName(p.config.serviceName),
tracer.ResourceName(parts[0]),
tracer.Tag(ext.TargetHost, p.host),
tracer.Tag(ext.TargetPort, p.port),
tracer.Tag("out.db", p.db),
tracer.Tag("redis.raw_command", raw),
tracer.Tag("redis.args_length", strconv.Itoa(length)),
}
if !math.IsNaN(p.config.analyticsRate) {
opts = append(opts, tracer.Tag(ext.EventSampleRate, p.config.analyticsRate))
}
_, ctx = tracer.StartSpanFromContext(ctx, "redis.command", opts...)
return ctx, nil
}

func (ddh *datadogHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
var span tracer.Span
span, _ = tracer.SpanFromContext(ctx)
var finishOpts []ddtrace.FinishOption
errRedis := cmd.Err()
if errRedis != redis.Nil {
finishOpts = append(finishOpts, tracer.WithError(errRedis))
}
span.Finish(finishOpts...)
return nil
}

func (ddh *datadogHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
raw := commandsToString(cmds)
parts := strings.Split(raw, " ")
length := len(parts) - 1
p := ddh.params
opts := []ddtrace.StartSpanOption{
tracer.SpanType(ext.SpanTypeRedis),
tracer.ServiceName(p.config.serviceName),
tracer.ResourceName(parts[0]),
tracer.Tag(ext.TargetHost, p.host),
tracer.Tag(ext.TargetPort, p.port),
tracer.Tag("out.db", p.db),
tracer.Tag("redis.raw_command", raw),
tracer.Tag("redis.args_length", strconv.Itoa(length)),
tracer.Tag(ext.ResourceName, raw),
tracer.Tag("redis.pipeline_length", strconv.Itoa(len(cmds))),
}
if !math.IsNaN(p.config.analyticsRate) {
opts = append(opts, tracer.Tag(ext.EventSampleRate, p.config.analyticsRate))
}
_, ctx = tracer.StartSpanFromContext(ctx, "redis.command", opts...)
return ctx, nil
}

func (ddh *datadogHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
var span tracer.Span
span, _ = tracer.SpanFromContext(ctx)
var finishOpts []ddtrace.FinishOption
for _, cmd := range cmds {
errCmd := cmd.Err()
if errCmd != redis.Nil {
finishOpts = append(finishOpts, tracer.WithError(errCmd))
}
}
span.Finish(finishOpts...)
return nil
}

// commandsToString returns a string representation of a slice of redis Commands, separated by newlines.
func commandsToString(cmds []redis.Cmder) string {
var b bytes.Buffer
for _, cmd := range cmds {
b.WriteString(cmd.String())
b.WriteString("\n")
}
return b.String()
}
Loading