ente/web/packages/next/log-web.ts

109 lines
3.4 KiB
TypeScript
Raw Normal View History

2024-04-08 12:30:47 +00:00
import { isDevBuild } from "@/next/env";
2024-04-09 06:31:02 +00:00
import log from "@/next/log";
2024-04-08 12:30:47 +00:00
/**
* Log a standard startup banner.
*
* This helps us identify app starts and other environment details in the logs.
*
* @param appId An identifier of the app that is starting.
* @param userId The uid for the currently logged in user, if any.
*/
export const logStartupBanner = (appId: string, userId?: number) => {
// TODO (MR): Remove the need to lowercase it, change the enum itself.
const appIdL = appId.toLowerCase();
const sha = process.env.GIT_SHA;
const buildId = isDevBuild ? "dev " : sha ? `git ${sha} ` : "";
2024-04-09 06:31:02 +00:00
log.info(`Starting ente-${appIdL}-web ${buildId}uid ${userId ?? 0}`);
2024-04-08 12:30:47 +00:00
};
/**
* Attach handlers to log any unhandled exceptions and promise rejections.
*
* @param attach If true, attach handlers, and if false, remove them. This
* allows us to use this in a React hook that cleans up after itself.
*/
export const logUnhandledErrorsAndRejections = (attach: boolean) => {
const handleError = (event: ErrorEvent) => {
log.error("Unhandled error", event.error);
};
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
log.error("Unhandled promise rejection", event.reason);
};
if (attach) {
window.addEventListener("error", handleError);
window.addEventListener("unhandledrejection", handleUnhandledRejection);
} else {
window.removeEventListener("error", handleError);
window.removeEventListener(
"unhandledrejection",
handleUnhandledRejection,
);
}
};
2024-04-08 12:30:47 +00:00
interface LogEntry {
timestamp: number;
logLine: string;
}
const lsKey = "logs";
/**
* Record {@link message} in a persistent log storage.
*
* These strings, alongwith associated timestamps, get added to a small ring
* buffer, whose contents can be later be retrieved by using {@link savedLogs}.
*
* This ring buffer is persisted in the browser's local storage.
*/
2024-04-08 14:30:24 +00:00
export const logToDisk = (message: string) => {
2024-04-08 12:30:47 +00:00
const maxCount = 1000;
const log: LogEntry = { logLine: message, timestamp: Date.now() };
try {
const logs = logEntries();
if (logs.length > maxCount) {
logs.slice(logs.length - maxCount);
}
logs.push(log);
2024-04-08 15:30:05 +00:00
localStorage.setItem(lsKey, JSON.stringify({ logs }));
2024-04-08 12:30:47 +00:00
} catch (e) {
console.error("Failed to persist log", e);
if (e instanceof Error && e.name === "QuotaExceededError") {
localStorage.removeItem(lsKey);
}
}
};
const logEntries = (): unknown[] => {
const s = localStorage.getItem("logs");
if (!s) return [];
const o: unknown = JSON.parse(s);
if (!(o && typeof o == "object" && "logs" in o && Array.isArray(o.logs))) {
console.error("Unexpected log entries obtained from local storage", o);
return [];
}
return o.logs;
};
/**
* Return a string containing all recently saved log messages.
*
* @see {@link persistLog}.
*/
export const savedLogs = () => logEntries().map(formatEntry).join("\n");
const formatEntry = (e: unknown) => {
if (e && typeof e == "object" && "timestamp" in e && "logLine" in e) {
const timestamp = e.timestamp;
const logLine = e.logLine;
if (typeof timestamp == "number" && typeof logLine == "string") {
return `[${new Date(timestamp).toISOString()}] ${logLine}`;
}
}
return String(e);
};