Request logs
Every option under logger.requests, the order the filters run in, which level a request lands at, and the exact shape of the record.
The options
logger.requests takes true, false, or an object. Leaving it unset behaves
like true, which behaves like {}. false turns access logging off entirely.
| Key | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | Set false to stop the lines. Ids and metrics survive. |
logSuccessfulRequests | boolean | true | Set false to drop lines under 400 that are not slow. |
slowThresholdMs | number | 1000 | At or above this a request is marked slow. |
ignorePaths | (string | RegExp)[] | [] | Drop lines under 400 whose path matches. |
ignore | (meta) => boolean | unset | Your own filter. Return true to drop any line. |
logger: {
requests: {
slowThresholdMs: 1000,
logSuccessfulRequests: false,
ignorePaths: ["/api/health", "/api/health/live"],
ignore: (meta) => meta.path.startsWith("/api/internal/"),
},
}The order the filters run in
Four checks, in this order. The first one that matches drops the line.
enabledis false.ignore(meta)returns true.- The status is under 400 and
ignorePathsmatches. - The status is under 400, the request was not slow, and
logSuccessfulRequestsis false.
The checks never interact. A line is dropped when any of the four matches. Order
only decides how early the work stops. It does mean ignore runs on every
request while logging is on.
Two things follow from the conditions themselves.
ignore drops errors as well. It is the only check without a status
condition. A 500 matching your ignore function is never logged. The other
three only ever suppress successes, so a failing health check still writes a
line.
ignorePaths does not spare slow requests. Check 3 never looks at slow,
so a successful but slow request on an ignored path is dropped too. Use ignore
with your own condition if you want slow probes visible.
ignorePaths matches the full pathname
Including the base path. Under a handler mounted at /api the value is
/api/health, not /health. A string has to match exactly. Pass a RegExp
when it should not.
Which level a request lands at
| Status | Level |
|---|---|
| 500 and up | error |
| 400 to 499 | warn |
| Under 400 and slow | warn |
| Everything else | info |
slow is set when durationMs is at or above slowThresholdMs. That duration
covers the whole dispatch, from parsing the URL to holding a finished response.
Route matching, session resolution and your handler are all inside it.
The record
The message is always HTTP request completed. The fields sit beside it.
| Field | Type | Present |
|---|---|---|
event | string | Always, as http.request |
requestId | string | Always |
traceId | string | Always |
method | string | Always |
path | string | Always, the full pathname |
status | number | Always |
durationMs | number | Always, rounded to 2 places |
slow | boolean | Always |
route | string | When a route matched |
error | object | When something threw |
route is the matched pattern with the base path stripped and no leading slash.
It is absent when routing found nothing. A 404 raised inside a handler still
carries it, because a route did match.
Watch what that means for collections. Every collection is served by one
parameterized route, so a request to /api/posts/42 logs path as
/api/posts/42 and route as :collection/:id. All your collections share
that pattern. Group on path when you need them apart.
error carries name and message, and nothing else. It appears only when a
handler threw. A handler that returns a 500 rather than raising still logs at
error level, but with no error object beside it.
Records written inside a request also pick up trace_id and span_id from the
open span, once an observability adapter is wired. See
Instrumented seams.
Overriding per handler
createFetchHandler takes the same shape under requestLogging. It replaces
the config value outright rather than merging into it, so a partial object there
drops back to defaults for everything you left out.
const handler = createFetchHandler(questpie, {
basePath: "/api",
requestLogging: { logSuccessfulRequests: false },
});That is the seam to use when one process should log differently from another, such as a worker beside an app.
Turning it off without losing anything
enabled: false stops the lines. It stops nothing else.
The request id and trace id are still derived, still stamped on the span, and
still returned as x-request-id and x-trace-id. The
http.server.request.duration histogram is still recorded. So an app whose
platform already writes access logs can drop QUESTPIE's without going blind.
The connection budget
PostgreSQL caps total connections and every process you run takes a slice. Who opens what, how to count it, and what a pooler in front changes.
Client
One typed client, created once, carrying the types your schema produced. Reads and writes, live queries, your own events, and ready-made query options for React.