Skip to content

feat(ai-gateway): store api_request_log bodies in R2, keep 30 days - #6768

Merged
chrarnoldus merged 8 commits into
mainfrom
api-request-log-r2-storage
Sep 29, 2026
Merged

chrarnoldus merged 8 commits into
mainfrom
api-request-log-r2-storage

Conversation

@chrarnoldus

@chrarnoldus chrarnoldus commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

api_request_log request and response bodies are now stored in a single R2 bucket instead of in the request/response columns, so they no longer grow the primary database. DB retention for api_request_log rows is raised from 7 to 30 days.

  • New nullable columns: request_r2_key and response_r2_key. The migration only adds columns. The legacy request/response columns stay, and new rows leave them NULL.
  • Bodies are written to R2_API_REQUEST_LOG_BUCKET_NAME under YYYY-MM-DD/<uuid>/request.json and .../response.txt.
  • The bucket is accessed with dedicated credentials, R2_API_REQUEST_LOG_ACCESS_KEY_ID and R2_API_REQUEST_LOG_SECRET_ACCESS_KEY, in the existing R2_ACCOUNT_ID account. The shared R2_ACCESS_KEY_ID token is not used for this bucket. r2/client.ts now exposes a createR2Client(credentials) factory, which the shared r2Client also uses.
  • The dedicated client and bucket name are loaded on first use rather than when the module is imported. If configuration is missing, only request logging is affected; the gateway keeps working.
  • The request body is still redacted: BYOK credentials are replaced before upload. JSONB sanitisation is no longer applied, because the body is no longer stored in PostgreSQL.
  • The request and response upload independently. Each object that was written has its key recorded on the row. Any failed upload, including missing bucket or credential configuration, is recorded as error.r2_upload_error, and the row is still inserted with its metadata. These rows also show up in the errors-only download.
  • The admin ZIP download fetches each batch's blobs from R2 in parallel. Legacy rows fall back to the inline columns. A missing object is skipped. Any other R2 read failure writes a <id>_<kind>_load-error.txt entry and the export continues, rather than producing a truncated ZIP.
  • The cleanup cron still deletes only DB rows, now older than 30 days (was 7). Blob expiry is configured on the R2 bucket with a lifecycle rule, not in application code.

Verification

  • tsgo typecheck for apps/web, oxlint on changed files, oxfmt.

  • Automated tests are left to CI. New tests use an in-memory fake R2 client and cover:

    • the dedicated credentials being used;
    • BYOK redaction in the stored request;
    • total and partial upload failure;
    • R2-backed and legacy downloads;
    • missing or failing R2 objects.

    The cleanup cron test now checks the 30-day cutoff.

  • No manual end-to-end test: this needs the real bucket and credentials.

Visual Changes

N/A

Reviewer Notes

  • Deployment prerequisites: create an R2 API token with Object Read & Write limited to the request-log bucket. Then set R2_API_REQUEST_LOG_BUCKET_NAME, R2_API_REQUEST_LOG_ACCESS_KEY_ID and R2_API_REQUEST_LOG_SECRET_ACCESS_KEY via pnpm web:env set <VARIABLE>. Until then, rows are logged with r2_upload_error and no bodies.
  • The bucket's lifecycle rule should expire objects no earlier than the 30-day DB retention, otherwise downloads of recent rows will silently skip expired bodies.
  • The bucket must be in the R2_ACCOUNT_ID account and reachable through the default <account>.r2.cloudflarestorage.com endpoint.
  • The legacy request/response columns can be dropped in a follow-up once the retention window has removed all rows that still use them.

@chrarnoldus chrarnoldus self-assigned this Sep 26, 2026
Comment thread apps/web/src/lib/r2/api-request-log.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit switches api_request_log blob storage to dedicated, lazily resolved R2 credentials through a new createR2Client factory; the wiring is correct across the two callers and the tests, with no observable defect introduced.

Files Reviewed (8 files)
  • .env.local.example
  • ENVIRONMENT.md
  • apps/web/.env.test
  • apps/web/src/app/admin/api/api-request-log/download/route.test.ts
  • apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts
  • apps/web/src/lib/r2/api-request-log.ts
  • apps/web/src/lib/r2/client.ts
  • apps/web/src/tests/helpers/fake-r2.helper.ts
Previous Review Summaries (4 snapshots, latest commit d524388)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit d524388)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit only raises the api_request_log DB retention window from 7 to 30 days and updates its test; the change is correct, internally consistent, and correctly covered by the adjusted test boundaries.

Files Reviewed (2 files)
  • apps/web/src/app/api/cron/cleanup-api-request-log/route.ts
  • apps/web/src/app/api/cron/cleanup-api-request-log/route.test.ts

Previous review (commit aaa5e04)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The single-bucket refactor is mechanically clean and internally consistent (r2_region fully removed, no dangling references); the only open concern remains that R2 request/response bodies have no deletion path matching the DB retention window.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/r2/api-request-log.ts — R2 request/response bodies still have no deletion path, so they outlive the DB retention window. Carried forward; already tracked in the existing review thread and documented in the PR as a rollout prerequisite (R2 lifecycle rule).
Files Reviewed (11 files)
  • .env.local.example
  • ENVIRONMENT.md
  • apps/web/.env.test
  • apps/web/src/app/admin/api/api-request-log/download/route.test.ts
  • apps/web/src/app/admin/api/api-request-log/download/route.ts
  • apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts
  • apps/web/src/lib/r2/api-request-log.ts - 1 issue (retained)
  • packages/db/src/migrations/0263_api_request_log_r2.sql
  • packages/db/src/migrations/meta/0263_snapshot.json
  • packages/db/src/migrations/meta/_journal.json
  • packages/db/src/schema.ts

Fix these issues in Kilo Cloud

Previous review (commit 4b5575e)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental commit correctly fixes partial R2 uploads so every object actually written is referenced by its row; the only remaining concern is that R2 bodies still have no deletion path matching the DB retention window.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/r2/api-request-log.ts — R2 request/response bodies still have no deletion path, so they outlive the DB retention window. Retained summary-only; already tracked in the existing review thread, and the partial-upload half was fixed in 4b5575e.
Files Reviewed (3 files)
  • apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts
  • apps/web/src/lib/ai-gateway/rewriteModelResponse.ts
  • apps/web/src/lib/r2/api-request-log.ts - 1 issue (retained)

Fix these issues in Kilo Cloud

Previous review (commit ca559b3)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/r2/api-request-log.ts 44 R2 request/response bodies have no deletion path, so they outlive the DB retention window; a partially failed upload also leaves orphaned objects
Files Reviewed (13 files)
  • .env.local.example
  • ENVIRONMENT.md
  • apps/web/.env.test
  • apps/web/src/app/admin/api/api-request-log/download/route.test.ts
  • apps/web/src/app/admin/api/api-request-log/download/route.ts
  • apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts
  • apps/web/src/lib/ai-gateway/rewriteModelResponse.ts
  • apps/web/src/lib/r2/api-request-log.ts - 1 issue
  • apps/web/src/tests/helpers/fake-r2.helper.ts
  • packages/db/src/migrations/0263_api_request_log_r2.sql
  • packages/db/src/migrations/meta/0263_snapshot.json
  • packages/db/src/migrations/meta/_journal.json
  • packages/db/src/schema.ts

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@chrarnoldus chrarnoldus changed the title feat(ai-gateway): store api_request_log bodies in R2 feat(ai-gateway): store api_request_log bodies in R2, keep 30 days Sep 28, 2026
chrarnoldus and others added 3 commits September 28, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants