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 { createContext, type ReactNode, useCallback, useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { log } from "../utils/logger";
import { ConfirmModal } from "./ConfirmModal"; import { ConfirmModal } from "./ConfirmModal";
import { PasswordInput } from "./PasswordInput"; import { PasswordInput } from "./PasswordInput";
@@ -109,7 +110,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
} }
setLoading(false); setLoading(false);
} catch (err) { } 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) // Retry on connection errors or 5xx errors (server might be restarting)
if (retryCount < maxRetries) { 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 { useCollapsedDays, useDoses, useMedications, useRefill, useSettings, useShare } from "../hooks";
import type { Coverage, Medication, ScheduleEvent, StockThresholds } from "../types"; import type { Coverage, Medication, ScheduleEvent, StockThresholds } from "../types";
import { getSystemLocale } from "../utils/formatters"; import { getSystemLocale } from "../utils/formatters";
import { log } from "../utils/logger";
import { buildSchedulePreview, calculateCoverage, computeMissedPastDoseIds, isDoseDismissed } from "../utils/schedule"; import { buildSchedulePreview, calculateCoverage, computeMissedPastDoseIds, isDoseDismissed } from "../utils/schedule";
// ============================================================================= // =============================================================================
@@ -514,7 +515,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} catch (err) { } catch (err) {
console.error("Export error:", err); log.error("Export error:", err);
} }
setExporting(false); setExporting(false);
}, },
@@ -568,7 +569,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
try { try {
data = text ? JSON.parse(text) : {}; data = text ? JSON.parse(text) : {};
} catch { } catch {
console.error("Import response parse error:", text); log.error("Import response parse error:", text);
alert(`${t("exportImport.importError")}: Server returned invalid response`); alert(`${t("exportImport.importError")}: Server returned invalid response`);
return; return;
} }
@@ -590,7 +591,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
settingsHook.loadSettings(); settingsHook.loadSettings();
doses.loadTakenDoses(); doses.loadTakenDoses();
} catch (err) { } catch (err) {
console.error("Import error:", err); log.error("Import error:", err);
alert(t("exportImport.importError")); alert(t("exportImport.importError"));
} }
+2 -1
View File
@@ -6,6 +6,7 @@ import { useMedicationForm, useUnsavedChangesWarning } from "../hooks";
import type { DoseUnit, Medication } from "../types"; import type { DoseUnit, Medication } from "../types";
import { DOSE_UNITS, FIELD_LIMITS, getPackageSize } from "../types"; import { DOSE_UNITS, FIELD_LIMITS, getPackageSize } from "../types";
import { combineDateAndTime, formatDateTime, formatNumber } from "../utils/formatters"; import { combineDateAndTime, formatDateTime, formatNumber } from "../utils/formatters";
import { log } from "../utils/logger";
export function MedicationsPage() { export function MedicationsPage() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -299,7 +300,7 @@ export function MedicationsPage() {
setOriginalForm(form); setOriginalForm(form);
} }
} catch (err) { } catch (err) {
console.error("Save error:", err); log.error("Save error:", err);
alert(err instanceof Error && err.message ? err.message : t("common.saveFailed")); 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);
},
};
+1
View File
@@ -14,6 +14,7 @@ export default defineConfig({
plugins: [react()], plugins: [react()],
define: { define: {
__APP_VERSION__: JSON.stringify(packageJson.version || "unknown"), __APP_VERSION__: JSON.stringify(packageJson.version || "unknown"),
__LOG_LEVEL__: JSON.stringify(process.env.LOG_LEVEL || "warn"),
}, },
server: { server: {
port: 5173, port: 5173,