-
Notifications
You must be signed in to change notification settings - Fork 190
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #248 from axw/v2-redis
module/apmredigo: introduce redigo instrumentation
- Loading branch information
Showing
8 changed files
with
402 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
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
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,93 @@ | ||
package apmredigo | ||
|
||
import ( | ||
"context" | ||
"strings" | ||
"time" | ||
|
||
"github.com/gomodule/redigo/redis" | ||
|
||
"github.com/elastic/apm-agent-go" | ||
) | ||
|
||
// Conn is the interface returned by ContextConn. | ||
// | ||
// Conn's Do method reports spans using the bound context. | ||
type Conn interface { | ||
redis.Conn | ||
|
||
// WithContext returns a shallow copy of the connection with | ||
// its context changed to ctx. | ||
// | ||
// To report commands as spans, ctx must contain a transaction or span. | ||
WithContext(ctx context.Context) Conn | ||
} | ||
|
||
// Wrap wraps conn such that its Do method calls apmredigo.Do with | ||
// context.Background(). The context can be changed using Conn.WithContext. | ||
// | ||
// If conn implements redis.ConnWithTimeout, then the DoWithTimeout method | ||
// will similarly call apmredigo.DoWithTimeout. | ||
// | ||
// Send and Receive calls are not currently captured. | ||
func Wrap(conn redis.Conn) Conn { | ||
ctx := context.Background() | ||
if cwt, ok := conn.(redis.ConnWithTimeout); ok { | ||
return contextConnWithTimeout{ConnWithTimeout: cwt, ctx: ctx} | ||
} | ||
return contextConn{Conn: conn, ctx: ctx} | ||
} | ||
|
||
type contextConnWithTimeout struct { | ||
redis.ConnWithTimeout | ||
ctx context.Context | ||
} | ||
|
||
func (c contextConnWithTimeout) WithContext(ctx context.Context) Conn { | ||
c.ctx = ctx | ||
return c | ||
} | ||
|
||
func (c contextConnWithTimeout) Do(commandName string, args ...interface{}) (reply interface{}, err error) { | ||
return Do(c.ctx, c.ConnWithTimeout, commandName, args...) | ||
} | ||
|
||
func (c contextConnWithTimeout) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { | ||
return DoWithTimeout(c.ctx, c.ConnWithTimeout, timeout, commandName, args...) | ||
} | ||
|
||
type contextConn struct { | ||
redis.Conn | ||
ctx context.Context | ||
} | ||
|
||
func (c contextConn) WithContext(ctx context.Context) Conn { | ||
c.ctx = ctx | ||
return c | ||
} | ||
|
||
func (c contextConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { | ||
return Do(c.ctx, c.Conn, commandName, args...) | ||
} | ||
|
||
// Do calls conn.Do(commandName, args...), and also reports the operation as a span to Elastic APM. | ||
func Do(ctx context.Context, conn redis.Conn, commandName string, args ...interface{}) (interface{}, error) { | ||
spanName := strings.ToUpper(commandName) | ||
if spanName == "" { | ||
spanName = "(flush pipeline)" | ||
} | ||
span, _ := elasticapm.StartSpan(ctx, spanName, "cache.redis") | ||
defer span.End() | ||
return conn.Do(commandName, args...) | ||
} | ||
|
||
// DoWithTimeout calls redis.DoWithTimeout(conn, timeout, commandName, args...), and also reports the operation as a span to Elastic APM. | ||
func DoWithTimeout(ctx context.Context, conn redis.Conn, timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { | ||
spanName := strings.ToUpper(commandName) | ||
if spanName == "" { | ||
spanName = "(flush pipeline)" | ||
} | ||
span, _ := elasticapm.StartSpan(ctx, spanName, "cache.redis") | ||
defer span.End() | ||
return redis.DoWithTimeout(conn, timeout, commandName, args...) | ||
} |
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,107 @@ | ||
package apmredigo_test | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"testing" | ||
"time" | ||
|
||
"github.com/gomodule/redigo/redis" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/elastic/apm-agent-go" | ||
"github.com/elastic/apm-agent-go/apmtest" | ||
"github.com/elastic/apm-agent-go/module/apmredigo" | ||
) | ||
|
||
func TestWrap(t *testing.T) { | ||
var conn mockConn | ||
_, spans, _ := apmtest.WithTransaction(func(ctx context.Context) { | ||
conn := apmredigo.Wrap(conn).WithContext(ctx) | ||
conn.Do("PING", "hello, world!") | ||
}) | ||
require.Len(t, spans, 1) | ||
assert.Equal(t, "PING", spans[0].Name) | ||
assert.Equal(t, "cache.redis", spans[0].Type) | ||
} | ||
|
||
func TestWithContext(t *testing.T) { | ||
ping := func(ctx context.Context, conn apmredigo.Conn) { | ||
span, ctx := elasticapm.StartSpan(ctx, "ping", "custom") | ||
defer span.End() | ||
|
||
// bind conn to the ctx containing the span above | ||
conn = conn.WithContext(ctx) | ||
conn.Do("PING", "hello, world!") | ||
} | ||
|
||
var conn mockConn | ||
_, spans, _ := apmtest.WithTransaction(func(ctx context.Context) { | ||
conn := apmredigo.Wrap(conn) | ||
ping(ctx, conn) | ||
}) | ||
require.Len(t, spans, 2) | ||
assert.Equal(t, "PING", spans[0].Name) | ||
assert.Equal(t, "ping", spans[1].Name) | ||
assert.Equal(t, spans[1].ID, spans[0].ParentID) | ||
} | ||
|
||
func TestConnWithTimeout(t *testing.T) { | ||
var conn mockConnWithTimeout | ||
_, spans, _ := apmtest.WithTransaction(func(ctx context.Context) { | ||
conn := apmredigo.Wrap(conn).WithContext(ctx) | ||
redis.DoWithTimeout(conn, time.Second, "PING", "hello, world!") | ||
}) | ||
require.Len(t, spans, 1) | ||
assert.Equal(t, "PING", spans[0].Name) | ||
assert.Equal(t, "cache.redis", spans[0].Type) | ||
} | ||
|
||
func TestWrapPipeline(t *testing.T) { | ||
var conn mockConnWithTimeout | ||
_, spans, _ := apmtest.WithTransaction(func(ctx context.Context) { | ||
conn := apmredigo.Wrap(conn).WithContext(ctx) | ||
conn.Do("") | ||
redis.DoWithTimeout(conn, time.Second, "") | ||
}) | ||
require.Len(t, spans, 2) | ||
assert.Equal(t, "(flush pipeline)", spans[0].Name) | ||
assert.Equal(t, "(flush pipeline)", spans[1].Name) | ||
} | ||
|
||
type mockConnWithTimeout struct{ mockConn } | ||
|
||
func (mockConnWithTimeout) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { | ||
return []byte("Done"), errors.New("DoWithTimeout failed") | ||
} | ||
|
||
func (mockConnWithTimeout) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { | ||
return []byte("REceived"), errors.New("ReceiveWithTimeout failed") | ||
} | ||
|
||
type mockConn struct{} | ||
|
||
func (mockConn) Close() error { | ||
panic("Close not implemented") | ||
} | ||
|
||
func (mockConn) Err() error { | ||
panic("Err not implemented") | ||
} | ||
|
||
func (mockConn) Flush() error { | ||
panic("Flush not implemented") | ||
} | ||
|
||
func (mockConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { | ||
return []byte("Done"), errors.New("Do failed") | ||
} | ||
|
||
func (mockConn) Send(commandName string, args ...interface{}) error { | ||
return errors.New("Send failed") | ||
} | ||
|
||
func (mockConn) Receive() (reply interface{}, err error) { | ||
return []byte("Received"), errors.New("Receive failed") | ||
} |
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,2 @@ | ||
// Package apmredigo provides helpers for tracing github.com/gomodule/redigo/redis client operations as spans. | ||
package apmredigo |
Oops, something went wrong.