fix(logs): the browser's copy of a polled read is quiet too

The server now logs polled reads at DEBUG, and the browser's copy of each
request line promises the server's level: at INFO the unread-count polls
filled My log within minutes and, at Detailed, the shipped log. The server
marks a line it logged at DEBUG with X-Log-Quiet, so the polled-route list
stays in one place.
This commit is contained in:
2026-09-19 05:03:31 +02:00
parent 6bbf532b70
commit 6cb8231a9f
5 changed files with 40 additions and 10 deletions
+15 -6
View File
@@ -12,6 +12,9 @@ import { log, newRequestId, type Level } from "./log";
* Grouping by it is what makes a client line comparable, and it is safe by construction: a token in
* the path (`/api/public/watch/{token}`) arrives already replaced by its parameter name. */
export const ROUTE_HEADER = "X-Route";
/** Set when the server logged this request at DEBUG (a polled read, the log console's own reads): the
* browser's copy of the line goes to DEBUG too, from the server's one list (`logs._POLLED_ROUTES`). */
export const QUIET_HEADER = "X-Log-Quiet";
export const REQUEST_ID_HEADER = "X-Request-Id";
// A static path word: kept. Anything else (an id, a channel id, a share token) becomes `{}`.
@@ -29,9 +32,9 @@ export function pathTemplate(url: string): string {
}
/** Level of a request line — the server's own rule (`logs.log_requests`), so both sides agree on
* what a 404 is. */
function statusLevel(status: number): Level {
return status >= 500 ? "ERROR" : status >= 400 ? "WARNING" : "INFO";
* what a 404 is, and on which successes are too routine for INFO (`quiet`, the server's say). */
function statusLevel(status: number, quiet: boolean): Level {
return status >= 500 ? "ERROR" : status >= 400 ? "WARNING" : quiet ? "DEBUG" : "INFO";
}
/** Record one request's outcome. `route` is the server's template when it sent one. */
@@ -40,7 +43,9 @@ export function logRequest(
method: string,
url: string,
started: number,
outcome: { status: number; route?: string | null } | { failed: "network" | "aborted" },
outcome:
| { status: number; route?: string | null; quiet?: boolean }
| { failed: "network" | "aborted" },
extra: Record<string, unknown> = {},
): void {
const duration_ms = Math.round((performance.now() - started) * 10) / 10;
@@ -56,7 +61,7 @@ export function logRequest(
}
const path = outcome.route || pathTemplate(url);
log.request(
statusLevel(outcome.status),
statusLevel(outcome.status, outcome.quiet === true),
`${method} ${path} ${outcome.status}`,
{ method, path, status: outcome.status, duration_ms, ...extra },
requestId,
@@ -92,7 +97,11 @@ export async function send(
method,
url,
started,
{ status: r.status, route: headerOf(r, ROUTE_HEADER) },
{
status: r.status,
route: headerOf(r, ROUTE_HEADER),
quiet: headerOf(r, QUIET_HEADER) === "1",
},
extra,
);
return r;