Skip to content

Commit

Permalink
Add extra modules
Browse files Browse the repository at this point in the history
  • Loading branch information
vmihailenco committed Oct 21, 2020
1 parent 26ade92 commit 7b1a844
Show file tree
Hide file tree
Showing 11 changed files with 365 additions and 5 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

## Ecosystem

- [redisext](https://github.com/go-redis/redisext) - tracing using OpenTelemetry and OpenCensus.
- [Distributed Locks](https://github.com/bsm/redislock).
- [Redis Cache](https://github.com/go-redis/cache).
- [Rate limiting](https://github.com/go-redis/redis_rate).
Expand Down
11 changes: 11 additions & 0 deletions extra/rediscensus/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/go-redis/redis/extra/rediscensus

go 1.15

replace github.com/go-redis/redis/extra/rediscmd => ../rediscmd

require (
github.com/go-redis/redis/extra/rediscmd v0.0.0-00010101000000-000000000000
github.com/go-redis/redis/v8 v8.3.2
go.opencensus.io v0.22.5
)
45 changes: 45 additions & 0 deletions extra/rediscensus/rediscensus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package rediscensus

import (
"context"

"github.com/go-redis/redis/extra/rediscmd"
"github.com/go-redis/redis/v8"
"go.opencensus.io/trace"
)

type TracingHook struct{}

var _ redis.Hook = TracingHook{}

func (TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
ctx, span := trace.StartSpan(ctx, cmd.FullName())
span.AddAttributes(trace.StringAttribute("db.system", "redis"),
trace.StringAttribute("redis.cmd", rediscmd.CmdString(cmd)))

return ctx, nil
}

func (TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
span := trace.FromContext(ctx)
if err := cmd.Err(); err != nil {
recordErrorOnOCSpan(ctx, span, err)
}
span.End()
return nil
}

func (TracingHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
return ctx, nil
}

func (TracingHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
return nil
}

func recordErrorOnOCSpan(ctx context.Context, span *trace.Span, err error) {
if err != redis.Nil {
span.AddAttributes(trace.BoolAttribute("error", true))
span.Annotate([]trace.Attribute{trace.StringAttribute("Error", "redis error")}, err.Error())
}
}
9 changes: 9 additions & 0 deletions extra/rediscmd/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/go-redis/redis/extra/rediscmd

go 1.15

require (
github.com/go-redis/redis/v8 v8.3.2
github.com/onsi/ginkgo v1.14.2
github.com/onsi/gomega v1.10.3
)
149 changes: 149 additions & 0 deletions extra/rediscmd/rediscmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package rediscmd

import (
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"

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

func CmdString(cmd redis.Cmder) string {
b := make([]byte, 0, 32)
b = AppendCmd(b, cmd)
return String(b)
}

func CmdsString(cmds []redis.Cmder) (string, string) {
const numCmdLimit = 100
const numNameLimit = 10

seen := make(map[string]struct{}, numNameLimit)
unqNames := make([]string, 0, numNameLimit)

b := make([]byte, 0, 32*len(cmds))

for i, cmd := range cmds {
if i > numCmdLimit {
break
}

if i > 0 {
b = append(b, '\n')
}
b = AppendCmd(b, cmd)

if len(unqNames) >= numNameLimit {
continue
}

name := cmd.FullName()
if _, ok := seen[name]; !ok {
seen[name] = struct{}{}
unqNames = append(unqNames, name)
}
}

summary := strings.Join(unqNames, " ")
return summary, String(b)
}

func AppendCmd(b []byte, cmd redis.Cmder) []byte {
const numArgLimit = 32

for i, arg := range cmd.Args() {
if i > numArgLimit {
break
}
if i > 0 {
b = append(b, ' ')
}
b = appendArg(b, arg)
}

if err := cmd.Err(); err != nil {
b = append(b, ": "...)
b = append(b, err.Error()...)
}

return b
}

func appendArg(b []byte, v interface{}) []byte {
const argLenLimit = 64

switch v := v.(type) {
case nil:
return append(b, "<nil>"...)
case string:
if len(v) > argLenLimit {
v = v[:argLenLimit]
}
return appendUTF8String(b, Bytes(v))
case []byte:
if len(v) > argLenLimit {
v = v[:argLenLimit]
}
return appendUTF8String(b, v)
case int:
return strconv.AppendInt(b, int64(v), 10)
case int8:
return strconv.AppendInt(b, int64(v), 10)
case int16:
return strconv.AppendInt(b, int64(v), 10)
case int32:
return strconv.AppendInt(b, int64(v), 10)
case int64:
return strconv.AppendInt(b, v, 10)
case uint:
return strconv.AppendUint(b, uint64(v), 10)
case uint8:
return strconv.AppendUint(b, uint64(v), 10)
case uint16:
return strconv.AppendUint(b, uint64(v), 10)
case uint32:
return strconv.AppendUint(b, uint64(v), 10)
case uint64:
return strconv.AppendUint(b, v, 10)
case float32:
return strconv.AppendFloat(b, float64(v), 'f', -1, 64)
case float64:
return strconv.AppendFloat(b, v, 'f', -1, 64)
case bool:
if v {
return append(b, "true"...)
}
return append(b, "false"...)
case time.Time:
return v.AppendFormat(b, time.RFC3339Nano)
default:
return append(b, fmt.Sprint(v)...)
}
}

func appendUTF8String(dst []byte, src []byte) []byte {
if isSimple(src) {
dst = append(dst, src...)
return dst
}

s := len(dst)
dst = append(dst, make([]byte, hex.EncodedLen(len(src)))...)
hex.Encode(dst[s:], src)
return dst
}

func isSimple(b []byte) bool {
for _, c := range b {
if !isSimpleByte(c) {
return false
}
}
return true
}

func isSimpleByte(c byte) bool {
return c >= 0x21 && c <= 0x7e
}
32 changes: 32 additions & 0 deletions extra/rediscmd/rediscmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package rediscmd

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)

func TestGinkgo(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "redisext")
}

var _ = Describe("AppendArg", func() {
DescribeTable("...",
func(src string, wanted string) {
b := appendArg(nil, src)
Expect(string(b)).To(Equal(wanted))
},

Entry("", "-inf", "-inf"),
Entry("", "+inf", "+inf"),
Entry("", "foo.bar", "foo.bar"),
Entry("", "foo:bar", "foo:bar"),
Entry("", "foo{bar}", "foo{bar}"),
Entry("", "foo-123_BAR", "foo-123_BAR"),
Entry("", "foo\nbar", "666f6f0a626172"),
Entry("", "\000", "00"),
)
})
11 changes: 11 additions & 0 deletions extra/rediscmd/safe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// +build appengine

package rediscmd

func String(b []byte) string {
return string(b)
}

func Bytes(s string) []byte {
return []byte(s)
}
20 changes: 20 additions & 0 deletions extra/rediscmd/unsafe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// +build !appengine

package rediscmd

import "unsafe"

// String converts byte slice to string.
func String(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

// Bytes converts string to byte slice.
func Bytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(
&struct {
string
Cap int
}{s, len(s)},
))
}
11 changes: 11 additions & 0 deletions extra/redisotel/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/go-redis/redis/extra/rediscensus

go 1.15

replace github.com/go-redis/redis/extra/rediscmd => ../rediscmd

require (
github.com/go-redis/redis/extra/rediscmd v0.0.0-00010101000000-000000000000
github.com/go-redis/redis/v8 v8.3.2
go.opentelemetry.io/otel v0.13.0
)
72 changes: 72 additions & 0 deletions extra/redisotel/redisotel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package redisotel

import (
"context"

"github.com/go-redis/redis/extra/rediscmd"
"github.com/go-redis/redis/v8"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/label"
)

var tracer = global.Tracer("github.com/go-redis/redis")

type TracingHook struct{}

var _ redis.Hook = TracingHook{}

func (TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
if !trace.SpanFromContext(ctx).IsRecording() {
return ctx, nil
}

ctx, span := tracer.Start(ctx, cmd.FullName())
span.SetAttributes(
label.String("db.system", "redis"),
label.String("redis.cmd", rediscmd.CmdString(cmd)),
)

return ctx, nil
}

func (TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
span := trace.SpanFromContext(ctx)
if err := cmd.Err(); err != nil {
recordError(ctx, span, err)
}
span.End()
return nil
}

func (TracingHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
if !trace.SpanFromContext(ctx).IsRecording() {
return ctx, nil
}

summary, cmdsString := rediscmd.CmdsString(cmds)

ctx, span := tracer.Start(ctx, "pipeline "+summary)
span.SetAttributes(
label.String("db.system", "redis"),
label.Int("redis.num_cmd", len(cmds)),
label.String("redis.cmds", cmdsString),
)

return ctx, nil
}

func (TracingHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
span := trace.SpanFromContext(ctx)
if err := cmds[0].Err(); err != nil {
recordError(ctx, span, err)
}
span.End()
return nil
}

func recordError(ctx context.Context, span trace.Span, err error) {
if err != redis.Nil {
span.RecordError(ctx, err)
}
}
Loading

0 comments on commit 7b1a844

Please sign in to comment.