Skip to content

Commit b7f515a

Browse files
committed
Simplify GET trace sampling
1 parent 3af9562 commit b7f515a

3 files changed

Lines changed: 5 additions & 122 deletions

File tree

cmd/api/main.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,6 @@ func run() error {
324324
r.Use(middleware.Recoverer)
325325
if cfg.Otel.Enabled {
326326
r.Use(otelchi.Middleware(cfg.Otel.ServiceName, otelchi.WithChiRoutes(r)))
327-
r.Use(otel.NewSuccessfulGETErrorTraceMiddleware(cfg.Otel.ServiceName))
328327
}
329328
r.Use(mw.InjectLogger(logger))
330329
r.Use(mw.AccessLogger(accessLogger))
@@ -347,7 +346,6 @@ func run() error {
347346
// OpenTelemetry tracing middleware FIRST (creates span context)
348347
if cfg.Otel.Enabled {
349348
r.Use(otelchi.Middleware(cfg.Otel.ServiceName, otelchi.WithChiRoutes(r)))
350-
r.Use(otel.NewSuccessfulGETErrorTraceMiddleware(cfg.Otel.ServiceName))
351349
}
352350

353351
// Inject logger into request context for handlers to use

lib/otel/http_sampling.go

Lines changed: 3 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
package otel
22

33
import (
4-
"context"
54
"fmt"
65
"net/http"
76

8-
"github.com/go-chi/chi/v5"
9-
otelapi "go.opentelemetry.io/otel"
107
"go.opentelemetry.io/otel/attribute"
11-
"go.opentelemetry.io/otel/codes"
128
sdktrace "go.opentelemetry.io/otel/sdk/trace"
139
"go.opentelemetry.io/otel/trace"
1410
)
@@ -26,9 +22,6 @@ func newSuccessfulGETSampler(ratio float64) sdktrace.Sampler {
2622
}
2723

2824
func (s *successfulGETSampler) ShouldSample(params sdktrace.SamplingParameters) sdktrace.SamplingResult {
29-
if attrValueFromAttrs(params.Attributes, "sampled_from") != "" {
30-
return sdktrace.SamplingResult{Decision: sdktrace.RecordAndSample}
31-
}
3225
if params.Kind == trace.SpanKindServer && httpMethodFromAttrs(params.Attributes) == http.MethodGet {
3326
return s.getSampler.ShouldSample(params)
3427
}
@@ -40,95 +33,11 @@ func (s *successfulGETSampler) Description() string {
4033
}
4134

4235
func httpMethodFromAttrs(attrs []attribute.KeyValue) string {
43-
return attrValueFromAttrs(attrs, "http.method", "http.request.method")
44-
}
45-
46-
func attrValueFromAttrs(attrs []attribute.KeyValue, keys ...string) string {
4736
for _, attr := range attrs {
48-
for _, key := range keys {
49-
if string(attr.Key) == key {
50-
return attr.Value.AsString()
51-
}
37+
switch string(attr.Key) {
38+
case "http.method", "http.request.method":
39+
return attr.Value.AsString()
5240
}
5341
}
5442
return ""
5543
}
56-
57-
type statusCapturingResponseWriter struct {
58-
http.ResponseWriter
59-
status int
60-
wrote bool
61-
}
62-
63-
func (w *statusCapturingResponseWriter) WriteHeader(status int) {
64-
if w.wrote {
65-
return
66-
}
67-
w.wrote = true
68-
w.status = status
69-
w.ResponseWriter.WriteHeader(status)
70-
}
71-
72-
func (w *statusCapturingResponseWriter) Write(body []byte) (int, error) {
73-
if !w.wrote {
74-
w.WriteHeader(http.StatusOK)
75-
}
76-
return w.ResponseWriter.Write(body)
77-
}
78-
79-
// NewSuccessfulGETErrorTraceMiddleware records a compact fallback span for GET
80-
// requests that were sampled out but still returned an error status.
81-
func NewSuccessfulGETErrorTraceMiddleware(serviceName string) func(http.Handler) http.Handler {
82-
tracer := otelapi.Tracer(serviceName + "/http")
83-
84-
return func(next http.Handler) http.Handler {
85-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
86-
if r.Method != http.MethodGet {
87-
next.ServeHTTP(w, r)
88-
return
89-
}
90-
91-
rw := &statusCapturingResponseWriter{
92-
ResponseWriter: w,
93-
status: http.StatusOK,
94-
}
95-
next.ServeHTTP(rw, r)
96-
97-
if rw.status < http.StatusBadRequest {
98-
return
99-
}
100-
101-
spanCtx := trace.SpanContextFromContext(r.Context())
102-
if spanCtx.IsValid() && spanCtx.IsSampled() {
103-
return
104-
}
105-
106-
route := ""
107-
if routeCtx := chi.RouteContext(r.Context()); routeCtx != nil {
108-
route = routeCtx.RoutePattern()
109-
}
110-
if route == "" {
111-
route = r.URL.Path
112-
}
113-
if route == "" {
114-
route = "/"
115-
}
116-
117-
_, span := tracer.Start(
118-
context.Background(),
119-
route,
120-
trace.WithNewRoot(),
121-
trace.WithSpanKind(trace.SpanKindServer),
122-
trace.WithAttributes(
123-
attribute.String("http.method", r.Method),
124-
attribute.String("http.route", route),
125-
attribute.String("url.path", r.URL.Path),
126-
attribute.Int("http.status_code", rw.status),
127-
attribute.String("sampled_from", "unsampled_get_error"),
128-
),
129-
)
130-
span.SetStatus(codes.Error, http.StatusText(rw.status))
131-
span.End()
132-
})
133-
}
134-
}

lib/otel/http_sampling_test.go

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ func TestSuccessfulGETSamplerDropsSuccessfulGETRequests(t *testing.T) {
1818
recorder, router, shutdown := newHTTPTraceTestHarness(t, 0)
1919
defer shutdown()
2020

21-
router.Get("/instances", func(w http.ResponseWriter, r *http.Request) {
22-
w.WriteHeader(http.StatusOK)
23-
})
21+
router.Get("/instances", func(w http.ResponseWriter, r *http.Request) {})
2422

2523
rr := httptest.NewRecorder()
2624
req := httptest.NewRequest(http.MethodGet, "/instances", nil)
@@ -44,32 +42,11 @@ func TestSuccessfulGETSamplerKeepsSuccessfulPOSTRequests(t *testing.T) {
4442
router.ServeHTTP(rr, req)
4543

4644
span := findEndedSpanByName(t, recorder.Ended(), "/instances")
47-
if attrValue(span.Attributes(), "http.method") != http.MethodPost {
45+
if got := attrValue(span.Attributes(), "http.method"); got != http.MethodPost {
4846
t.Fatalf("expected POST span, got attrs %v", span.Attributes())
4947
}
5048
}
5149

52-
func TestSuccessfulGETSamplerKeepsUnsampledGETErrors(t *testing.T) {
53-
recorder, router, shutdown := newHTTPTraceTestHarness(t, 0)
54-
defer shutdown()
55-
56-
router.Get("/instances", func(w http.ResponseWriter, r *http.Request) {
57-
http.Error(w, "boom", http.StatusInternalServerError)
58-
})
59-
60-
rr := httptest.NewRecorder()
61-
req := httptest.NewRequest(http.MethodGet, "/instances", nil)
62-
router.ServeHTTP(rr, req)
63-
64-
span := findEndedSpanByName(t, recorder.Ended(), "/instances")
65-
if attrValue(span.Attributes(), "sampled_from") != "unsampled_get_error" {
66-
t.Fatalf("expected unsampled GET error fallback span, got attrs %v", span.Attributes())
67-
}
68-
if attrValue(span.Attributes(), "http.status_code") != "500" {
69-
t.Fatalf("expected status code attr on fallback span, got attrs %v", span.Attributes())
70-
}
71-
}
72-
7350
func newHTTPTraceTestHarness(t *testing.T, getRatio float64) (*tracetest.SpanRecorder, *chi.Mux, func()) {
7451
t.Helper()
7552

@@ -83,7 +60,6 @@ func newHTTPTraceTestHarness(t *testing.T, getRatio float64) (*tracetest.SpanRec
8360

8461
router := chi.NewRouter()
8562
router.Use(otelchi.Middleware("hypeman-test", otelchi.WithChiRoutes(router)))
86-
router.Use(NewSuccessfulGETErrorTraceMiddleware("hypeman-test"))
8763

8864
return recorder, router, func() {
8965
otelapi.SetTracerProvider(previous)

0 commit comments

Comments
 (0)