Skip to content

Commit d2ee8c7

Browse files
authored
feat: add a span around dialing connections (#830)
1 parent c39d562 commit d2ee8c7

File tree

2 files changed

+85
-26
lines changed

2 files changed

+85
-26
lines changed

rueidisotel/metrics.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/redis/rueidis"
1111
"go.opentelemetry.io/otel"
1212
"go.opentelemetry.io/otel/attribute"
13+
"go.opentelemetry.io/otel/codes"
1314
"go.opentelemetry.io/otel/metric"
1415
"go.opentelemetry.io/otel/trace"
1516
)
@@ -58,6 +59,11 @@ type dialMetrics struct {
5859
recordOpts []metric.RecordOption
5960
}
6061

62+
type dialTracer struct {
63+
trace.Tracer
64+
tAttrs trace.SpanStartEventOption
65+
}
66+
6167
// WithHistogramOption sets the HistogramOption.
6268
// If not set, DefaultHistogramBuckets will be used.
6369
func WithHistogramOption(histogramOption HistogramOption) Option {
@@ -116,7 +122,7 @@ func NewClient(clientOption rueidis.ClientOption, opts ...Option) (rueidis.Clien
116122
return nil, err
117123
}
118124

119-
clientOption.DialCtxFn = trackDialing(metrics, clientOption.DialCtxFn)
125+
clientOption.DialCtxFn = trackDialing(metrics, dialTracer{Tracer: oclient.tracer, tAttrs: oclient.tAttrs}, clientOption.DialCtxFn)
120126

121127
cli, err := rueidis.NewClient(clientOption)
122128
if err != nil {
@@ -174,16 +180,22 @@ func newClient(opts ...Option) (*otelclient, error) {
174180
return cli, nil
175181
}
176182

177-
func trackDialing(m dialMetrics, dialFn func(context.Context, string, *net.Dialer, *tls.Config) (conn net.Conn, err error)) func(context.Context, string, *net.Dialer, *tls.Config) (conn net.Conn, err error) {
178-
return func(ctx context.Context, network string, dialer *net.Dialer, tlsConfig *tls.Config) (conn net.Conn, err error) {
183+
func trackDialing(m dialMetrics, t dialTracer, dialFn func(context.Context, string, *net.Dialer, *tls.Config) (conn net.Conn, err error)) func(context.Context, string, *net.Dialer, *tls.Config) (conn net.Conn, err error) {
184+
return func(ctx context.Context, dst string, dialer *net.Dialer, tlsConfig *tls.Config) (conn net.Conn, err error) {
185+
ctx, span := t.Start(ctx, "redis.dial", kind, trace.WithAttributes(dbattr, attribute.String("server.address", dst)), t.tAttrs)
186+
defer span.End()
187+
179188
m.attempt.Add(ctx, 1, m.addOpts...)
180189

181190
start := time.Now()
182191

183-
conn, err = dialFn(ctx, network, dialer, tlsConfig)
192+
conn, err = dialFn(ctx, dst, dialer, tlsConfig)
184193
if err != nil {
194+
span.RecordError(err)
195+
span.SetStatus(codes.Error, err.Error())
185196
return nil, err
186197
}
198+
span.SetStatus(codes.Ok, "")
187199

188200
// Use floating point division for higher precision (instead of Seconds method).
189201
m.latency.Record(ctx, float64(time.Since(start))/float64(time.Second), m.recordOpts...)

rueidisotel/trace_test.go

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"slices"
78
"strings"
89
"testing"
910
"time"
@@ -338,12 +339,7 @@ func TestWithDBStatement(t *testing.T) {
338339
}
339340
}
340341

341-
func TestWithClientSimple(t *testing.T) {
342-
client, err := rueidis.NewClient(rueidis.ClientOption{InitAddress: []string{"127.0.0.1:6379"}})
343-
if err != nil {
344-
t.Fatal(err)
345-
}
346-
342+
func TestNewClientSimple(t *testing.T) {
347343
exp := tracetest.NewInMemoryExporter()
348344
tracerProvider := trace.NewTracerProvider(trace.WithSyncer(exp))
349345

@@ -352,39 +348,43 @@ func TestWithClientSimple(t *testing.T) {
352348

353349
dbStmtFunc := func(cmdTokens []string) string { return strings.Join(cmdTokens, " ") }
354350

355-
client = WithClient(
356-
client,
351+
client, err := NewClient(
352+
rueidis.ClientOption{InitAddress: []string{"127.0.0.1:6379"}},
357353
TraceAttrs(attribute.String("any", "label")),
358354
MetricAttrs(attribute.String("any", "label")),
359355
WithTracerProvider(tracerProvider),
360356
WithMeterProvider(meterProvider),
361357
WithDBStatement(dbStmtFunc),
362358
WithOperationMetricAttr(),
363359
)
360+
if err != nil {
361+
t.Fatal(err)
362+
}
364363
defer client.Close()
365364

366365
cmd := client.B().Set().Key("key").Value("val").Build()
367366
client.Do(context.Background(), cmd)
368367

369368
// Validate trace
370369
spans := exp.GetSpans().Snapshots()
371-
if len(spans) != 1 {
372-
t.Fatalf("expected 1 span, got %d", len(spans))
370+
if len(spans) < 2 {
371+
t.Fatalf("expected at least 2 spans, got %d", len(spans))
373372
}
374-
span := spans[0]
375-
if span.Name() != "SET" {
376-
t.Fatalf("unexpected span name: got %s, expected %s", span.Name(), "Set")
377-
}
378-
var found bool
379-
for _, attr := range span.Attributes() {
380-
if string(attr.Key) == "any" && attr.Value.AsString() == "label" {
381-
found = true
382-
break
383-
}
373+
374+
commandSpanIdx := slices.IndexFunc(spans, func(span trace.ReadOnlySpan) bool { return span.Name() == "SET" })
375+
if commandSpanIdx == -1 {
376+
t.Fatal("could not find SET span")
384377
}
385-
if !found {
386-
t.Fatalf("expected attribute 'any: label' not found in span attributes")
378+
commandSpan := spans[commandSpanIdx]
379+
validateSpanHasAttribute(t, commandSpan, "any", "label")
380+
381+
dialSpanIdx := slices.IndexFunc(spans, func(span trace.ReadOnlySpan) bool { return span.Name() == "redis.dial" })
382+
if dialSpanIdx == -1 {
383+
t.Fatal("could not find dial span")
387384
}
385+
dialSpan := spans[dialSpanIdx]
386+
validateSpanHasAttribute(t, dialSpan, "server.address", "127.0.0.1:6379")
387+
validateSpanHasAttribute(t, dialSpan, "any", "label")
388388

389389
metrics := metricdata.ResourceMetrics{}
390390
if err := mxp.Collect(context.Background(), &metrics); err != nil {
@@ -395,6 +395,53 @@ func TestWithClientSimple(t *testing.T) {
395395
validateMetricHasAttributes(t, metrics, "rueidis_command_duration_seconds", "operation")
396396
}
397397

398+
func TestNewClientErrorSpan(t *testing.T) {
399+
exp := tracetest.NewInMemoryExporter()
400+
tracerProvider := trace.NewTracerProvider(trace.WithSyncer(exp))
401+
402+
mxp := metric.NewManualReader()
403+
meterProvider := metric.NewMeterProvider(metric.WithReader(mxp))
404+
405+
_, err := NewClient(
406+
rueidis.ClientOption{InitAddress: []string{"256.256.256.256:6379"}},
407+
TraceAttrs(attribute.String("any", "label")),
408+
MetricAttrs(attribute.String("any", "label")),
409+
WithTracerProvider(tracerProvider),
410+
WithMeterProvider(meterProvider),
411+
WithOperationMetricAttr(),
412+
)
413+
if err == nil {
414+
t.Fatal("expected error")
415+
}
416+
417+
spans := exp.GetSpans().Snapshots()
418+
if len(spans) != 1 {
419+
t.Fatalf("expected 1 span, got %d", len(spans))
420+
}
421+
span := spans[0]
422+
if span.Name() != "redis.dial" {
423+
t.Fatalf("expected span name 'redis.dial', got %s", span.Name())
424+
}
425+
validateSpanHasAttribute(t, span, "any", "label")
426+
events := span.Events()
427+
if len(events) != 1 {
428+
t.Fatalf("expected 1 event, got %d", len(events))
429+
}
430+
event := events[0]
431+
if event.Name != "exception" {
432+
t.Fatalf("expected event name 'exception', got %s", event.Name)
433+
}
434+
}
435+
436+
func validateSpanHasAttribute(t *testing.T, span trace.ReadOnlySpan, key, value string) {
437+
t.Helper()
438+
if !slices.ContainsFunc(span.Attributes(), func(attr attribute.KeyValue) bool {
439+
return string(attr.Key) == key && attr.Value.AsString() == value
440+
}) {
441+
t.Fatalf("expected attribute '%s: %s' not found in span attributes", key, value)
442+
}
443+
}
444+
398445
func validateMetrics(t *testing.T, metrics metricdata.ResourceMetrics, name string, value int64) {
399446
t.Helper()
400447

0 commit comments

Comments
 (0)