Skip to content

Commit 9348732

Browse files
authored
feat(tracing): HTTP propagation (#3000)
* feat: add sequencer tracing instrumentation Add OpenTelemetry tracing for the core Sequencer interface. This traces all three main operations: - SubmitBatchTxs: tracks tx count and batch size - GetNextBatch: tracks tx count, forced inclusion count, batch size - VerifyBatch: tracks batch data count and verification result The tracing wrapper can be used with any Sequencer implementation (single, based, etc.) via WithTracingSequencer(). * chore: using helper fn instead of having it duplicated * chore: adding da retreiver syncing * chore: bump sonic version to work with 1.25 * chore: adding tracing for da submitter * chore: adding forced inclusion tracing * chore: handle tracing internally * chore: removed duplicate tracer * chore: simplified naming * chore: add store tracing * chore: add http tracing propagation
1 parent a396437 commit 9348732

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed

pkg/rpc/server/server.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/evstack/ev-node/pkg/config"
2525
"github.com/evstack/ev-node/pkg/p2p"
2626
"github.com/evstack/ev-node/pkg/store"
27+
"github.com/evstack/ev-node/pkg/telemetry"
2728
"github.com/evstack/ev-node/types"
2829
pb "github.com/evstack/ev-node/types/pb/evnode/v1"
2930
rpc "github.com/evstack/ev-node/types/pb/evnode/v1/v1connect"
@@ -413,8 +414,13 @@ func NewServiceHandler(
413414
// Register custom HTTP endpoints
414415
RegisterCustomHTTPEndpoints(mux, store, peerManager, config, bestKnown, logger)
415416

417+
var handler http.Handler = mux
418+
if config.Instrumentation.IsTracingEnabled() {
419+
handler = telemetry.ExtractTraceContext(mux)
420+
}
421+
416422
// Use h2c to support HTTP/2 without TLS
417-
return h2c.NewHandler(mux, &http2.Server{
423+
return h2c.NewHandler(handler, &http2.Server{
418424
IdleTimeout: 120 * time.Second,
419425
MaxReadFrameSize: 1 << 24,
420426
MaxConcurrentStreams: 100,

pkg/telemetry/http_extract.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package telemetry
2+
3+
import (
4+
"net/http"
5+
6+
"go.opentelemetry.io/otel"
7+
"go.opentelemetry.io/otel/propagation"
8+
)
9+
10+
// ExtractTraceContext returns HTTP middleware that extracts W3C Trace Context
11+
// headers (traceparent, tracestate) from incoming requests and adds them to
12+
// the request context. This enables spans created downstream to be children
13+
// of the caller's trace.
14+
func ExtractTraceContext(next http.Handler) http.Handler {
15+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16+
prop := otel.GetTextMapPropagator()
17+
ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
18+
next.ServeHTTP(w, r.WithContext(ctx))
19+
})
20+
}

pkg/telemetry/http_extract_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package telemetry
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
"go.opentelemetry.io/otel"
11+
"go.opentelemetry.io/otel/propagation"
12+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
13+
"go.opentelemetry.io/otel/sdk/trace/tracetest"
14+
"go.opentelemetry.io/otel/trace"
15+
)
16+
17+
func TestExtractTraceContext_WithParentTrace(t *testing.T) {
18+
sr := tracetest.NewSpanRecorder()
19+
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
20+
t.Cleanup(func() { _ = tp.Shutdown(context.Background()) })
21+
otel.SetTracerProvider(tp)
22+
otel.SetTextMapPropagator(propagation.TraceContext{})
23+
24+
tracer := tp.Tracer("test")
25+
26+
// create a parent span to get a valid trace context
27+
parentCtx, parentSpan := tracer.Start(context.Background(), "parent")
28+
parentSpanCtx := parentSpan.SpanContext()
29+
parentSpan.End()
30+
31+
// inject the parent context into headers
32+
headers := make(http.Header)
33+
otel.GetTextMapPropagator().Inject(parentCtx, propagation.HeaderCarrier(headers))
34+
35+
var extractedSpanCtx trace.SpanContext
36+
handler := ExtractTraceContext(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37+
// create a child span using the extracted context
38+
_, childSpan := tracer.Start(r.Context(), "child")
39+
extractedSpanCtx = childSpan.SpanContext()
40+
childSpan.End()
41+
w.WriteHeader(http.StatusOK)
42+
}))
43+
44+
req := httptest.NewRequest(http.MethodGet, "/test", nil)
45+
req.Header = headers
46+
rec := httptest.NewRecorder()
47+
48+
handler.ServeHTTP(rec, req)
49+
50+
require.Equal(t, http.StatusOK, rec.Code)
51+
require.True(t, extractedSpanCtx.IsValid(), "child span should be valid")
52+
require.Equal(t, parentSpanCtx.TraceID(), extractedSpanCtx.TraceID(), "child should have same trace ID as parent")
53+
}
54+
55+
func TestExtractTraceContext_WithoutParentTrace(t *testing.T) {
56+
sr := tracetest.NewSpanRecorder()
57+
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
58+
t.Cleanup(func() { _ = tp.Shutdown(context.Background()) })
59+
otel.SetTracerProvider(tp)
60+
otel.SetTextMapPropagator(propagation.TraceContext{})
61+
62+
tracer := tp.Tracer("test")
63+
64+
var extractedSpanCtx trace.SpanContext
65+
handler := ExtractTraceContext(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66+
// create a span - should be a root span since no parent headers
67+
_, span := tracer.Start(r.Context(), "root")
68+
extractedSpanCtx = span.SpanContext()
69+
span.End()
70+
w.WriteHeader(http.StatusOK)
71+
}))
72+
73+
req := httptest.NewRequest(http.MethodGet, "/test", nil)
74+
// no trace headers
75+
rec := httptest.NewRecorder()
76+
77+
handler.ServeHTTP(rec, req)
78+
79+
require.Equal(t, http.StatusOK, rec.Code)
80+
require.True(t, extractedSpanCtx.IsValid(), "span should be valid")
81+
82+
// verify it's a root span by checking no parent exists in the recorded spans
83+
spans := sr.Ended()
84+
require.Len(t, spans, 1)
85+
require.False(t, spans[0].Parent().IsValid(), "span should be a root span with no parent")
86+
}

0 commit comments

Comments
 (0)