feat: frontend LOG_LEVEL support via logger utility (#209)

- Inject LOG_LEVEL at build time via Vite define (__LOG_LEVEL__, default: warn)
- Create frontend logger utility (frontend/src/utils/logger.ts) mirroring backend API
- Replace all console.error calls with log.error in MedicationsPage, AppContext, Auth
- Supports levels: silent > error > warn > info > debug

Closes #205
This commit is contained in:
Daniel Volz
2026-02-14 20:28:06 +01:00
committed by GitHub
parent cbc71822b0
commit a016e45ef2
5 changed files with 48 additions and 5 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
import { createContext, type ReactNode, useCallback, useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { log } from "../utils/logger";
import { ConfirmModal } from "./ConfirmModal";
import { PasswordInput } from "./PasswordInput";
@@ -109,7 +110,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
setLoading(false);
} catch (err) {
console.error(`Failed to fetch auth state (attempt ${retryCount + 1}/${maxRetries + 1}):`, err);
log.error(`Failed to fetch auth state (attempt ${retryCount + 1}/${maxRetries + 1}):`, err);
// Retry on connection errors or 5xx errors (server might be restarting)
if (retryCount < maxRetries) {
+4 -3
View File
@@ -5,6 +5,7 @@ import { useAuth } from "../components/Auth";
import { useCollapsedDays, useDoses, useMedications, useRefill, useSettings, useShare } from "../hooks";
import type { Coverage, Medication, ScheduleEvent, StockThresholds } from "../types";
import { getSystemLocale } from "../utils/formatters";
import { log } from "../utils/logger";
import { buildSchedulePreview, calculateCoverage, computeMissedPastDoseIds, isDoseDismissed } from "../utils/schedule";
// =============================================================================
@@ -514,7 +515,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (err) {
console.error("Export error:", err);
log.error("Export error:", err);
}
setExporting(false);
},
@@ -568,7 +569,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
try {
data = text ? JSON.parse(text) : {};
} catch {
console.error("Import response parse error:", text);
log.error("Import response parse error:", text);
alert(`${t("exportImport.importError")}: Server returned invalid response`);
return;
}
@@ -590,7 +591,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
settingsHook.loadSettings();
doses.loadTakenDoses();
} catch (err) {
console.error("Import error:", err);
log.error("Import error:", err);
alert(t("exportImport.importError"));
}
+2 -1
View File
@@ -6,6 +6,7 @@ import { useMedicationForm, useUnsavedChangesWarning } from "../hooks";
import type { DoseUnit, Medication } from "../types";
import { DOSE_UNITS, FIELD_LIMITS, getPackageSize } from "../types";
import { combineDateAndTime, formatDateTime, formatNumber } from "../utils/formatters";
import { log } from "../utils/logger";
export function MedicationsPage() {
const { t } = useTranslation();
@@ -299,7 +300,7 @@ export function MedicationsPage() {
setOriginalForm(form);
}
} catch (err) {
console.error("Save error:", err);
log.error("Save error:", err);
alert(err instanceof Error && err.message ? err.message : t("common.saveFailed"));
}
+39
View File
@@ -0,0 +1,39 @@
/**
* Frontend logger that respects LOG_LEVEL (injected at build time via Vite define).
* Mirrors the backend logger API so usage is consistent across the stack.
*
* Levels (high → low): silent > error > warn > info > debug
* Default: "warn" — only warn and error messages are shown.
*/
declare const __LOG_LEVEL__: string;
const LOG_LEVELS: Record<string, number> = {
silent: 50,
error: 40,
warn: 30,
info: 20,
debug: 10,
};
const configuredLevel = typeof __LOG_LEVEL__ !== "undefined" ? __LOG_LEVEL__.toLowerCase() : "warn";
const threshold = LOG_LEVELS[configuredLevel] ?? LOG_LEVELS.warn;
function shouldLog(level: string): boolean {
return (LOG_LEVELS[level] ?? 0) >= threshold;
}
export const log = {
debug(...args: unknown[]): void {
if (shouldLog("debug")) console.debug(...args);
},
info(...args: unknown[]): void {
if (shouldLog("info")) console.info(...args);
},
warn(...args: unknown[]): void {
if (shouldLog("warn")) console.warn(...args);
},
error(...args: unknown[]): void {
if (shouldLog("error")) console.error(...args);
},
};