TraceMiddleware intercepts and logs JSON API requests and responses
(app *application.Application)
| 158 | |
| 159 | // TraceMiddleware intercepts and logs JSON API requests and responses |
| 160 | func TraceMiddleware(app *application.Application) echo.MiddlewareFunc { |
| 161 | return func(next echo.HandlerFunc) echo.HandlerFunc { |
| 162 | return func(c echo.Context) error { |
| 163 | if !app.ApplicationConfig().EnableTracing { |
| 164 | return next(c) |
| 165 | } |
| 166 | |
| 167 | initializeTracing(app.ApplicationConfig().TracingMaxItems) |
| 168 | |
| 169 | ct, _, _ := mime.ParseMediaType(c.Request().Header.Get("Content-Type")) |
| 170 | if ct != "application/json" { |
| 171 | return next(c) |
| 172 | } |
| 173 | |
| 174 | body, err := io.ReadAll(c.Request().Body) |
| 175 | if err != nil { |
| 176 | xlog.Error("Failed to read request body") |
| 177 | return err |
| 178 | } |
| 179 | |
| 180 | // Restore the body for downstream handlers |
| 181 | c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) |
| 182 | |
| 183 | startTime := time.Now() |
| 184 | |
| 185 | // Cap captured payload size. Without this, /embeddings and |
| 186 | // streaming /chat/completions blow the in-memory buffer into the |
| 187 | // tens of MB, which then locks the admin Traces UI fetching the |
| 188 | // JSON dump faster than the 5s auto-refresh. |
| 189 | maxBodyBytes := app.ApplicationConfig().TracingMaxBodyBytes |
| 190 | |
| 191 | // Wrap response writer to capture body |
| 192 | resBody := new(bytes.Buffer) |
| 193 | mw := &bodyWriter{ |
| 194 | ResponseWriter: c.Response().Writer, |
| 195 | body: resBody, |
| 196 | maxBytes: maxBodyBytes, |
| 197 | } |
| 198 | c.Response().Writer = mw |
| 199 | |
| 200 | handlerErr := next(c) |
| 201 | |
| 202 | // Restore original writer unconditionally |
| 203 | c.Response().Writer = mw.ResponseWriter |
| 204 | |
| 205 | // Determine response status (use 500 if handler errored and no status was set) |
| 206 | status := c.Response().Status |
| 207 | if status == 0 && handlerErr != nil { |
| 208 | status = http.StatusInternalServerError |
| 209 | } |
| 210 | |
| 211 | // Create exchange log (always, even on error). Sensitive headers |
| 212 | // (Authorization, API keys, cookies) are redacted before storage — |
| 213 | // the trace endpoint is admin-only but the buffer is also reachable |
| 214 | // via any heap-dump-style introspection, and tokens shouldn't |
| 215 | // outlive the request that carried them. |
| 216 | requestHeaders := redactSensitiveHeaders(c.Request().Header) |
| 217 | requestBody, requestTruncated := truncateForTrace(body, maxBodyBytes) |
no test coverage detected