Files
elysium/apps/web/src/utils/logger.ts
T
hikari de5570b5fc
CI / Lint, Build & Test (push) Successful in 1m14s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m56s
fix: filter third-party script errors from frontend telemetry
2026-04-01 13:55:40 -07:00

81 lines
2.8 KiB
TypeScript

/**
* @file Frontend logger that forwards console output to the backend telemetry service.
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable no-console -- This file intentionally overrides console methods */
type Level = "debug" | "info" | "warn";
const post = (path: string, body: object): void => {
void fetch(path, {
body: JSON.stringify(body),
// eslint-disable-next-line @typescript-eslint/naming-convention -- HTTP header names use kebab-case
headers: { "Content-Type": "application/json" },
method: "POST",
}).catch(() => {
// Intentionally swallowed — we cannot log logger failures without infinite recursion.
});
};
/**
* Overrides the global console.log and console.error methods so that all
* frontend log output is forwarded to the backend telemetry endpoints.
* Must be called once at application startup before any other code runs.
*/
const initialiseFrontendLogger = (): void => {
const originalLog = console.log.bind(console);
const originalError = console.error.bind(console);
console.log = (...consoleArguments: Array<unknown>): void => {
originalLog(...consoleArguments);
const level: Level = "info";
const message = consoleArguments.map((argument) => {
return typeof argument === "string"
? argument
: JSON.stringify(argument);
}).join(" ");
post("/api/fe/log", { level, message });
};
console.error = (...consoleArguments: Array<unknown>): void => {
originalError(...consoleArguments);
const message = consoleArguments.map((argument) => {
if (argument instanceof Error) {
return `${argument.message}\n${argument.stack ?? ""}`;
}
return typeof argument === "string"
? argument
: JSON.stringify(argument);
}).join(" ");
/*
* Ignore errors originating entirely from third-party scripts (e.g. AdSense).
* Stack frames from our own code reference elysium.nhcarrigan.com or localhost;
* if none are present but external URLs are, the error is not actionable.
*/
const hasExternalUrl = (/https?:\/\//u).test(message);
const hasOurDomain = message.includes("elysium.nhcarrigan.com");
const hasOwnFrame = hasOurDomain || message.includes("localhost");
if (hasExternalUrl && !hasOwnFrame) {
return;
}
const context = "console.error";
post("/api/fe/error", { context, message });
};
console.warn = (...consoleArguments: Array<unknown>): void => {
originalLog(...consoleArguments);
const level: Level = "warn";
const message = consoleArguments.map((argument) => {
return typeof argument === "string"
? argument
: JSON.stringify(argument);
}).join(" ");
post("/api/fe/log", { level, message });
};
};
export { initialiseFrontendLogger };