mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
rpc,internal/telemetry: fix deferred spanEnd to capture errors via pointer (#33772)
The endSpan closure accepted error by value, meaning deferred calls like defer spanEnd(err) captured the error at defer-time (always nil), not at function-return time. This meant errors were never recorded on spans. - Changed endSpan to accept *error - Updated all call sites in rpc/handler.go to pass error pointers, and adjusted handleCall to avoid propagating child-span errors to the parent - Added TestTracingHTTPErrorRecording to verify that errors from RPC methods are properly recorded on the rpc.runMethod span
This commit is contained in:
parent
ac85a6f254
commit
d8b92cb9e6
3 changed files with 62 additions and 18 deletions
|
|
@ -46,12 +46,12 @@ func BoolAttribute(key string, val bool) Attribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartSpan creates a SpanKind=INTERNAL span.
|
// StartSpan creates a SpanKind=INTERNAL span.
|
||||||
func StartSpan(ctx context.Context, spanName string, attributes ...Attribute) (context.Context, trace.Span, func(error)) {
|
func StartSpan(ctx context.Context, spanName string, attributes ...Attribute) (context.Context, trace.Span, func(*error)) {
|
||||||
return StartSpanWithTracer(ctx, otel.Tracer(""), spanName, attributes...)
|
return StartSpanWithTracer(ctx, otel.Tracer(""), spanName, attributes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartSpanWithTracer requires a tracer to be passed in and creates a SpanKind=INTERNAL span.
|
// StartSpanWithTracer requires a tracer to be passed in and creates a SpanKind=INTERNAL span.
|
||||||
func StartSpanWithTracer(ctx context.Context, tracer trace.Tracer, name string, attributes ...Attribute) (context.Context, trace.Span, func(error)) {
|
func StartSpanWithTracer(ctx context.Context, tracer trace.Tracer, name string, attributes ...Attribute) (context.Context, trace.Span, func(*error)) {
|
||||||
return startSpan(ctx, tracer, trace.SpanKindInternal, name, attributes...)
|
return startSpan(ctx, tracer, trace.SpanKindInternal, name, attributes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ type RPCInfo struct {
|
||||||
// The span name is formatted as $rpcSystem.$rpcService/$rpcMethod
|
// The span name is formatted as $rpcSystem.$rpcService/$rpcMethod
|
||||||
// (e.g. "jsonrpc.engine/newPayloadV4") which follows the Open Telemetry
|
// (e.g. "jsonrpc.engine/newPayloadV4") which follows the Open Telemetry
|
||||||
// semantic convensions: https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#span-name.
|
// semantic convensions: https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#span-name.
|
||||||
func StartServerSpan(ctx context.Context, tracer trace.Tracer, rpc RPCInfo, others ...Attribute) (context.Context, func(error)) {
|
func StartServerSpan(ctx context.Context, tracer trace.Tracer, rpc RPCInfo, others ...Attribute) (context.Context, func(*error)) {
|
||||||
var (
|
var (
|
||||||
name = fmt.Sprintf("%s.%s/%s", rpc.System, rpc.Service, rpc.Method)
|
name = fmt.Sprintf("%s.%s/%s", rpc.System, rpc.Service, rpc.Method)
|
||||||
attributes = append([]Attribute{
|
attributes = append([]Attribute{
|
||||||
|
|
@ -84,7 +84,7 @@ func StartServerSpan(ctx context.Context, tracer trace.Tracer, rpc RPCInfo, othe
|
||||||
}
|
}
|
||||||
|
|
||||||
// startSpan creates a span with the given kind.
|
// startSpan creates a span with the given kind.
|
||||||
func startSpan(ctx context.Context, tracer trace.Tracer, kind trace.SpanKind, spanName string, attributes ...Attribute) (context.Context, trace.Span, func(error)) {
|
func startSpan(ctx context.Context, tracer trace.Tracer, kind trace.SpanKind, spanName string, attributes ...Attribute) (context.Context, trace.Span, func(*error)) {
|
||||||
ctx, span := tracer.Start(ctx, spanName, trace.WithSpanKind(kind))
|
ctx, span := tracer.Start(ctx, spanName, trace.WithSpanKind(kind))
|
||||||
if len(attributes) > 0 {
|
if len(attributes) > 0 {
|
||||||
span.SetAttributes(attributes...)
|
span.SetAttributes(attributes...)
|
||||||
|
|
@ -93,11 +93,11 @@ func startSpan(ctx context.Context, tracer trace.Tracer, kind trace.SpanKind, sp
|
||||||
}
|
}
|
||||||
|
|
||||||
// endSpan ends the span and handles error recording.
|
// endSpan ends the span and handles error recording.
|
||||||
func endSpan(span trace.Span) func(error) {
|
func endSpan(span trace.Span) func(*error) {
|
||||||
return func(err error) {
|
return func(err *error) {
|
||||||
if err != nil {
|
if err != nil && *err != nil {
|
||||||
span.RecordError(err)
|
span.RecordError(*err)
|
||||||
span.SetStatus(codes.Error, err.Error())
|
span.SetStatus(codes.Error, (*err).Error())
|
||||||
}
|
}
|
||||||
span.End()
|
span.End()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -524,7 +524,6 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start root span for the request.
|
// Start root span for the request.
|
||||||
var err error
|
|
||||||
rpcInfo := telemetry.RPCInfo{
|
rpcInfo := telemetry.RPCInfo{
|
||||||
System: "jsonrpc",
|
System: "jsonrpc",
|
||||||
Service: service,
|
Service: service,
|
||||||
|
|
@ -535,24 +534,25 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
|
||||||
telemetry.BoolAttribute("rpc.batch", cp.isBatch),
|
telemetry.BoolAttribute("rpc.batch", cp.isBatch),
|
||||||
}
|
}
|
||||||
ctx, spanEnd := telemetry.StartServerSpan(cp.ctx, h.tracer(), rpcInfo, attrib...)
|
ctx, spanEnd := telemetry.StartServerSpan(cp.ctx, h.tracer(), rpcInfo, attrib...)
|
||||||
defer spanEnd(err)
|
defer spanEnd(nil) // don't propagate errors to parent spans
|
||||||
|
|
||||||
// Start tracing span before parsing arguments.
|
// Start tracing span before parsing arguments.
|
||||||
_, _, pSpanEnd := telemetry.StartSpanWithTracer(ctx, h.tracer(), "rpc.parsePositionalArguments")
|
_, _, pSpanEnd := telemetry.StartSpanWithTracer(ctx, h.tracer(), "rpc.parsePositionalArguments")
|
||||||
args, err := parsePositionalArguments(msg.Params, callb.argTypes)
|
args, pErr := parsePositionalArguments(msg.Params, callb.argTypes)
|
||||||
pSpanEnd(err)
|
pSpanEnd(&pErr)
|
||||||
if err != nil {
|
if pErr != nil {
|
||||||
return msg.errorResponse(&invalidParamsError{err.Error()})
|
return msg.errorResponse(&invalidParamsError{pErr.Error()})
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Start tracing span before running the method.
|
// Start tracing span before running the method.
|
||||||
rctx, _, rSpanEnd := telemetry.StartSpanWithTracer(ctx, h.tracer(), "rpc.runMethod")
|
rctx, _, rSpanEnd := telemetry.StartSpanWithTracer(ctx, h.tracer(), "rpc.runMethod")
|
||||||
answer := h.runMethod(rctx, msg, callb, args)
|
answer := h.runMethod(rctx, msg, callb, args)
|
||||||
|
var rErr error
|
||||||
if answer.Error != nil {
|
if answer.Error != nil {
|
||||||
err = errors.New(answer.Error.Message)
|
rErr = errors.New(answer.Error.Message)
|
||||||
}
|
}
|
||||||
rSpanEnd(err)
|
rSpanEnd(&rErr)
|
||||||
|
|
||||||
// Collect the statistics for RPC calls if metrics is enabled.
|
// Collect the statistics for RPC calls if metrics is enabled.
|
||||||
rpcRequestGauge.Inc(1)
|
rpcRequestGauge.Inc(1)
|
||||||
|
|
@ -625,7 +625,7 @@ func (h *handler) runMethod(ctx context.Context, msg *jsonrpcMessage, callb *cal
|
||||||
if response.Error != nil {
|
if response.Error != nil {
|
||||||
err = errors.New(response.Error.Message)
|
err = errors.New(response.Error.Message)
|
||||||
}
|
}
|
||||||
spanEnd(err)
|
spanEnd(&err)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||||
|
|
@ -141,6 +142,49 @@ func TestTracingHTTP(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTracingErrorRecording verifies that errors are recorded on spans.
|
||||||
|
func TestTracingHTTPErrorRecording(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
server, tracer, exporter := newTracingServer(t)
|
||||||
|
httpsrv := httptest.NewServer(server)
|
||||||
|
t.Cleanup(httpsrv.Close)
|
||||||
|
client, err := DialHTTP(httpsrv.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to dial: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(client.Close)
|
||||||
|
|
||||||
|
// Call a method that returns an error.
|
||||||
|
var result any
|
||||||
|
err = client.Call(&result, "test_returnError")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error from test_returnError")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush and verify spans recorded the error.
|
||||||
|
if err := tracer.ForceFlush(context.Background()); err != nil {
|
||||||
|
t.Fatalf("failed to flush: %v", err)
|
||||||
|
}
|
||||||
|
spans := exporter.GetSpans()
|
||||||
|
|
||||||
|
// Only the runMethod span should have error status.
|
||||||
|
if len(spans) == 0 {
|
||||||
|
t.Fatal("no spans were emitted")
|
||||||
|
}
|
||||||
|
for _, span := range spans {
|
||||||
|
switch span.Name {
|
||||||
|
case "rpc.runMethod":
|
||||||
|
if span.Status.Code != codes.Error {
|
||||||
|
t.Errorf("expected %s span status Error, got %v", span.Name, span.Status.Code)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if span.Status.Code == codes.Error {
|
||||||
|
t.Errorf("unexpected error status on span %s", span.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestTracingBatchHTTP verifies that RPC spans are emitted for batched JSON-RPC calls over HTTP.
|
// TestTracingBatchHTTP verifies that RPC spans are emitted for batched JSON-RPC calls over HTTP.
|
||||||
func TestTracingBatchHTTP(t *testing.T) {
|
func TestTracingBatchHTTP(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue