From a291faad71cf870dc7fe19133399d2e4e40fa6f2 Mon Sep 17 00:00:00 2001 From: Aleksander Mistewicz Date: Wed, 9 Jul 2025 10:55:13 +0200 Subject: [PATCH] Add OTEL tracing to Range and Txn Signed-off-by: Aleksander Mistewicz --- pkg/go.mod | 1 + pkg/traceutil/trace.go | 10 ++++++++++ server/embed/config_tracing.go | 4 ++++ server/etcdserver/v3_server.go | 34 ++++++++++++++++++++++++++++++++++ server/go.mod | 2 +- 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/pkg/go.mod b/pkg/go.mod index 2081725f8..3f30f4b9c 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -11,6 +11,7 @@ require ( github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 + go.opentelemetry.io/otel/trace v1.37.0 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.73.0 ) diff --git a/pkg/traceutil/trace.go b/pkg/traceutil/trace.go index cd75a9e60..f89ba83c9 100644 --- a/pkg/traceutil/trace.go +++ b/pkg/traceutil/trace.go @@ -22,9 +22,19 @@ import ( "strings" "time" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" "go.uber.org/zap" ) +const instrumentationScope = "go.etcd.io/etcd" + +var Tracer trace.Tracer = noop.NewTracerProvider().Tracer(instrumentationScope) + +func Init(tp trace.TracerProvider) { + Tracer = tp.Tracer(instrumentationScope) +} + // TraceKey is used as a key of context for Trace. type TraceKey struct{} diff --git a/server/embed/config_tracing.go b/server/embed/config_tracing.go index 0ca90fdc5..7fa985a75 100644 --- a/server/embed/config_tracing.go +++ b/server/embed/config_tracing.go @@ -25,6 +25,8 @@ import ( tracesdk "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.uber.org/zap" + + "go.etcd.io/etcd/pkg/v3/traceutil" ) const maxSamplingRatePerMillion = 1000000 @@ -81,6 +83,8 @@ func newTracingExporter(ctx context.Context, cfg *Config) (*tracingExporter, err ), ) + traceutil.Init(traceProvider) + options := []otelgrpc.Option{ otelgrpc.WithPropagators( propagation.NewCompositeTextMapPropagator( diff --git a/server/etcdserver/v3_server.go b/server/etcdserver/v3_server.go index cd85cf988..fb8f88aad 100644 --- a/server/etcdserver/v3_server.go +++ b/server/etcdserver/v3_server.go @@ -24,6 +24,8 @@ import ( "time" "github.com/gogo/protobuf/proto" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" @@ -102,6 +104,16 @@ type Authenticator interface { } func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) { + var span trace.Span + ctx, span = traceutil.Tracer.Start(ctx, "range", trace.WithAttributes( + attribute.String("range_begin", string(r.GetKey())), + attribute.String("range_end", string(r.GetRangeEnd())), + attribute.Int64("rev", r.GetRevision()), + attribute.Int64("limit", r.GetLimit()), + attribute.Bool("count_only", r.GetCountOnly()), + )) + defer span.End() + ctx, trace := traceutil.EnsureTrace(ctx, s.Logger(), "range", traceutil.Field{Key: "range_begin", Value: string(r.Key)}, traceutil.Field{Key: "range_end", Value: string(r.RangeEnd)}, @@ -156,8 +168,30 @@ func (s *EtcdServer) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) return resp.(*pb.DeleteRangeResponse), nil } +// firstCompareKey returns first non-empty key in the list of comparison operations. +func firstCompareKey(c []*pb.Compare) string { + for _, op := range c { + key := string(op.GetKey()) + if key != "" { + return key + } + } + return "" +} + func (s *EtcdServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) { readOnly := txn.IsTxnReadonly(r) + + var span trace.Span + ctx, span = traceutil.Tracer.Start(ctx, "txn", trace.WithAttributes( + attribute.String("compare_first_key", firstCompareKey(r.GetCompare())), + attribute.Int("compare_len", len(r.GetCompare())), + attribute.Int("success_len", len(r.GetSuccess())), + attribute.Int("failure_len", len(r.GetFailure())), + attribute.Bool("read_only", readOnly), + )) + defer span.End() + ctx, trace := traceutil.EnsureTrace(ctx, s.Logger(), "transaction", traceutil.Field{Key: "read_only", Value: readOnly}, ) diff --git a/server/go.mod b/server/go.mod index f8acef47e..ab53278a8 100644 --- a/server/go.mod +++ b/server/go.mod @@ -35,6 +35,7 @@ require ( go.opentelemetry.io/otel v1.37.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 go.opentelemetry.io/otel/sdk v1.37.0 + go.opentelemetry.io/otel/trace v1.37.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.39.0 golang.org/x/net v0.41.0 @@ -67,7 +68,6 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect