-
Notifications
You must be signed in to change notification settings - Fork 439
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Working go-redis integration with 1st test in test-suit * adding parent-child span test, cleaning code * test multiple commands * test errors propagation * todo check multiple test names (separate go routines asynchronous pbms * Casting tests to write types * After Aaditya's review * tracing pipelines with test * Overiding Pipeline() * go-redis: ensure parent context propagation * go-redis: fix tests for context * After Aaditya's review * Rewriting Exec() method and having a separate ExecWithContext() * Correction for tests * Adding TraceParams + commets * After Christian's review * docker-compose agent correction * go-redis: examples * go-redis: examples * go-redis: touch up examples * go-redis doc fix * goredis doc fix * go-redis pipeline -> pipeliner * np: example
- Loading branch information
1 parent
fbe92b2
commit c2f3125
Showing
3 changed files
with
451 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
// p 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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
} |
Oops, something went wrong.