RelayCaptureMiddleware currently writes capture files under data/relay-capture/YYYY-MM-DD/<request_id>.log using custom text sections:
=== REQUEST ... ====== RESPONSE ====== END duration_ms=... ===This format is easy to append to, but hard to parse reliably. It also stores sensitive request headers in plaintext. The new design changes relay capture output to one structured JSON file per captured request while preserving relay behavior.
data/relay-capture/YYYY-MM-DD/<request_id>.json.response.body as the complete concatenated response string.response.body_chunks as the ordered chunks captured from ResponseWriter.Write and WriteString.map[string][]string, matching http.Header semantics.common.Marshal for JSON encoding..log format.Each capture file contains one complete JSON object:
{
"request_id": "test-non-stream-120000",
"captured_at": "2026-06-23T12:00:00.000000000Z",
"duration_ms": 123,
"user_id": 42,
"request": {
"method": "POST",
"path": "/v1/chat/completions",
"query": "stream=true",
"headers": {
"Authorization": ["[REDACTED]"],
"Content-Type": ["application/json"]
},
"body": "{\"model\":\"gpt-4\"}"
},
"response": {
"status_code": 200,
"headers": {
"Content-Type": ["application/json; charset=utf-8"]
},
"body": "{\"id\":\"chatcmpl-test\"}",
"body_chunks": ["{\"id\":\"chatcmpl-test\"}"]
},
"capture_errors": []
}
capture_errors is present to preserve non-fatal capture problems without failing the relay request. It should be an empty array when capture succeeds.
capture_relay cache with the authenticated user ID. Disabled users continue through the middleware unchanged.start, captured_at, request_id, user ID, method, path, query, and a redacted copy of request headers.common.GetBodyStorage(c), not directly from c.Request.Body, so downstream handlers can continue using the reusable body storage.c.Writer with a capture response writer that delegates writes to the original writer and copies only the successfully written bytes into an in-memory collector.c.Next().body, and ordered body_chunks.common.Marshal.data/relay-capture/YYYY-MM-DD/<request_id>.json.If request_id is empty, generate a fallback value like capture-<unix_nano> to avoid invalid or colliding file names in tests and edge paths.
Request header redaction is case-insensitive and only affects the capture record. It must not mutate c.Request.Header.
Initial sensitive request headers:
AuthorizationProxy-AuthorizationX-Api-KeyApi-KeyCookieEach value for a sensitive header is replaced with [REDACTED]. Response headers are copied as-is in this change.
Capture is diagnostic. It must not block or alter relay behavior.
capture_errors.ResponseWriter result exactly as today. The collector only records bytes that were successfully written.common.SysError and do not write a partial capture file.common.SysError and leave the relay response untouched.=== END text trailer are removed because JSON is written once after c.Next().Update the existing relay capture tests around middleware/relay_capture.go.
Unit coverage:
map[string][]string and redacts sensitive request headers, including case variants..json and the existing date directory layout.body and body_chunks.Integration coverage:
duration_ms, user_id, and request_id.response.body containing the full SSE response and response.body_chunks preserving chunk order, including [DONE].Bearer sk-test are absent from capture files and replaced with [REDACTED].capture-*.json file name.Primary verification command:
go test ./middleware