feat(core): add enableCap query param to bound countLogs scan#8796
Merged
simeng-li merged 1 commit intoMay 13, 2026
Merged
Conversation
COMPARE TO
|
| Name | Diff |
|---|---|
| .changeset/logs-enable-cap.md | 📈 +696 Bytes |
| packages/core/src/middleware/koa-pagination.test.ts | 📈 +2.06 KB |
| packages/core/src/middleware/koa-pagination.ts | 📈 +533 Bytes |
| packages/core/src/queries/log.ts | 📈 +1.03 KB |
| packages/core/src/routes/hook.test.ts | 📈 +1.2 KB |
| packages/core/src/routes/hook.ts | 📈 +198 Bytes |
| packages/core/src/routes/log.test.ts | 📈 +1.02 KB |
| packages/core/src/routes/log.ts | 📈 +225 Bytes |
| packages/integration-tests/src/api/logs.ts | 📈 +152 Bytes |
| packages/integration-tests/src/tests/api/audit-logs/count-cap.test.ts | 📈 +1.37 KB |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an opt-in enableCap=true query param to Management API log endpoints to cap the count(*) scan (avoiding statement_timeout on very large tenants) and updates pagination signaling when totals are capped.
Changes:
- Add capped-count mode to
countLogsand propagate “capped” state through routes and pagination context. - Update pagination middleware to emit
Total-Number-Is-Capped: true, omitLink: rel="last", and keepnextavailable when capped. - Add/adjust unit + integration tests and a changeset documenting the new opt-in behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/queries/log.ts | Adds capped count query option and returns isCapped when saturation occurs. |
| packages/core/src/middleware/koa-pagination.ts | Adds totalCountIsCapped handling and new response/header/link behavior for capped totals. |
| packages/core/src/routes/log.ts | Accepts enableCap query param and forwards cap option + capped state to pagination. |
| packages/core/src/routes/hook.ts | Accepts enableCap on recent-logs and forwards cap option + capped state to pagination. |
| packages/core/src/routes/log.test.ts | Updates expectations for new countLogs signature and adds enableCap coverage. |
| packages/core/src/routes/hook.test.ts | Updates expectations for new countLogs signature. |
| packages/core/src/middleware/koa-pagination.test.ts | Adds tests for capped total signaling and link behavior. |
| packages/integration-tests/src/api/logs.ts | Adds getAuditLogsResponse helper to assert headers in integration tests. |
| packages/integration-tests/src/tests/api/audit-logs/count-cap.test.ts | Integration coverage for “no enableCap” vs “enableCap but unsaturated” header/link behavior. |
| .changeset/logs-enable-cap.md | Documents the new opt-in parameter and capped response signaling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
wangsijie
approved these changes
May 12, 2026
8ae79ba to
69a2b01
Compare
69a2b01 to
9d53bca
Compare
9d53bca to
ad81e2f
Compare
2b13d61 to
a178339
Compare
simeng-li
added a commit
that referenced
this pull request
May 13, 2026
…guard
Updates the OpenAPI specs for `GET /api/logs` and
`GET /api/hooks/{id}/recent-logs` to document the `enableCap` query
parameter, `Total-Number-Is-Capped` response header, and the capped-mode
`Link` rel behavior introduced in LOG-13415.
Also removes the `isDevFeaturesEnabled` gate on the Console Audit Logs
page so capped mode is the production default — the backend (#8796) and
console UI (#8799) have both validated.
Refs LOG-13434
gao-sun
approved these changes
May 13, 2026
simeng-li
added a commit
that referenced
this pull request
May 13, 2026
…guard
Updates the OpenAPI specs for `GET /api/logs` and
`GET /api/hooks/{id}/recent-logs` to document the `enableCap` query
parameter, `Total-Number-Is-Capped` response header, and the capped-mode
`Link` rel behavior introduced in LOG-13415.
Also removes the `isDevFeaturesEnabled` gate on the Console Audit Logs
page so capped mode is the production default — the backend (#8796) and
console UI (#8799) have both validated.
Refs LOG-13434
a178339 to
f0fcd56
Compare
simeng-li
added a commit
that referenced
this pull request
May 13, 2026
…guard
Updates the OpenAPI specs for `GET /api/logs` and
`GET /api/hooks/{id}/recent-logs` to document the `enableCap` query
parameter, `Total-Number-Is-Capped` response header, and the capped-mode
`Link` rel behavior introduced in LOG-13415.
Also removes the `isDevFeaturesEnabled` gate on the Console Audit Logs
page so capped mode is the production default — the backend (#8796) and
console UI (#8799) have both validated.
Refs LOG-13434
f0fcd56 to
d3169ec
Compare
simeng-li
added a commit
that referenced
this pull request
May 13, 2026
…guard
Updates the OpenAPI specs for `GET /api/logs` and
`GET /api/hooks/{id}/recent-logs` to document the `enableCap` query
parameter, `Total-Number-Is-Capped` response header, and the capped-mode
`Link` rel behavior introduced in LOG-13415.
Also removes the `isDevFeaturesEnabled` gate on the Console Audit Logs
page so capped mode is the production default — the backend (#8796) and
console UI (#8799) have both validated.
Refs LOG-13434
simeng-li
added a commit
that referenced
this pull request
May 13, 2026
…guard
Updates the OpenAPI specs for `GET /api/logs` and
`GET /api/hooks/{id}/recent-logs` to document the `enableCap` query
parameter, `Total-Number-Is-Capped` response header, and the capped-mode
`Link` rel behavior introduced in LOG-13415.
Also removes the `isDevFeaturesEnabled` gate on the Console Audit Logs
page so capped mode is the production default — the backend (#8796) and
console UI (#8799) have both validated.
Refs LOG-13434
d3169ec to
34903a6
Compare
simeng-li
added a commit
that referenced
this pull request
May 14, 2026
…guard (#8802) * docs(core): document enableCap contract; remove console dev-features guard Updates the OpenAPI specs for `GET /api/logs` and `GET /api/hooks/{id}/recent-logs` to document the `enableCap` query parameter, `Total-Number-Is-Capped` response header, and the capped-mode `Link` rel behavior introduced in LOG-13415. Also removes the `isDevFeaturesEnabled` gate on the Console Audit Logs page so capped mode is the production default — the backend (#8796) and console UI (#8799) have both validated. Refs LOG-13434 * chore: update changeset update changeset
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refs LOG-13415.
A customer reported that
GET /logson the Management API consistently times out with a SlonikStatementTimeoutError. Tracing the stack pointed atcountLogs(pool.one(...)), notfindLogs— so the timeout is inselect count(*) from logs ..., not the page fetch.Why it times out
For a tenant with millions of
logsrows,count(*)is unavoidably row-scanning on Postgres. Combined with thekey LIKE 'prefix%'OR-chain that the route applies by default, the planner falls back to bitmap heap scans that exceedstatement_timeout. The recently addedlogs__created_at_idindex helpsORDER BY created_at DESC LIMITinfindLogs, but does nothing forcount(*). The route also requirestotalCountto be set, so even a fast page fetch can't return until the slow count resolves.What changed
Per-request opt-in via a new
?enableCap=truequery param onGET /logsandGET /hooks/:id/recent-logs. Default behavior is byte-for-byte identical to current master.packages/core/src/queries/log.ts—countLogsnow accepts an optional{ capped?: boolean }option. Whencappedis true, it runsselect count(*) from (select 1 from logs <conds> limit 10001) sso Postgres halts at the cap; when false (or omitted), the legacy uncapped query is used. Both branches follow the canonicalpool.one<{ count: string }>+Number(...)pattern (matchesdatabase/row-count.ts). Return shape gains an optionalisCapped?: boolean.packages/core/src/middleware/koa-pagination.ts—Paginationtype gains an optionaltotalCountIsCapped?: boolean. When set, the middleware emits aTotal-Number-Is-Capped: trueresponse header and omits bothLink: rel="last"andLink: rel="next". The saturatedtotalCountmakes the derivedtotalPageunreliable, so neither count-derived rel can be answered honestly. Capped responses emit onlyfirstandprev.packages/core/src/routes/log.tsandroutes/hook.ts— acceptenableCapvia the zod guard, coerce withyes(...), pass{ capped: ... }tocountLogs, and propagateisCappedtoctx.pagination.totalCountIsCapped.Client navigation in capped mode
This matches the Stripe / cursor-style convention for unknown totals: when the server can't honestly answer "what's the last page" or "is there a next page", it doesn't emit those rels. Clients in capped mode walk forward by constructing
?page=N+1URLs themselves and stop on an empty response. This is the stable contract (documented in LOG-13434) — not a transitional state.Why per-request, not a global flag
Earlier draft of this PR used
EnvSet.values.isDevFeaturesEnabledas a global gate. A per-request query param is strictly better:Backward compatibility
GET /logsrequest (noenableCap): identical to current master. Same SQL behavior (now with the canonical{ count: string }typing applied to both branches), same{ count }destructure-compatible return shape, same headers, same Link rels.Paginationtype gains an optional field; existing paginated routes that don't set it are unaffected.countLogssignature gains an optionaloptionsparameter; both existing call sites in this PR are updated, no external callers.Reviewer notes
select 1 ... limit Nin a subquery so Postgres can stream and halt at N without sorting. NoORDER BY, no materialize node.Total-Number-Is-Cappedheader and thelast/nextrel omissions only fire when the handler setstotalCountIsCapped = true. Other paginated routes are unaffected because they don't set the field.countLogsreturningisCapped: true, middleware test settingtotalCountIsCapped = truedirectly). Integration coverage of the actual saturated SQL would require seeding > 10k rows per run; we deemed the unit coverage sufficient.Testing
Unit tests, integration tests
Checklist
.changeset