mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-27 05:16:22 +02:00
83 lines
2.3 KiB
Go
83 lines
2.3 KiB
Go
package tracing
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/propagation"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
// HTTPMiddleware creates spans for each HTTP request.
|
|
// Extracts trace context from incoming headers and sets span attributes.
|
|
func HTTPMiddleware(next http.Handler) http.Handler {
|
|
tracer := otel.Tracer("sentinel-soc/http")
|
|
propagator := otel.GetTextMapPropagator()
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Extract trace context from incoming headers.
|
|
ctx := propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
|
|
|
|
spanName := fmt.Sprintf("%s %s", r.Method, r.URL.Path)
|
|
ctx, span := tracer.Start(ctx, spanName,
|
|
trace.WithSpanKind(trace.SpanKindServer),
|
|
trace.WithAttributes(
|
|
attribute.String("http.method", r.Method),
|
|
attribute.String("http.url", r.URL.String()),
|
|
attribute.String("http.target", r.URL.Path),
|
|
attribute.String("http.user_agent", r.UserAgent()),
|
|
attribute.String("net.host.name", r.Host),
|
|
),
|
|
)
|
|
defer span.End()
|
|
|
|
// Wrap response writer to capture status code.
|
|
sw := &statusWriter{ResponseWriter: w, status: http.StatusOK}
|
|
next.ServeHTTP(sw, r.WithContext(ctx))
|
|
|
|
span.SetAttributes(
|
|
attribute.Int("http.status_code", sw.status),
|
|
)
|
|
if sw.status >= 400 {
|
|
span.SetAttributes(attribute.Bool("error", true))
|
|
}
|
|
})
|
|
}
|
|
|
|
// statusWriter captures the HTTP status code for span attributes.
|
|
// Implements http.Flusher to support SSE/streaming through middleware chain.
|
|
type statusWriter struct {
|
|
http.ResponseWriter
|
|
status int
|
|
wroteHeader bool
|
|
}
|
|
|
|
func (sw *statusWriter) WriteHeader(code int) {
|
|
if !sw.wroteHeader {
|
|
sw.status = code
|
|
sw.wroteHeader = true
|
|
}
|
|
sw.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
func (sw *statusWriter) Write(b []byte) (int, error) {
|
|
if !sw.wroteHeader {
|
|
sw.wroteHeader = true
|
|
}
|
|
return sw.ResponseWriter.Write(b)
|
|
}
|
|
|
|
// Flush delegates to the underlying ResponseWriter if it supports http.Flusher.
|
|
// Required for SSE streaming endpoints to work through the middleware chain.
|
|
func (sw *statusWriter) Flush() {
|
|
if f, ok := sw.ResponseWriter.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
}
|
|
|
|
// Unwrap returns the underlying ResponseWriter for Go 1.20+ ResponseController.
|
|
func (sw *statusWriter) Unwrap() http.ResponseWriter {
|
|
return sw.ResponseWriter
|
|
}
|