File size: 1,782 Bytes
82b44e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35243f1
 
 
 
 
 
 
82b44e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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);
};