Development #24
@@ -9,8 +9,7 @@ export const Route = createFileRoute("/api/health")({
|
||||
ensureTelemetryScheduler();
|
||||
const snapshot = await getHealthSnapshot();
|
||||
|
||||
// Always 200: a PB/SuperTokens outage must not fail the k8s probes;
|
||||
// restarting the app pod cannot fix a dependency.
|
||||
// Always 200: a dependency outage must not fail the k8s probes.
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
status: snapshot.status,
|
||||
|
||||
@@ -45,8 +45,6 @@ const writeEvents = async (
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
await pbAdmin.authPromise;
|
||||
|
||||
// Sequential on purpose: SQLite is single-writer; a burst of parallel
|
||||
// creates just queues behind the lock.
|
||||
for (const event of events) {
|
||||
try {
|
||||
if (event.kind === "error") {
|
||||
|
||||
@@ -5,8 +5,6 @@ const csrfMiddleware = createCsrfMiddleware({
|
||||
filter: (ctx) => ctx.handlerType === "serverFn",
|
||||
});
|
||||
|
||||
// Global function middleware: every server fn (current and future) is audited
|
||||
// without opting in at the definition site.
|
||||
export const startInstance = createStart(() => ({
|
||||
requestMiddleware: [csrfMiddleware],
|
||||
functionMiddleware: [serverFnLoggingMiddleware],
|
||||
|
||||
@@ -10,7 +10,6 @@ interface QuietBoundaryState {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
// One failing telemetry card must not blank the whole dashboard.
|
||||
export class QuietBoundary extends Component<QuietBoundaryProps, QuietBoundaryState> {
|
||||
state: QuietBoundaryState = { hasError: false };
|
||||
|
||||
|
||||
@@ -99,8 +99,6 @@ export const getClientErrorGroups = createServerFn()
|
||||
const sample = latest.items[0];
|
||||
if (!sample) continue;
|
||||
|
||||
// A group counts as resolved when its most recent occurrence is
|
||||
// resolved; a recurrence arrives resolved=false and surfaces again.
|
||||
const resolved = !!sample.resolved;
|
||||
if (resolved && !includeResolved) continue;
|
||||
|
||||
@@ -161,8 +159,6 @@ export const getTelemetryRuntimeStatus = createServerFn()
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async () =>
|
||||
toServerResult<TelemetryRuntimeStatus>(async () => {
|
||||
// Dev has no k8s probes hitting /api/health, so opening the dashboard
|
||||
// is the scheduler's fallback starter.
|
||||
ensureTelemetryScheduler();
|
||||
const health = await getHealthSnapshot();
|
||||
return {
|
||||
|
||||
@@ -199,8 +199,7 @@ export function createTelemetryService(pb: PocketBase) {
|
||||
};
|
||||
},
|
||||
|
||||
// Idempotent by the (date, metric, dim) unique index; dim must be "" (never
|
||||
// null/undefined) or the index stops deduplicating.
|
||||
// dim must be "" (never null/undefined) or the unique index stops deduplicating.
|
||||
async upsertRollup(input: RollupInput): Promise<RollupRecord> {
|
||||
const data = { ...input, dim: input.dim ?? "" };
|
||||
const filter = pbFilter(pb, "date = {:date} && metric = {:metric} && dim = {:dim}", {
|
||||
@@ -277,8 +276,6 @@ export function createTelemetryService(pb: PocketBase) {
|
||||
};
|
||||
},
|
||||
|
||||
// Bounded flip of a group's rows; new occurrences arrive resolved=false by
|
||||
// design, so a partial flip on a huge group is acceptable.
|
||||
async resolveErrorsByGroup(groupHash: string, resolved = true): Promise<number> {
|
||||
const MAX_PAGES = 25;
|
||||
let updated = 0;
|
||||
@@ -300,8 +297,6 @@ export function createTelemetryService(pb: PocketBase) {
|
||||
return updated;
|
||||
},
|
||||
|
||||
// Paged scan over a base collection (never SQL views). Stable created sort;
|
||||
// tight `fields` keeps payloads small for rollup passes.
|
||||
async pageCollection<T>(
|
||||
collection: string,
|
||||
opts: { filter?: string; fields?: string },
|
||||
|
||||
@@ -2,9 +2,6 @@ import { createMiddleware } from "@tanstack/react-start";
|
||||
import { recordActivity } from "@/utils/activities";
|
||||
import { getRequestActor } from "./request-context.server";
|
||||
|
||||
// Request-type counterpart of serverFnLoggingMiddleware for createFileRoute
|
||||
// API handlers, which the global function middleware never sees. Not for the
|
||||
// SSE route (a long-lived stream is not a request-shaped unit) or /api/health.
|
||||
export const apiLoggingMiddleware = createMiddleware({
|
||||
type: "request",
|
||||
}).server(async ({ next, request }) => {
|
||||
|
||||
@@ -9,8 +9,6 @@ interface AuthEventInput {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// Fire-and-forget: auth flows must never fail or slow down because telemetry
|
||||
// did. The phone number is used only to resolve the player, never stored.
|
||||
export const recordAuthEvent = (name: string, input: AuthEventInput) => {
|
||||
void (async () => {
|
||||
try {
|
||||
@@ -52,8 +50,6 @@ export const recordAuthEvent = (name: string, input: AuthEventInput) => {
|
||||
|
||||
type PasswordlessOverride = (originalImplementation: any) => any;
|
||||
|
||||
// Composes with a recipe's existing override.functions so telemetry wraps the
|
||||
// final implementation (Twilio verify included).
|
||||
export const withPasswordlessTelemetry =
|
||||
(existing?: PasswordlessOverride): PasswordlessOverride =>
|
||||
(originalImplementation: any) => {
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import type { AnyRouter } from "@tanstack/react-router";
|
||||
|
||||
// Provider-free by design: renders nothing and mounts no React context, so it
|
||||
// cannot disturb the LinguiProvider -> AuthProvider order. Browser-only; every
|
||||
// entry point is a no-op during SSR. Runs in dev too - events go to the same
|
||||
// PB the app is pointed at, which in dev is the local instance.
|
||||
|
||||
type QueuedEvent = Record<string, unknown> & { kind: "event" | "error" | "vital" };
|
||||
|
||||
const FLUSH_INTERVAL_MS = 10_000;
|
||||
@@ -148,8 +143,7 @@ export const initTelemetry = (router: AnyRouter) => {
|
||||
captureError("unhandledrejection", event.reason);
|
||||
});
|
||||
|
||||
// Vitals mostly report at hidden-visibility; their callbacks are registered
|
||||
// before the flush listeners so those reports make the final beacon.
|
||||
// Vitals report at hidden-visibility; register before the flush listeners below.
|
||||
void import("web-vitals")
|
||||
.then(({ onLCP, onCLS, onINP, onFCP, onTTFB }) => {
|
||||
const report = (metric: { name: string; value: number; rating: string }) => {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Deterministic client-error grouping across deploys: volatile fragments
|
||||
// (ids, URLs, asset content hashes) are normalized away before hashing.
|
||||
|
||||
const normalizeMessage = (message: string): string =>
|
||||
message
|
||||
.toLowerCase()
|
||||
@@ -13,8 +10,6 @@ const normalizeMessage = (message: string): string =>
|
||||
|
||||
const FRAME_NOISE = /^(at\s+)?(async\s+)?(module code|<anonymous>|eval)/i;
|
||||
|
||||
// Vite emits content-hashed asset names (index-Bq2z1a3f.js); strip the hash so
|
||||
// the same frame groups across deploys.
|
||||
const normalizeFrame = (frame: string): string =>
|
||||
frame
|
||||
.trim()
|
||||
|
||||
@@ -41,8 +41,6 @@ const runChecks = async (): Promise<HealthSnapshot> => {
|
||||
return cached;
|
||||
};
|
||||
|
||||
// Stale-while-revalidate: k8s probes get the cached snapshot instantly while a
|
||||
// refresh runs in the background; only the very first request awaits a check.
|
||||
export const getHealthSnapshot = async (): Promise<HealthSnapshot> => {
|
||||
if (cached && Date.now() - Date.parse(cached.checkedAt) < CHECK_TTL_MS) {
|
||||
return cached;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Per-request side-channel between the session layer and the global logging
|
||||
// middleware, which runs outside session middleware and cannot see its context.
|
||||
|
||||
const actorByRequest = new WeakMap<Request, { playerId?: string }>();
|
||||
const deniedRecorded = new WeakSet<Request>();
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ export interface ClientErrorRow {
|
||||
group_hash: string;
|
||||
}
|
||||
|
||||
// Nearest-rank percentile; values need not be pre-sorted.
|
||||
export const percentile = (values: number[], p: number): number => {
|
||||
if (values.length === 0) return 0;
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
|
||||
@@ -23,8 +23,6 @@ const addDays = (dateStr: string, days: number): string => {
|
||||
return utcDay(d);
|
||||
};
|
||||
|
||||
// PB stores autodates as "YYYY-MM-DD HH:MM:SS.sssZ" UTC; boundaries in the
|
||||
// same shape compare correctly in filters.
|
||||
const bound = (dateStr: string): string => `${dateStr} 00:00:00.000Z`;
|
||||
|
||||
const dayFilter = (dateStr: string): string =>
|
||||
@@ -91,8 +89,6 @@ export const runRollups = async (dateStr: string): Promise<number> => {
|
||||
return rollups.length;
|
||||
};
|
||||
|
||||
// The first run after UTC midnight recomputes yesterday once more so its
|
||||
// partial rows become final. Idempotent via the (date, metric, dim) key.
|
||||
export const runScheduledRollups = async (lastRunDay?: string): Promise<string> => {
|
||||
const today = utcDay(new Date());
|
||||
if (lastRunDay && lastRunDay !== today) {
|
||||
|
||||
@@ -52,9 +52,7 @@ const tickAlerts = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Single-replica deployment makes in-process intervals a valid scheduler
|
||||
// (same precedent as the badge migration job). Started lazily from request
|
||||
// handlers (health probes guarantee a call within seconds of boot).
|
||||
// In-process intervals are valid only while the deployment runs a single replica.
|
||||
export const ensureTelemetryScheduler = () => {
|
||||
if (initialized || typeof window !== "undefined") return;
|
||||
initialized = true;
|
||||
|
||||
@@ -41,8 +41,6 @@ const serverFnName = (request: Request, meta?: ServerFnMeta): string => {
|
||||
return pathParts[pathParts.length - 1] || "unknown";
|
||||
};
|
||||
|
||||
// Successful reads are skipped (client page views cover usage); read failures
|
||||
// are always recorded. Naming is uniform across every feature server.ts.
|
||||
export const READ_FN_NAME = /^(get|list|search|fetch|preview|is[A-Z])/;
|
||||
|
||||
const readSampleRate = (): number => {
|
||||
@@ -56,8 +54,6 @@ const shouldRecordSuccess = (name: string): boolean => {
|
||||
return sample > 0 && Math.random() < sample;
|
||||
};
|
||||
|
||||
// Thrown Responses/redirects are control flow (session refresh 401s, route
|
||||
// redirects), not failures.
|
||||
const isControlFlowError = (error: unknown): boolean =>
|
||||
error instanceof Response || isRedirect(error);
|
||||
|
||||
@@ -86,8 +82,7 @@ export const serverFnLoggingMiddleware = createMiddleware({
|
||||
const userAgent = request.headers.get("user-agent") || undefined;
|
||||
const safeArgs = redactArguments(data);
|
||||
|
||||
// Registered globally, so session middleware runs inside this one; the actor
|
||||
// is stashed on the Request by getSessionContextCore and read after next().
|
||||
// Runs outside session middleware; the actor is stashed on the Request and read after next().
|
||||
const resolveActor = (): string | undefined =>
|
||||
getRequestActor(request)?.playerId ??
|
||||
((context as any)?.metadata?.player_id as string | undefined);
|
||||
|
||||
@@ -26,9 +26,6 @@ export const superTokensRequestMiddleware = createMiddleware({
|
||||
return next({ context });
|
||||
});
|
||||
|
||||
// Session context when available, anonymous otherwise — never throws or
|
||||
// redirects. For endpoints that must accept logged-out traffic (telemetry
|
||||
// beacon: login-page errors are exactly the signal we want).
|
||||
export const superTokensOptionalRequestMiddleware = createMiddleware({
|
||||
type: "request",
|
||||
}).server(async ({ next, request }) => {
|
||||
|
||||
Reference in New Issue
Block a user