File: nrredis.go

package info (click to toggle)
golang-github-newrelic-go-agent 3.15.2-9
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 8,356 kB
  • sloc: sh: 65; makefile: 6
file content (99 lines) | stat: -rw-r--r-- 2,780 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

// Package nrredis instruments github.com/go-redis/redis/v7.
//
// Use this package to instrument your go-redis/redis/v7 calls without having to
// manually create DatastoreSegments.
package nrredis

import (
	"context"
	"net"
	"strings"

	redis "github.com/go-redis/redis/v7"
	"github.com/newrelic/go-agent/v3/internal"
	newrelic "github.com/newrelic/go-agent/v3/newrelic"
)

func init() { internal.TrackUsage("integration", "datastore", "redis") }

type contextKeyType struct{}

type hook struct {
	segment newrelic.DatastoreSegment
}

var (
	segmentContextKey = contextKeyType(struct{}{})
)

// NewHook creates a redis.Hook to instrument Redis calls.  Add it to your
// client, then ensure that all calls contain a context which includes the
// transaction.  The options are optional.  Provide them to get instance metrics
// broken out by host and port.  The hook returned can be used with
// redis.Client, redis.ClusterClient, and redis.Ring.
func NewHook(opts *redis.Options) redis.Hook {
	h := hook{}
	h.segment.Product = newrelic.DatastoreRedis
	if opts != nil {
		// Per https://godoc.org/github.com/go-redis/redis#Options the
		// network should either be tcp or unix, and the default is tcp.
		if opts.Network == "unix" {
			h.segment.Host = "localhost"
			h.segment.PortPathOrID = opts.Addr
		} else if host, port, err := net.SplitHostPort(opts.Addr); err == nil {
			if "" == host {
				host = "localhost"
			}
			h.segment.Host = host
			h.segment.PortPathOrID = port
		}
	}
	return h
}

func (h hook) before(ctx context.Context, operation string) (context.Context, error) {
	txn := newrelic.FromContext(ctx)
	if txn == nil {
		return ctx, nil
	}
	s := h.segment
	s.StartTime = txn.StartSegmentNow()
	s.Operation = operation
	ctx = context.WithValue(ctx, segmentContextKey, &s)
	return ctx, nil
}

func (h hook) after(ctx context.Context) {
	if segment, ok := ctx.Value(segmentContextKey).(interface{ End() }); ok {
		segment.End()
	}
}

func (h hook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
	return h.before(ctx, cmd.Name())
}

func (h hook) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
	h.after(ctx)
	return nil
}

func pipelineOperation(cmds []redis.Cmder) string {
	operations := make([]string, 0, len(cmds))
	for _, cmd := range cmds {
		operations = append(operations, cmd.Name())
	}
	return "pipeline:" + strings.Join(operations, ",")
}

func (h hook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
	return h.before(ctx, pipelineOperation(cmds))
}

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