Files
bookhoard/internal/middleware/request_tracing.go
T
john-okeefe 0438ec4625 refactor(middleware): fix type signatures for Echo v5 compatibility
Update all middleware functions to use *echo.Context (pointer) instead of echo.Context (value) as required by Echo v5.

Changes in device_auth.go:
- Update DeviceAuthMiddleware() signature (line 38)
- Update validateDeviceAuth() signature (line 170)
- Update RequireDeviceAuth() signature (line 212)

Changes in error_handler.go:
- Update RespondWithError() signature (line 44)
- Update RespondWithHTTPError() signature (line 69)
- Update WrapHandler() to accept *echo.Context (line 82)
- Fix context passing in WrapHandler() (c is already pointer)

Changes in rate_limiter.go:
- Update RateLimiterMiddleware() signature (line 102)

Changes in request_tracing.go:
- Update RequestTracingMiddleware() signature (line 48)
- Fix Response() dereference for v5 API (line 264)
  - Use *c.Response() to get http.ResponseWriter

Changes in security.go:
- Update SecurityHeadersMiddleware() signature (line 14)

Changes in device_auth_test.go:
- Update test helper signatures

Changes in middleware_test.go:
- Remove unused import

All middleware now properly implements Echo v5's pointer-based context pattern.
2026-03-06 14:00:05 -05:00

123 lines
3.2 KiB
Go

package middleware
import (
"bookhoard/internal/config"
"bytes"
"encoding/json"
"io"
"log"
"net/http"
"time"
"github.com/google/uuid"
"github.com/labstack/echo/v5"
)
type responseWriter struct {
http.ResponseWriter
size int
}
func (r *responseWriter) Write(b []byte) (int, error) {
size, err := r.ResponseWriter.Write(b)
r.size += size
return size, err
}
type RequestLogEntry struct {
RequestID string `json:"request_id"`
Timestamp time.Time `json:"timestamp"`
Method string `json:"method"`
Path string `json:"path"`
QueryParams map[string]string `json:"query_params,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
UserID string `json:"user_id,omitempty"`
UserRole string `json:"user_role,omitempty"`
UserEmail string `json:"user_email,omitempty"`
RemoteAddr string `json:"remote_addr"`
UserAgent string `json:"user_agent,omitempty"`
Duration time.Duration `json:"duration"`
StatusCode int `json:"status_code"`
ResponseSize int64 `json:"response_size"`
Error string `json:"error,omitempty"`
}
func RequestTracingMiddleware(cfg *config.Config) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error {
start := time.Now()
requestID := c.Response().Header().Get(echo.HeaderXRequestID)
if requestID == "" {
requestID = uuid.New().String()
c.Response().Header().Set(echo.HeaderXRequestID, requestID)
}
recorder := &responseWriter{
ResponseWriter: c.Response(),
}
c.SetResponse(recorder)
var body interface{}
if c.Request().Body != nil && c.Request().Method != "GET" {
bodyBytes, _ := io.ReadAll(c.Request().Body)
c.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
if len(bodyBytes) > 0 && len(bodyBytes) < 10000 {
json.Unmarshal(bodyBytes, &body)
}
}
headers := make(map[string]string)
for k, v := range c.Request().Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
userID, _ := c.Get("user_id").(string)
userRole, _ := c.Get("user_role").(string)
userEmail, _ := c.Get("user_email").(string)
err := next(c)
duration := time.Since(start)
queryParams := make(map[string]string)
for k, v := range c.QueryParams() {
if len(v) > 0 {
queryParams[k] = v[0]
}
}
logEntry := RequestLogEntry{
RequestID: requestID,
Timestamp: start,
Method: c.Request().Method,
Path: c.Request().URL.Path,
QueryParams: queryParams,
Headers: headers,
Body: body,
UserID: userID,
UserRole: userRole,
UserEmail: userEmail,
RemoteAddr: c.RealIP(),
UserAgent: c.Request().UserAgent(),
Duration: duration,
StatusCode: c.Response().(*echo.Response).Status,
ResponseSize: int64(recorder.size),
}
if err != nil {
logEntry.Error = err.Error()
}
logJSON, _ := json.Marshal(logEntry)
log.Printf("[REQUEST] %s", string(logJSON))
return err
}
}
}