diff --git a/internal/middleware/logging.go b/internal/middleware/logging.go index b1d8684..16c66c1 100644 --- a/internal/middleware/logging.go +++ b/internal/middleware/logging.go @@ -28,6 +28,18 @@ func (r *statusRecorder) Write(b []byte) (int, error) { return n, err } +// Flush propagates to the wrapped writer so SSE/streaming handlers can +// flush through this middleware. Without it, w.(http.Flusher) fails and +// streaming endpoints error out. +func (r *statusRecorder) Flush() { + if f, ok := r.ResponseWriter.(http.Flusher); ok { + f.Flush() + } +} + +// Unwrap exposes the wrapped writer for http.ResponseController. +func (r *statusRecorder) Unwrap() http.ResponseWriter { return r.ResponseWriter } + // AccessLog logs one structured line per HTTP request: method, path, // status, duration, request ID, and principal ID. // diff --git a/internal/middleware/metrics.go b/internal/middleware/metrics.go index dc59f63..46a6400 100644 --- a/internal/middleware/metrics.go +++ b/internal/middleware/metrics.go @@ -56,6 +56,17 @@ func (r *metricsRecorder) Write(b []byte) (int, error) { return n, err } +// Flush propagates to the wrapped writer so SSE/streaming handlers can +// flush through the metrics middleware. +func (r *metricsRecorder) Flush() { + if f, ok := r.ResponseWriter.(http.Flusher); ok { + f.Flush() + } +} + +// Unwrap exposes the wrapped writer for http.ResponseController. +func (r *metricsRecorder) Unwrap() http.ResponseWriter { return r.ResponseWriter } + // normalizePath strips URL parameters to keep metric cardinality // bounded. Document and section IDs are replaced with a placeholder. func normalizePath(path string) string { diff --git a/internal/middleware/tracing.go b/internal/middleware/tracing.go index e0d0bff..f0005c1 100644 --- a/internal/middleware/tracing.go +++ b/internal/middleware/tracing.go @@ -67,6 +67,17 @@ func (r *tracingRecorder) WriteHeader(code int) { r.ResponseWriter.WriteHeader(code) } +// Flush propagates to the wrapped writer so SSE/streaming handlers can +// flush through the tracing middleware. +func (r *tracingRecorder) Flush() { + if f, ok := r.ResponseWriter.(http.Flusher); ok { + f.Flush() + } +} + +// Unwrap exposes the wrapped writer for http.ResponseController. +func (r *tracingRecorder) Unwrap() http.ResponseWriter { return r.ResponseWriter } + // TraceIDFromContext extracts the trace ID string from the current span, // useful for correlating logs with traces. func TraceIDFromContext(ctx context.Context) string {