/** * The one line a request writes (SPEC.md §88). * * The logger already attaches the request id, the actor and the source from the * ambient context (SPEC.md §87), so what is left is the four things only this layer * knows: the method, the route, the status and how long it took. * * What is deliberately not here is the URL, the query string, the body and the * headers. A log line is written to a disk, shipped to whoever collects logs and * pasted into tickets, and each of those four carries something that has no business * going there — an id in a path, a search term, a password on its way to be compared, * a session cookie, a bearer token. The route's own path says everything a reader can * act on, and says it in the one form that aggregates (SPEC.md §85). */ import type { Logger, LogLevel } from '@assemora/core'; /** How the line is tuned. `requestLog: false` is how an application has none. */ export type RequestLogOptions = { /** * How long a request may take, in milliseconds, before its line is a warning * rather than information. * * The point of it is that a log nobody reads still surfaces the line worth * reading: an application that logs every request at one level has, in practice, * logged nothing. */ readonly slowMs?: number; }; /** * A second. * * SPEC.md §89 budgets 100ms for a read and 150ms for a mutation at p95, so nothing * inside the performance envelope trips this — and it sits far enough above that * envelope that a cold first request, a large upload or a streamed file does not trip * it either. A threshold that fires on ordinary traffic is a threshold somebody turns * off, and then the slow request nobody was told about is the one that matters. */ export declare const SLOW_REQUEST_MS = 1000; /** One finished request, as much of it as may be written down. */ export type ServedRequest = { readonly method: string; /** * The route's path as it is served — `/api/articles/:id`, never * `/api/articles/8f3a…`. A log keyed by URL cannot be aggregated and quietly * records ids. * * Absent when nothing matched: there is no route to name then, and the URL that * missed is not a substitute for one. */ readonly path?: string; readonly status: number; readonly durationMs: number; /** A file from `mountAssets`, rather than an endpoint. */ readonly asset?: boolean; /** * The status is 5xx and nothing failed: the endpoint answered with it. * * Read from `isIncident`, so the question of whose failure a status is stays decided * in one place — the error model — rather than here and there. `/api/ready` is the * case that exists: it refuses with a 503 because that is what a load balancer reads, * and a readiness probe answering "not yet" every few seconds for as long as a * deployment is unfinished is the endpoint working, not an incident. */ readonly expected?: boolean; }; /** * How loudly the line is written, or `undefined` for not at all. * * The rungs are the ones every access log already uses, so nobody has to learn a new * convention: a failure the server owns is an error, a request the caller was refused * is a warning, and everything else is information — raised to a warning when it took * too long. A 5xx the endpoint meant to answer with is a refusal rather than a failure, * and is written as one (see `ServedRequest.expected`). */ export declare const requestLogLevel: (served: ServedRequest, slowMs: number) => LogLevel | undefined; export declare const logRequest: (logger: Logger, served: ServedRequest, slowMs: number) => void; //# sourceMappingURL=request-log.d.ts.map