Skip to content

Commit 94fe911

Browse files
authored
Merge branch 'main' into alex/2609_hints
2 parents 66b6db8 + 9348732 commit 94fe911

File tree

4 files changed

+114
-2
lines changed

4 files changed

+114
-2
lines changed

.github/workflows/docs_deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ jobs:
4646
- name: Deploy to GitHub Pages
4747
uses: peaceiris/actions-gh-pages@v4
4848
with:
49-
github_token: ${{ secrets.DOCS_DEPLOY_TOKEN }}
49+
github_token: ${{ secrets.PAT_DOCS }}
5050
publish_dir: ./docs/.vitepress/dist
5151
cname: ev.xyz

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)