Sporalize-api / src /request-timing.ts
Shoraky's picture
Bound auth upstream waits and preserve sessions during outages
35243f1
Raw History Blame Contribute Delete
1.78 kB
import { AsyncLocalStorage } from "node:async_hooks";
import type { RequestHandler } from "express";
const timings = new AsyncLocalStorage<{ upstreamMs: number; calls: number }>();
/** Measures remote response-header wait, without logging tokens or query data. */
export const timedFetch: typeof fetch = async (input, init) => {
const timing = timings.getStore();
const started = performance.now();
try {
return await fetch(input, init);
} finally {
if (timing) {
timing.calls += 1;
timing.upstreamMs += performance.now() - started;
}
}
};
// Bound identity-service stalls so a shared token check cannot stay pending forever.
export const timedAuthFetch: typeof fetch = (input, init) => {
const deadline = AbortSignal.timeout(8_000);
const signal = init?.signal ?? (input instanceof Request ? input.signal : undefined);
return timedFetch(input, { ...init, signal: signal ? AbortSignal.any([signal, deadline]) : deadline });
};
export const requestTiming: RequestHandler = (req, res, next) => {
const timing = { upstreamMs: 0, calls: 0 };
const started = performance.now();
const json = res.json.bind(res);
res.json = (body) => {
res.setHeader("Server-Timing",
`app;dur=${(performance.now() - started).toFixed(1)}, supabase;dur=${timing.upstreamMs.toFixed(1)};desc="${timing.calls} upstream calls"`);
return json(body);
};
res.once("finish", () => {
const duration = performance.now() - started;
if (duration >= 1000) console.info("slow_request", {
method: req.method, route: req.route?.path ?? "(middleware)",
status: res.statusCode, durationMs: Math.round(duration),
upstreamMs: Math.round(timing.upstreamMs), upstreamCalls: timing.calls,
});
});
timings.run(timing, next);
};