feat(telemetry): collections, universal server-fn coverage, beacon, client tracker
- activities indexes + client_events/client_errors/rollups/alerts collections - global function middleware via createStart; reads skipped on success - actor stash, denial dedup, control-flow error guards - api route logging, auth event overrides, beacon endpoint, web vitals - centralized admin role checks, shared skeletons, award-badges fix - extracted deriveRunState with tests
This commit is contained in:
+56
-31
@@ -1,12 +1,18 @@
|
||||
import { createMiddleware } from "@tanstack/react-start";
|
||||
import { getRequest } from "@tanstack/react-start/server";
|
||||
import { isRedirect } from "@tanstack/react-router";
|
||||
import { Logger } from "@/lib/logger";
|
||||
import { redactValue } from "@/lib/redact";
|
||||
import { redactArguments } from "@/lib/telemetry/redact-args";
|
||||
import {
|
||||
getRequestActor,
|
||||
markDeniedRecorded,
|
||||
wasDeniedRecorded,
|
||||
} from "@/lib/telemetry/request-context.server";
|
||||
import type { ActivityInput } from "@/lib/pocketbase/services/activities";
|
||||
|
||||
const logger = new Logger("Activities");
|
||||
|
||||
const recordActivity = (activity: ActivityInput) => {
|
||||
export const recordActivity = (activity: ActivityInput) => {
|
||||
import("@/lib/pocketbase/client")
|
||||
.then(async ({ pbAdmin }) => {
|
||||
await pbAdmin.authPromise;
|
||||
@@ -17,19 +23,6 @@ const recordActivity = (activity: ActivityInput) => {
|
||||
});
|
||||
};
|
||||
|
||||
const MAX_ARGUMENTS_CHARS = 2048;
|
||||
|
||||
const redactArguments = (data: unknown): unknown => {
|
||||
if (data === undefined) return undefined;
|
||||
|
||||
const redacted = redactValue(data);
|
||||
const serialized = JSON.stringify(redacted) ?? "";
|
||||
if (serialized.length > MAX_ARGUMENTS_CHARS) {
|
||||
return { truncated: serialized.slice(0, MAX_ARGUMENTS_CHARS) };
|
||||
}
|
||||
return redacted;
|
||||
};
|
||||
|
||||
const auditErrorMessage = (
|
||||
error?: { code?: string; userMessage?: string }
|
||||
): string => {
|
||||
@@ -48,11 +41,32 @@ 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 => {
|
||||
const raw = Number(process.env.TELEMETRY_READ_SAMPLE);
|
||||
return Number.isFinite(raw) && raw > 0 ? Math.min(raw, 1) : 0;
|
||||
};
|
||||
|
||||
const shouldRecordSuccess = (name: string): boolean => {
|
||||
if (!READ_FN_NAME.test(name)) return true;
|
||||
const sample = readSampleRate();
|
||||
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);
|
||||
|
||||
export const recordDeniedServerFn = (
|
||||
request: Request,
|
||||
actor?: Actor,
|
||||
meta?: ServerFnMeta
|
||||
) => {
|
||||
markDeniedRecorded(request);
|
||||
recordActivity({
|
||||
name: serverFnName(request, meta),
|
||||
player: actor?.player_id,
|
||||
@@ -69,10 +83,15 @@ export const serverFnLoggingMiddleware = createMiddleware({
|
||||
const request = getRequest();
|
||||
|
||||
const name = serverFnName(request, serverFnMeta);
|
||||
const playerId = (context as any)?.metadata?.player_id as string | undefined;
|
||||
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().
|
||||
const resolveActor = (): string | undefined =>
|
||||
getRequestActor(request)?.playerId ??
|
||||
((context as any)?.metadata?.player_id as string | undefined);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
@@ -86,29 +105,35 @@ export const serverFnLoggingMiddleware = createMiddleware({
|
||||
"success" in envelope &&
|
||||
(envelope as { success: boolean }).success === false;
|
||||
|
||||
recordActivity({
|
||||
name,
|
||||
player: playerId,
|
||||
duration,
|
||||
success: !failed,
|
||||
error: failed
|
||||
? auditErrorMessage(
|
||||
(envelope as { error?: { code?: string; userMessage?: string } })
|
||||
.error
|
||||
)
|
||||
: undefined,
|
||||
arguments: safeArgs,
|
||||
user_agent: userAgent,
|
||||
});
|
||||
if (failed || shouldRecordSuccess(name)) {
|
||||
recordActivity({
|
||||
name,
|
||||
player: resolveActor(),
|
||||
duration,
|
||||
success: !failed,
|
||||
error: failed
|
||||
? auditErrorMessage(
|
||||
(envelope as { error?: { code?: string; userMessage?: string } })
|
||||
.error
|
||||
)
|
||||
: undefined,
|
||||
arguments: safeArgs,
|
||||
user_agent: userAgent,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (isControlFlowError(error) || wasDeniedRecorded(request)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
recordActivity({
|
||||
name,
|
||||
player: playerId,
|
||||
player: resolveActor(),
|
||||
duration,
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
|
||||
Reference in New Issue
Block a user