Files
flxn-app/src/lib/telemetry/rollup.server.ts
T
kyle 1ddcffc511 fix(telemetry): review findings
- wau/mau trailing scans run hourly and on day finalization, not every tick
- exclusion lookup failures cached 30s so an st outage cannot amplify
- unverified relation filter dropped; player emptiness checked in js
- anonymous unauthenticated read failures no longer logged
- alerts and exclusions ensure supertokens init on cold pods
2026-08-25 23:26:34 -07:00

111 lines
3.5 KiB
TypeScript

import { pbAdmin } from "@/lib/pocketbase/client";
import { Logger } from "@/lib/logger";
import {
accumulateActivities,
accumulateClientErrors,
accumulateClientEvents,
buildDayRollups,
newActivityAccumulator,
newClientErrorAccumulator,
newClientEventAccumulator,
type ActivityRow,
type ClientErrorRow,
type ClientEventRow,
} from "./rollup-core";
const logger = new Logger("Telemetry > Rollup");
export const utcDay = (date: Date): string => date.toISOString().slice(0, 10);
const addDays = (dateStr: string, days: number): string => {
const d = new Date(`${dateStr}T00:00:00.000Z`);
d.setUTCDate(d.getUTCDate() + days);
return utcDay(d);
};
const bound = (dateStr: string): string => `${dateStr} 00:00:00.000Z`;
const dayFilter = (dateStr: string): string =>
`created >= '${bound(dateStr)}' && created < '${bound(addDays(dateStr, 1))}'`;
const distinctPlayersInWindow = async (fromDate: string, toDateExclusive: string): Promise<number> => {
const players = new Set<string>();
const filter = `created >= '${bound(fromDate)}' && created < '${bound(toDateExclusive)}'`;
await pbAdmin.pageCollection<{ player?: string }>("activities", { filter, fields: "player,created" }, (rows) => {
for (const row of rows) if (row.player) players.add(row.player);
});
await pbAdmin.pageCollection<{ player?: string }>("client_events", { filter, fields: "player,created" }, (rows) => {
for (const row of rows) if (row.player) players.add(row.player);
});
return players.size;
};
export const runRollups = async (
dateStr: string,
opts: { trailing?: boolean } = {}
): Promise<number> => {
await pbAdmin.authPromise;
const activities = newActivityAccumulator();
const clientEvents = newClientEventAccumulator();
const clientErrors = newClientErrorAccumulator();
await pbAdmin.pageCollection<ActivityRow>(
"activities",
{ filter: dayFilter(dateStr), fields: "name,player,duration,success,error,created" },
(rows) => accumulateActivities(activities, rows)
);
await pbAdmin.pageCollection<ClientEventRow>(
"client_events",
{ filter: dayFilter(dateStr), fields: "name,player,session_id,route_id,value,created" },
(rows) => accumulateClientEvents(clientEvents, rows)
);
await pbAdmin.pageCollection<ClientErrorRow>(
"client_errors",
{ filter: dayFilter(dateStr), fields: "group_hash,created" },
(rows) => accumulateClientErrors(clientErrors, rows)
);
// Trailing scans re-read the full 7/30-day windows; hourly at most.
let wau: number | undefined;
let mau: number | undefined;
if (opts.trailing !== false) {
const nextDay = addDays(dateStr, 1);
[wau, mau] = await Promise.all([
distinctPlayersInWindow(addDays(dateStr, -6), nextDay),
distinctPlayersInWindow(addDays(dateStr, -29), nextDay),
]);
}
const partial = dateStr >= utcDay(new Date());
const rollups = buildDayRollups(dateStr, {
activities,
clientEvents,
clientErrors,
wau,
mau,
partial,
});
for (const rollup of rollups) {
await pbAdmin.upsertRollup(rollup);
}
logger.info(`Rolled up ${dateStr}: ${rollups.length} metrics${partial ? " (partial)" : ""}`);
return rollups.length;
};
export const runScheduledRollups = async (
lastRunDay: string | undefined,
opts: { trailing?: boolean } = {}
): Promise<string> => {
const today = utcDay(new Date());
if (lastRunDay && lastRunDay !== today) {
await runRollups(lastRunDay, { trailing: true });
}
await runRollups(today, opts);
return today;
};