diff --git a/ui/eslint.config.cjs b/ui/eslint.config.cjs
index a6c0c1f..662a9cb 100644
--- a/ui/eslint.config.cjs
+++ b/ui/eslint.config.cjs
@@ -62,6 +62,7 @@ module.exports = defineConfig([{
             allowConstantExport: true,
         }],
 
+        "no-console": ["warn", {}],
         "import/order": ["error", {
             groups: ["builtin", "external", "internal", "parent", "sibling"],
             "newlines-between": "always",
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 8ac57a1..5ffc4b2 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -36,6 +36,7 @@
         "react-xtermjs": "^1.0.10",
         "recharts": "^2.15.3",
         "tailwind-merge": "^3.3.0",
+        "tslog": "^4.9.3",
         "usehooks-ts": "^3.1.1",
         "validator": "^13.15.0",
         "zustand": "^4.5.2"
@@ -6533,6 +6534,17 @@
       "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
       "license": "0BSD"
     },
+    "node_modules/tslog": {
+      "version": "4.9.3",
+      "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz",
+      "integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==",
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/fullstack-build/tslog?sponsor=1"
+      }
+    },
     "node_modules/type-check": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/ui/package.json b/ui/package.json
index eb9a9a3..ac7aa19 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -47,6 +47,7 @@
     "react-xtermjs": "^1.0.10",
     "recharts": "^2.15.3",
     "tailwind-merge": "^3.3.0",
+    "tslog": "^4.9.3",
     "usehooks-ts": "^3.1.1",
     "validator": "^13.15.0",
     "zustand": "^4.5.2"
diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx
index 83ae509..e6c3e1a 100644
--- a/ui/src/components/ActionBar.tsx
+++ b/ui/src/components/ActionBar.tsx
@@ -19,7 +19,7 @@ import WakeOnLanModal from "@/components/popovers/WakeOnLan/Index";
 import MountPopopover from "@/components/popovers/MountPopover";
 import ExtensionPopover from "@/components/popovers/ExtensionPopover";
 import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
-
+import { logger } from "@/log";
 export default function Actionbar({
   requestFullscreen,
 }: {
@@ -48,7 +48,7 @@ export default function Actionbar({
         if (!open) {
           setTimeout(() => {
             setDisableFocusTrap(false);
-            console.log("Popover is closing. Returning focus trap to video");
+            logger.info("Popover is closing. Returning focus trap to video");
           }, 0);
         }
       }
diff --git a/ui/src/components/FeatureFlag.tsx b/ui/src/components/FeatureFlag.tsx
index cc0c7c5..a02d1c9 100644
--- a/ui/src/components/FeatureFlag.tsx
+++ b/ui/src/components/FeatureFlag.tsx
@@ -1,7 +1,8 @@
 import { useEffect } from "react";
 
-import { useFeatureFlag } from "../hooks/useFeatureFlag";
+import { logger } from "@/log";
 
+import { useFeatureFlag } from "../hooks/useFeatureFlag";
 export function FeatureFlag({
   minAppVersion,
   name = "unnamed",
@@ -17,7 +18,7 @@ export function FeatureFlag({
 
   useEffect(() => {
     if (!appVersion) return;
-    console.log(
+    logger.info(
       `Feature '${name}' ${isEnabled ? "ENABLED" : "DISABLED"}: ` +
         `Current version: ${appVersion}, ` +
         `Required min version: ${minAppVersion || "N/A"}`,
diff --git a/ui/src/components/InfoBar.tsx b/ui/src/components/InfoBar.tsx
index b865985..b1ef75d 100644
--- a/ui/src/components/InfoBar.tsx
+++ b/ui/src/components/InfoBar.tsx
@@ -9,6 +9,7 @@ import {
   useVideoStore,
 } from "@/hooks/stores";
 import { keys, modifiers } from "@/keyboardMappings";
+import { logger } from "@/log";
 
 export default function InfoBar() {
   const activeKeys = useHidStore(state => state.activeKeys);
@@ -31,9 +32,9 @@ export default function InfoBar() {
 
   useEffect(() => {
     if (!rpcDataChannel) return;
-    rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed");
+    rpcDataChannel.onclose = () => logger.info("rpcDataChannel has closed");
     rpcDataChannel.onerror = e =>
-      console.log(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
+      logger.error(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
   }, [rpcDataChannel]);
 
   const keyboardLedState = useHidStore(state => state.keyboardLedState);
diff --git a/ui/src/components/USBStateStatus.tsx b/ui/src/components/USBStateStatus.tsx
index f0b2cb2..af1c1c3 100644
--- a/ui/src/components/USBStateStatus.tsx
+++ b/ui/src/components/USBStateStatus.tsx
@@ -5,6 +5,7 @@ import KeyboardAndMouseConnectedIcon from "@/assets/keyboard-and-mouse-connected
 import LoadingSpinner from "@components/LoadingSpinner";
 import StatusCard from "@components/StatusCards";
 import { HidState } from "@/hooks/stores";
+import { logger } from "@/log";
 
 type USBStates = HidState["usbState"];
 
@@ -67,7 +68,7 @@ export default function USBStateStatus({
   };
   const props = StatusCardProps[state];
   if (!props) {
-    console.log("Unsupported USB state: ", state);
+    logger.error("Unsupported USB state: ", { state });
     return;
   }
 
diff --git a/ui/src/components/UsbDeviceSetting.tsx b/ui/src/components/UsbDeviceSetting.tsx
index 432ec3d..41d491a 100644
--- a/ui/src/components/UsbDeviceSetting.tsx
+++ b/ui/src/components/UsbDeviceSetting.tsx
@@ -1,5 +1,7 @@
 import { useCallback , useEffect, useState } from "react";
 
+import { logger } from "@/log";
+
 import { useJsonRpc } from "../hooks/useJsonRpc";
 import notifications from "../notifications";
 import { SettingsItem } from "../routes/devices.$id.settings";
@@ -9,6 +11,7 @@ import { Button } from "./Button";
 import { SelectMenuBasic } from "./SelectMenuBasic";
 import { SettingsSectionHeader } from "./SettingsSectionHeader";
 import Fieldset from "./Fieldset";
+
 export interface USBConfig {
   vendor_id: string;
   product_id: string;
@@ -69,7 +72,7 @@ export function UsbDeviceSetting() {
   const syncUsbDeviceConfig = useCallback(() => {
     send("getUsbDevices", {}, resp => {
       if ("error" in resp) {
-        console.error("Failed to load USB devices:", resp.error);
+        logger.error("Failed to load USB devices:", resp.error);
         notifications.error(
           `Failed to load USB devices: ${resp.error.data || "Unknown error"}`,
         );
diff --git a/ui/src/components/UsbInfoSetting.tsx b/ui/src/components/UsbInfoSetting.tsx
index 198335c..ed86095 100644
--- a/ui/src/components/UsbInfoSetting.tsx
+++ b/ui/src/components/UsbInfoSetting.tsx
@@ -1,7 +1,7 @@
 import { useMemo , useCallback , useEffect, useState } from "react";
 
 import { Button } from "@components/Button";
-
+import { jsonRpcLogger, logger } from "@/log";
 
 import { UsbConfigState } from "../hooks/stores";
 import { useJsonRpc } from "../hooks/useJsonRpc";
@@ -96,12 +96,12 @@ export function UsbInfoSetting() {
   const syncUsbConfigProduct = useCallback(() => {
     send("getUsbConfig", {}, resp => {
       if ("error" in resp) {
-        console.error("Failed to load USB Config:", resp.error);
+        jsonRpcLogger.error("Failed to load USB Config:", resp.error);
         notifications.error(
           `Failed to load USB Config: ${resp.error.data || "Unknown error"}`,
         );
       } else {
-        console.log("syncUsbConfigProduct#getUsbConfig result:", resp.result);
+        jsonRpcLogger.info("syncUsbConfigProduct#getUsbConfig result:", resp.result);
         const usbConfigState = resp.result as UsbConfigState;
         const product = usbConfigs.map(u => u.value).includes(usbConfigState.product)
           ? usbConfigState.product
@@ -210,7 +210,7 @@ function USBConfigDialog({
   const syncUsbConfig = useCallback(() => {
     send("getUsbConfig", {}, resp => {
       if ("error" in resp) {
-        console.error("Failed to load USB Config:", resp.error);
+        logger.error("Failed to load USB Config:", resp.error);
       } else {
         setUsbConfigState(resp.result as UsbConfigState);
       }
diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx
index ca4db08..8b5c97c 100644
--- a/ui/src/components/WebRTCVideo.tsx
+++ b/ui/src/components/WebRTCVideo.tsx
@@ -18,6 +18,7 @@ import InfoBar from "@components/InfoBar";
 import useKeyboard from "@/hooks/useKeyboard";
 import { useJsonRpc } from "@/hooks/useJsonRpc";
 import notifications from "@/notifications";
+import { logger } from "@/log";
 
 import {
   HDMIErrorOverlay,
@@ -445,7 +446,7 @@ export default function WebRTCVideo() {
     // Fix only works in chrome based browsers.
     if (e.code === "Space") {
       if (videoElm.current?.paused == true) {
-        console.log("Force playing video");
+        logger.info("Force playing video");
         videoElm.current?.play();
       }
     }
@@ -487,7 +488,7 @@ export default function WebRTCVideo() {
   useEffect(
     function updateVideoStream() {
       if (!mediaStream) return;
-      console.log("Updating video stream from mediaStream");
+      logger.info("Updating video stream from mediaStream");
       // We set the as early as possible
       addStreamToVideoElm(mediaStream);
     },
diff --git a/ui/src/components/extensions/ATXPowerControl.tsx b/ui/src/components/extensions/ATXPowerControl.tsx
index 0334a18..822722b 100644
--- a/ui/src/components/extensions/ATXPowerControl.tsx
+++ b/ui/src/components/extensions/ATXPowerControl.tsx
@@ -6,6 +6,7 @@ import Card from "@components/Card";
 import { SettingsPageHeader } from "@components/SettingsPageheader";
 import notifications from "@/notifications";
 import LoadingSpinner from "@/components/LoadingSpinner";
+import { logger } from "@/log";
 
 import { useJsonRpc } from "../../hooks/useJsonRpc";
 
@@ -53,7 +54,7 @@ export function ATXPowerControl() {
       // Start long press timer
       const timer = setTimeout(() => {
         // Send long press action
-        console.log("Sending long press ATX power action");
+        logger.info("Sending long press ATX power action");
         send("setATXPowerAction", { action: "power-long" }, resp => {
           if ("error" in resp) {
             notifications.error(
@@ -74,7 +75,7 @@ export function ATXPowerControl() {
         setPowerPressTimer(null);
 
         // Send short press action
-        console.log("Sending short press ATX power action");
+        logger.info("Sending short press ATX power action");
         send("setATXPowerAction", { action: "power-short" }, resp => {
           if ("error" in resp) {
             notifications.error(
diff --git a/ui/src/components/extensions/SerialConsole.tsx b/ui/src/components/extensions/SerialConsole.tsx
index 544d3fd..56b14d7 100644
--- a/ui/src/components/extensions/SerialConsole.tsx
+++ b/ui/src/components/extensions/SerialConsole.tsx
@@ -69,7 +69,7 @@ export function SerialConsole() {
               text="Open Console"
               onClick={() => {
                 setTerminalType("serial");
-                console.log("Opening serial console with settings: ", settings);
+                logger.info("Opening serial console with settings: ", settings);
               }}
             />
           </div>
diff --git a/ui/src/components/popovers/PasteModal.tsx b/ui/src/components/popovers/PasteModal.tsx
index 26e4b82..ee42983 100644
--- a/ui/src/components/popovers/PasteModal.tsx
+++ b/ui/src/components/popovers/PasteModal.tsx
@@ -12,7 +12,7 @@ import { useHidStore, useRTCStore, useUiStore, useSettingsStore } from "@/hooks/
 import { keys, modifiers } from "@/keyboardMappings";
 import { layouts, chars } from "@/keyboardLayouts";
 import notifications from "@/notifications";
-
+import { logger } from "@/log";
 const hidKeyboardPayload = (keys: number[], modifier: number) => {
   return { keys, modifier };
 };
@@ -95,7 +95,7 @@ export default function PasteModal() {
 	}
       }
     } catch (error) {
-      console.error(error);
+      logger.error("Failed to paste text", error);
       notifications.error("Failed to paste text");
     }
   }, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode, keyboardLayout]);
diff --git a/ui/src/components/popovers/WakeOnLan/Index.tsx b/ui/src/components/popovers/WakeOnLan/Index.tsx
index f4f4951..c9204ad 100644
--- a/ui/src/components/popovers/WakeOnLan/Index.tsx
+++ b/ui/src/components/popovers/WakeOnLan/Index.tsx
@@ -6,6 +6,7 @@ import { SettingsPageHeader } from "@components/SettingsPageheader";
 import { useJsonRpc } from "@/hooks/useJsonRpc";
 import { useRTCStore, useUiStore } from "@/hooks/stores";
 import notifications from "@/notifications";
+import { logger } from "@/log";
 
 import EmptyStateCard from "./EmptyStateCard";
 import DeviceList, { StoredDevice } from "./DeviceList";
@@ -56,7 +57,7 @@ export default function WakeOnLanModal() {
       if ("result" in resp) {
         setStoredDevices(resp.result as StoredDevice[]);
       } else {
-        console.error("Failed to load Wake-on-LAN devices:", resp.error);
+        logger.error("Failed to load Wake-on-LAN devices:", resp.error);
       }
     });
   }, [send, setStoredDevices]);
@@ -72,7 +73,7 @@ export default function WakeOnLanModal() {
 
       send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, resp => {
         if ("error" in resp) {
-          console.error("Failed to update Wake-on-LAN devices:", resp.error);
+          logger.error("Failed to update Wake-on-LAN devices:", resp.error);
         } else {
           syncStoredDevices();
         }
@@ -85,10 +86,10 @@ export default function WakeOnLanModal() {
     (name: string, macAddress: string) => {
       if (!name || !macAddress) return;
       const updatedDevices = [...storedDevices, { name, macAddress }];
-      console.log("updatedDevices", updatedDevices);
+      logger.info("updatedDevices", updatedDevices);
       send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, resp => {
         if ("error" in resp) {
-          console.error("Failed to add Wake-on-LAN device:", resp.error);
+          logger.error("Failed to add Wake-on-LAN device:", { error: resp.error });
           setAddDeviceErrorMessage("Failed to add device");
         } else {
           setShowAddForm(false);
diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts
index 52ef89d..a947867 100644
--- a/ui/src/hooks/stores.ts
+++ b/ui/src/hooks/stores.ts
@@ -6,6 +6,7 @@ import {
   MAX_TOTAL_MACROS,
   MAX_KEYS_PER_STEP,
 } from "@/constants/macros";
+import { logger } from "@/log";
 
 // Define the JsonRpc types for better type checking
 interface JsonRpcResponse {
@@ -744,7 +745,7 @@ export const useNetworkStateStore = create<NetworkState>((set, get) => ({
   setDhcpLeaseExpiry: (expiry: Date) => {
     const lease = get().dhcp_lease;
     if (!lease) {
-      console.warn("No lease found");
+      logger.warn("No lease found");
       return;
     }
 
@@ -807,7 +808,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
 
     const { sendFn } = get();
     if (!sendFn) {
-      console.warn("JSON-RPC send function not available.");
+      logger.warn("JSON-RPC send function not available.");
       return;
     }
 
@@ -817,7 +818,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
       await new Promise<void>((resolve, reject) => {
         sendFn("getKeyboardMacros", {}, response => {
           if (response.error) {
-            console.error("Error loading macros:", response.error);
+            logger.error("Error loading macros:", response.error);
             reject(new Error(response.error.message));
             return;
           }
@@ -842,7 +843,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
         });
       });
     } catch (error) {
-      console.error("Failed to load macros:", error);
+      logger.error("Failed to load macros:", error);
     } finally {
       set({ loading: false });
     }
@@ -851,18 +852,18 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
   saveMacros: async (macros: KeySequence[]) => {
     const { sendFn } = get();
     if (!sendFn) {
-      console.warn("JSON-RPC send function not available.");
+      logger.warn("JSON-RPC send function not available.");
       throw new Error("JSON-RPC send function not available");
     }
 
     if (macros.length > MAX_TOTAL_MACROS) {
-      console.error(`Cannot save: exceeded maximum of ${MAX_TOTAL_MACROS} macros`);
+      logger.error(`Cannot save: exceeded maximum of ${MAX_TOTAL_MACROS} macros`);
       throw new Error(`Cannot save: exceeded maximum of ${MAX_TOTAL_MACROS} macros`);
     }
 
     for (const macro of macros) {
       if (macro.steps.length > MAX_STEPS_PER_MACRO) {
-        console.error(
+        logger.error(
           `Cannot save: macro "${macro.name}" exceeds maximum of ${MAX_STEPS_PER_MACRO} steps`,
         );
         throw new Error(
@@ -873,7 +874,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
       for (let i = 0; i < macro.steps.length; i++) {
         const step = macro.steps[i];
         if (step.keys && step.keys.length > MAX_KEYS_PER_STEP) {
-          console.error(
+          logger.error(
             `Cannot save: macro "${macro.name}" step ${i + 1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`,
           );
           throw new Error(
@@ -902,7 +903,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
       });
 
       if (response.error) {
-        console.error("Error saving macros:", response.error);
+        logger.error("Error saving macros:", response.error);
         const errorMessage =
           typeof response.error.data === "string"
             ? response.error.data
@@ -913,7 +914,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
       // Only update the store if the request was successful
       set({ macros: macrosWithSortOrder });
     } catch (error) {
-      console.error("Failed to save macros:", error);
+      logger.error("Failed to save macros:", error);
       throw error;
     } finally {
       set({ loading: false });
diff --git a/ui/src/hooks/useAppNavigation.ts b/ui/src/hooks/useAppNavigation.ts
index 6c9270a..32ec5a3 100644
--- a/ui/src/hooks/useAppNavigation.ts
+++ b/ui/src/hooks/useAppNavigation.ts
@@ -1,6 +1,8 @@
 import { useNavigate, useParams, NavigateOptions } from "react-router-dom";
 import { useCallback, useMemo } from "react";
 
+import { logger } from "@/log";
+
 import { isOnDevice } from "../main";
 
 /**
@@ -21,7 +23,7 @@ export function getDeviceUiPath(path: string, deviceId?: string): string {
     return normalizedPath;
   } else {
     if (!deviceId) {
-      console.error("No device ID provided when generating path in cloud mode");
+      logger.error("No device ID provided when generating path in cloud mode");
       throw new Error("Device ID is required for cloud mode path generation");
     }
     return `/devices/${deviceId}${normalizedPath}`;
diff --git a/ui/src/hooks/useJsonRpc.ts b/ui/src/hooks/useJsonRpc.ts
index 92b56ff..600f580 100644
--- a/ui/src/hooks/useJsonRpc.ts
+++ b/ui/src/hooks/useJsonRpc.ts
@@ -1,7 +1,7 @@
 import { useCallback, useEffect } from "react";
 
 import { useRTCStore } from "@/hooks/stores";
-
+import { jsonRpcLogger } from "@/log";
 export interface JsonRpcRequest {
   jsonrpc: string;
   method: string;
@@ -61,7 +61,7 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
         return;
       }
 
-      if ("error" in payload) console.error(payload.error);
+      if ("error" in payload) jsonRpcLogger.error(payload.error);
       if (!payload.id) return;
 
       const callback = callbackStore.get(payload.id);
diff --git a/ui/src/routes/devices.$id.deregister.tsx b/ui/src/routes/devices.$id.deregister.tsx
index 8c0a87f..d62090d 100644
--- a/ui/src/routes/devices.$id.deregister.tsx
+++ b/ui/src/routes/devices.$id.deregister.tsx
@@ -16,6 +16,7 @@ import { User } from "@/hooks/stores";
 import { checkAuth } from "@/main";
 import Fieldset from "@components/Fieldset";
 import { CLOUD_API } from "@/ui.config";
+import { logger } from "@/log";
 
 interface LoaderData {
   device: { id: string; name: string; user: { googleId: string } };
@@ -37,7 +38,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
       return { message: "There was an error renaming your device. Please try again." };
     }
   } catch (e) {
-    console.error(e);
+    logger.error("Error deregistering device", e);
     return { message: "There was an error renaming your device. Please try again." };
   }
 
@@ -61,7 +62,7 @@ const loader = async ({ params }: LoaderFunctionArgs) => {
 
     return { device, user };
   } catch (e) {
-    console.error(e);
+    logger.error("Error deregistering device", e);
     return { devices: [] };
   }
 };
diff --git a/ui/src/routes/devices.$id.mount.tsx b/ui/src/routes/devices.$id.mount.tsx
index 7ac519c..ca69842 100644
--- a/ui/src/routes/devices.$id.mount.tsx
+++ b/ui/src/routes/devices.$id.mount.tsx
@@ -26,6 +26,7 @@ import ArchIcon from "@/assets/arch-icon.png";
 import NetBootIcon from "@/assets/netboot-icon.svg";
 import Fieldset from "@/components/Fieldset";
 import { DEVICE_API } from "@/ui.config";
+import { logger } from "@/log";
 
 import { useJsonRpc } from "../hooks/useJsonRpc";
 import notifications from "../notifications";
@@ -86,7 +87,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
   }
 
   function handleUrlMount(url: string, mode: RemoteVirtualMediaState["mode"]) {
-    console.log(`Mounting ${url} as ${mode}`);
+    logger.info(`Mounting ${url} as ${mode}`);
 
     setMountInProgress(true);
     send("mountWithHTTP", { url, mode }, async resp => {
@@ -105,7 +106,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
   }
 
   function handleStorageMount(fileName: string, mode: RemoteVirtualMediaState["mode"]) {
-    console.log(`Mounting ${fileName} as ${mode}`);
+    logger.info(`Mounting ${fileName} as ${mode}`);
 
     setMountInProgress(true);
     send("mountWithStorage", { filename: fileName, mode }, async resp => {
@@ -132,7 +133,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
   }
 
   function handleBrowserMount(file: File, mode: RemoteVirtualMediaState["mode"]) {
-    console.log(`Mounting ${file.name} as ${mode}`);
+    logger.info(`Mounting ${file.name} as ${mode}`);
 
     setMountInProgress(true);
     send(
@@ -419,7 +420,7 @@ function BrowserFileView({
 
   const handleMount = () => {
     if (selectedFile) {
-      console.log(`Mounting ${selectedFile.name} as ${setUsbMode}`);
+      logger.info(`Mounting ${selectedFile.name} as ${setUsbMode}`);
       onMountFile(selectedFile, usbMode);
     }
   };
@@ -756,7 +757,7 @@ function DeviceFileView({
   }, [syncStorage]);
 
   function handleDeleteFile(file: { name: string; size: string; createdAt: string }) {
-    console.log("Deleting file:", file);
+    logger.info("Deleting file:", file);
     send("deleteStorageFile", { filename: file.name }, res => {
       if ("error" in res) {
         notifications.error(`Error deleting file: ${res.error}`);
@@ -986,6 +987,8 @@ function UploadFileView({
   onCancelUpload: () => void;
   incompleteFileName?: string;
 }) {
+  const l = logger.getSubLogger({ name: "file-upload" });
+
   const [uploadState, setUploadState] = useState<"idle" | "uploading" | "success">(
     "idle",
   );
@@ -1002,7 +1005,7 @@ function UploadFileView({
   useEffect(() => {
     const ref = rtcDataChannelRef.current;
     return () => {
-      console.log("unmounting");
+      logger.info("unmounting");
       if (ref) {
         ref.onopen = null;
         ref.onerror = null;
@@ -1023,10 +1026,10 @@ function UploadFileView({
       .peerConnection?.createDataChannel(dataChannel);
 
     if (!rtcDataChannel) {
-      console.error("Failed to create data channel for file upload");
+      l.error("Failed to create data channel for file upload");
       notifications.error("Failed to create data channel for file upload");
       setUploadState("idle");
-      console.log("Upload state set to 'idle'");
+      l.info("Upload state set to 'idle'");
 
       return;
     }
@@ -1072,7 +1075,7 @@ function UploadFileView({
         lastUploadedBytes = AlreadyUploadedBytes;
         lastUpdateTime = now;
       } catch (e) {
-        console.error("Error processing RTC Data channel message:", e);
+        l.error("Error processing RTC Data channel message:", e);
       }
     };
 
@@ -1099,24 +1102,24 @@ function UploadFileView({
           }
 
           offset += buffer.byteLength;
-          console.log(`Chunk sent: ${offset} / ${file.size} bytes`);
+          l.info(`Chunk sent: ${offset} / ${file.size} bytes`);
           sendNextChunk();
         });
       };
 
       sendNextChunk();
       rtcDataChannel.onbufferedamountlow = () => {
-        console.log("RTC Data channel buffered amount low");
+        l.info("RTC Data channel buffered amount low");
         pauseSending = false; // Now the data channel is ready to send more data
         sendNextChunk();
       };
     };
 
     rtcDataChannel.onerror = error => {
-      console.error("RTC Data channel error:", error);
+      l.error("RTC Data channel error:", error);
       notifications.error(`Upload failed: ${error}`);
       setUploadState("idle");
-      console.log("Upload state set to 'idle'");
+      l.info("Upload state set to 'idle'");
     };
   }
 
@@ -1169,14 +1172,14 @@ function UploadFileView({
       if (xhr.status === 200) {
         setUploadState("success");
       } else {
-        console.error("Upload error:", xhr.statusText);
+        l.error("Upload error:", xhr.statusText);
         setUploadError(xhr.statusText);
         setUploadState("idle");
       }
     };
 
     xhr.onerror = () => {
-      console.error("XHR error:", xhr.statusText);
+      l.error("XHR error:", xhr.statusText);
       setUploadError(xhr.statusText);
       setUploadState("idle");
     };
@@ -1205,19 +1208,19 @@ function UploadFileView({
       }
 
       setFileError(null);
-      console.log(`File selected: ${file.name}, size: ${file.size} bytes`);
+      l.info(`File selected: ${file.name}, size: ${file.size} bytes`);
       setUploadedFileName(file.name);
       setUploadedFileSize(file.size);
       setUploadState("uploading");
-      console.log("Upload state set to 'uploading'");
+      l.info("Upload state set to 'uploading'");
 
       send("startStorageFileUpload", { filename: file.name, size: file.size }, resp => {
-        console.log("startStorageFileUpload response:", resp);
+        l.info("startStorageFileUpload response:", resp);
         if ("error" in resp) {
-          console.error("Upload error:", resp.error.message);
+          l.error("Upload error:", resp.error.message);
           setUploadError(resp.error.data || resp.error.message);
           setUploadState("idle");
-          console.log("Upload state set to 'idle'");
+          l.info("Upload state set to 'idle'");
           return;
         }
 
@@ -1226,7 +1229,7 @@ function UploadFileView({
           dataChannel: string;
         };
 
-        console.log(
+        l.info(
           `Already uploaded bytes: ${alreadyUploadedBytes}, Data channel: ${dataChannel}`,
         );
 
diff --git a/ui/src/routes/devices.$id.rename.tsx b/ui/src/routes/devices.$id.rename.tsx
index 2852561..76cd174 100644
--- a/ui/src/routes/devices.$id.rename.tsx
+++ b/ui/src/routes/devices.$id.rename.tsx
@@ -17,6 +17,7 @@ import { User } from "@/hooks/stores";
 import { checkAuth } from "@/main";
 import Fieldset from "@components/Fieldset";
 import { CLOUD_API } from "@/ui.config";
+import { logger } from "@/log";
 
 import api from "../api";
 
@@ -41,7 +42,7 @@ const action = async ({ params, request }: ActionFunctionArgs) => {
       return { message: "There was an error renaming your device. Please try again." };
     }
   } catch (e) {
-    console.error(e);
+    logger.error("Error renaming device", e);
     return { message: "There was an error renaming your device. Please try again." };
   }
 
@@ -65,7 +66,7 @@ const loader = async ({ params }: LoaderFunctionArgs) => {
 
     return { device, user };
   } catch (e) {
-    console.error(e);
+    logger.error("Error renaming device", e);
     return { devices: [] };
   }
 };
diff --git a/ui/src/routes/devices.$id.settings.access._index.tsx b/ui/src/routes/devices.$id.settings.access._index.tsx
index e0543b8..f035b26 100644
--- a/ui/src/routes/devices.$id.settings.access._index.tsx
+++ b/ui/src/routes/devices.$id.settings.access._index.tsx
@@ -15,6 +15,7 @@ import { DEVICE_API } from "@/ui.config";
 import { useJsonRpc } from "@/hooks/useJsonRpc";
 import { isOnDevice } from "@/main";
 import { TextAreaWithLabel } from "@components/TextArea";
+import { logger } from "@/log";
 
 import { LocalDevice } from "./devices.$id";
 import { SettingsItem } from "./devices.$id.settings";
@@ -57,7 +58,7 @@ export default function SettingsAccessIndexRoute() {
 
   const getCloudState = useCallback(() => {
     send("getCloudState", {}, resp => {
-      if ("error" in resp) return console.error(resp.error);
+      if ("error" in resp) return logger.error("Error getting cloud state", resp.error);
       const cloudState = resp.result as CloudState;
       setAdopted(cloudState.connected);
       setCloudApiUrl(cloudState.url);
@@ -78,7 +79,7 @@ export default function SettingsAccessIndexRoute() {
 
   const getTLSState = useCallback(() => {
     send("getTLSState", {}, resp => {
-      if ("error" in resp) return console.error(resp.error);
+      if ("error" in resp) return logger.error("Error getting TLS state", resp.error);
       const tlsState = resp.result as TLSState;
 
       setTlsMode(tlsState.mode);
@@ -199,7 +200,7 @@ export default function SettingsAccessIndexRoute() {
     getTLSState();
 
     send("getDeviceID", {}, async resp => {
-      if ("error" in resp) return console.error(resp.error);
+      if ("error" in resp) return logger.error("Error getting device ID", resp.error);
       setDeviceId(resp.result as string);
     });
   }, [send, getCloudState, getTLSState]);
diff --git a/ui/src/routes/devices.$id.settings.access.local-auth.tsx b/ui/src/routes/devices.$id.settings.access.local-auth.tsx
index 50b2cc4..09a4f7f 100644
--- a/ui/src/routes/devices.$id.settings.access.local-auth.tsx
+++ b/ui/src/routes/devices.$id.settings.access.local-auth.tsx
@@ -6,6 +6,7 @@ import { InputFieldWithLabel } from "@/components/InputField";
 import api from "@/api";
 import { useLocalAuthModalStore } from "@/hooks/stores";
 import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
+import { logger } from "@/log";
 
 export default function SecurityAccessLocalAuthRoute() {
   const { setModalView } = useLocalAuthModalStore();
@@ -54,7 +55,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
         setError(data.error || "An error occurred while setting the password");
       }
     } catch (error) {
-      console.error(error);
+      logger.error("An error occurred while setting the password", error);
       setError("An error occurred while setting the password");
     }
   };
@@ -94,7 +95,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
         setError(data.error || "An error occurred while changing the password");
       }
     } catch (error) {
-      console.error(error);
+      logger.error("An error occurred while changing the password", error);
       setError("An error occurred while changing the password");
     }
   };
@@ -116,7 +117,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
         setError(data.error || "An error occurred while disabling the password");
       }
     } catch (error) {
-      console.error(error);
+      logger.error("An error occurred while disabling the password", error);
       setError("An error occurred while disabling the password");
     }
   };
diff --git a/ui/src/routes/devices.$id.settings.general.update.tsx b/ui/src/routes/devices.$id.settings.general.update.tsx
index 7c41449..2bc0059 100644
--- a/ui/src/routes/devices.$id.settings.general.update.tsx
+++ b/ui/src/routes/devices.$id.settings.general.update.tsx
@@ -9,6 +9,7 @@ import { UpdateState, useDeviceStore, useUpdateStore } from "@/hooks/stores";
 import notifications from "@/notifications";
 import LoadingSpinner from "@/components/LoadingSpinner";
 import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
+import { logger } from "@/log";
 
 export default function SettingsGeneralUpdateRoute() {
   const navigate = useNavigate();
@@ -184,7 +185,7 @@ function LoadingState({
       })
       .catch(error => {
         if (!signal.aborted) {
-          console.error("LoadingState: Error fetching version info", error);
+          logger.error("LoadingState: Error fetching version info", error);
         }
       });
 
@@ -240,7 +241,7 @@ function UpdatingDeviceState({
       return 0;
     }
 
-    console.log(
+    logger.info(
       `For ${type}:\n` +
       `  Download Progress: ${downloadProgress}% (${otaState[`${type}DownloadProgress`]})\n` +
       `  Update Progress: ${updateProgress}% (${otaState[`${type}UpdateProgress`]})\n` +
diff --git a/ui/src/routes/devices.$id.settings.network.tsx b/ui/src/routes/devices.$id.settings.network.tsx
index 0905db5..c63c2fe 100644
--- a/ui/src/routes/devices.$id.settings.network.tsx
+++ b/ui/src/routes/devices.$id.settings.network.tsx
@@ -22,6 +22,7 @@ import { SettingsPageHeader } from "@/components/SettingsPageheader";
 import Fieldset from "@/components/Fieldset";
 import { ConfirmDialog } from "@/components/ConfirmDialog";
 import notifications from "@/notifications";
+import { logger } from "@/log";
 
 import Ipv6NetworkCard from "../components/Ipv6NetworkCard";
 import EmptyCard from "../components/EmptyCard";
@@ -29,7 +30,6 @@ import AutoHeight from "../components/AutoHeight";
 import DhcpLeaseCard from "../components/DhcpLeaseCard";
 
 import { SettingsItem } from "./devices.$id.settings";
-
 dayjs.extend(relativeTime);
 
 const defaultNetworkSettings: NetworkSettings = {
@@ -105,7 +105,7 @@ export default function SettingsNetworkRoute() {
     setNetworkSettingsLoaded(false);
     send("getNetworkSettings", {}, resp => {
       if ("error" in resp) return;
-      console.log(resp.result);
+      logger.trace("getNetworkSettings result:", resp.result);
       setNetworkSettings(resp.result as NetworkSettings);
 
       if (!firstNetworkSettings.current) {
@@ -118,7 +118,7 @@ export default function SettingsNetworkRoute() {
   const getNetworkState = useCallback(() => {
     send("getNetworkState", {}, resp => {
       if ("error" in resp) return;
-      console.log(resp.result);
+      logger.trace("getNetworkState result:", resp.result);
       setNetworkState(resp.result as NetworkState);
     });
   }, [send, setNetworkState]);
diff --git a/ui/src/routes/devices.$id.settings.tsx b/ui/src/routes/devices.$id.settings.tsx
index 5b277a1..0e64311 100644
--- a/ui/src/routes/devices.$id.settings.tsx
+++ b/ui/src/routes/devices.$id.settings.tsx
@@ -20,11 +20,11 @@ import { LinkButton } from "@/components/Button";
 import LoadingSpinner from "@/components/LoadingSpinner";
 import { useUiStore } from "@/hooks/stores";
 import useKeyboard from "@/hooks/useKeyboard";
+import { logger } from "@/log";
 
 import { FeatureFlag } from "../components/FeatureFlag";
 import { cx } from "../cva.config";
 
-
 /* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
 export default function SettingsRoute() {
   const location = useLocation();
@@ -73,7 +73,7 @@ export default function SettingsRoute() {
       // For some reason, the focus trap is not disabled immediately
       // so we need to blur the active element
       (document.activeElement as HTMLElement)?.blur();
-      console.log("Just disabled focus trap");
+      logger.info("Just disabled focus trap");
     }, 300);
 
     return () => {
diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx
index c9aac36..23b90ed 100644
--- a/ui/src/routes/devices.$id.tsx
+++ b/ui/src/routes/devices.$id.tsx
@@ -28,6 +28,7 @@ import {
   useNetworkStateStore,
   User,
   useRTCStore,
+  useSettingsStore,
   useUiStore,
   useUpdateStore,
   useVideoStore,
@@ -40,6 +41,7 @@ import ConnectionStatsSidebar from "@/components/sidebar/connectionStats";
 import { JsonRpcRequest, useJsonRpc } from "@/hooks/useJsonRpc";
 import Terminal from "@components/Terminal";
 import { CLOUD_API, DEVICE_API } from "@/ui.config";
+import { logger, enableDebugMode, disableDebugMode } from "@/log";
 
 import UpdateInProgressStatusCard from "../components/UpdateInProgressStatusCard";
 import api from "../api";
@@ -130,6 +132,16 @@ export default function KvmIdRoute() {
   const sidebarView = useUiStore(state => state.sidebarView);
   const [queryParams, setQueryParams] = useSearchParams();
 
+  // Enable dev mode if the user is in debug mode
+  const isDebugMode = useSettingsStore(state => state.debugMode);
+  useEffect(() => {
+    if (isDebugMode) {
+      enableDebugMode();
+    } else {
+      disableDebugMode();
+    }
+  }, [isDebugMode]);
+
   const setIsTurnServerInUse = useRTCStore(state => state.setTurnServerInUse);
   const peerConnection = useRTCStore(state => state.peerConnection);
   const setPeerConnectionState = useRTCStore(state => state.setPeerConnectionState);
@@ -151,7 +163,7 @@ export default function KvmIdRoute() {
   const [loadingMessage, setLoadingMessage] = useState("Connecting to device...");
   const cleanupAndStopReconnecting = useCallback(
     function cleanupAndStopReconnecting() {
-      console.log("Closing peer connection");
+      logger.info("Closing peer connection");
 
       setConnectionFailed(true);
       if (peerConnection) {
@@ -187,15 +199,14 @@ export default function KvmIdRoute() {
     ) {
       setLoadingMessage("Setting remote description");
 
+      const l = logger.getSubLogger({ name: "setRemoteSessionDescription" });
+
       try {
         await pc.setRemoteDescription(new RTCSessionDescription(remoteDescription));
-        console.log("[setRemoteSessionDescription] Remote description set successfully");
+        l.info("Remote description set successfully");
         setLoadingMessage("Establishing secure connection...");
       } catch (error) {
-        console.error(
-          "[setRemoteSessionDescription] Failed to set remote description:",
-          error,
-        );
+        l.error("Failed to set remote description", { error });
         cleanupAndStopReconnecting();
         return;
       }
@@ -207,12 +218,12 @@ export default function KvmIdRoute() {
 
         // When vivaldi has disabled "Broadcast IP for Best WebRTC Performance", this never connects
         if (pc.sctp?.state === "connected") {
-          console.log("[setRemoteSessionDescription] Remote description set");
+          l.info("Remote description set");
           clearInterval(checkInterval);
           setLoadingMessage("Connection established");
         } else if (attempts >= 10) {
-          console.log(
-            "[setRemoteSessionDescription] Failed to establish connection after 10 attempts",
+          l.error(
+            "Failed to establish connection after 10 attempts",
             {
               connectionState: pc.connectionState,
               iceConnectionState: pc.iceConnectionState,
@@ -221,7 +232,7 @@ export default function KvmIdRoute() {
           cleanupAndStopReconnecting();
           clearInterval(checkInterval);
         } else {
-          console.log("[setRemoteSessionDescription] Waiting for connection, state:", {
+          l.info("[setRemoteSessionDescription] Waiting for connection, state:", {
             connectionState: pc.connectionState,
             iceConnectionState: pc.iceConnectionState,
           });
@@ -236,6 +247,7 @@ export default function KvmIdRoute() {
   const makingOffer = useRef(false);
 
   const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
+  const wsLogger = logger.getSubLogger({ name: "websocket" });
 
   const { sendMessage, getWebSocket } = useWebSocket(
     isOnDevice
@@ -247,27 +259,27 @@ export default function KvmIdRoute() {
       reconnectAttempts: 15,
       reconnectInterval: 1000,
       onReconnectStop: () => {
-        console.log("Reconnect stopped");
+        wsLogger.info("Reconnect stopped");
         cleanupAndStopReconnecting();
       },
 
       shouldReconnect(event) {
-        console.log("[Websocket] shouldReconnect", event);
+        wsLogger.info("shouldReconnect", event);
         // TODO: Why true?
         return true;
       },
 
       onClose(event) {
-        console.log("[Websocket] onClose", event);
+        wsLogger.info("onClose", event);
         // We don't want to close everything down, we wait for the reconnect to stop instead
       },
 
       onError(event) {
-        console.log("[Websocket] onError", event);
+        wsLogger.error("onError", event);
         // We don't want to close everything down, we wait for the reconnect to stop instead
       },
       onOpen() {
-        console.log("[Websocket] onOpen");
+        wsLogger.info("onOpen");
       },
 
       onMessage: message => {
@@ -289,18 +301,18 @@ export default function KvmIdRoute() {
         const parsedMessage = JSON.parse(message.data);
         if (parsedMessage.type === "device-metadata") {
           const { deviceVersion } = parsedMessage.data;
-          console.log("[Websocket] Received device-metadata message");
-          console.log("[Websocket] Device version", deviceVersion);
+          wsLogger.info("Received device-metadata message");
+          wsLogger.info("Device version", deviceVersion);
           // If the device version is not set, we can assume the device is using the legacy signaling
           if (!deviceVersion) {
-            console.log("[Websocket] Device is using legacy signaling");
+            wsLogger.info("Device is using legacy signaling");
 
             // Now we don't need the websocket connection anymore, as we've established that we need to use the legacy signaling
             // which does everything over HTTP(at least from the perspective of the client)
             isLegacySignalingEnabled.current = true;
             getWebSocket()?.close();
           } else {
-            console.log("[Websocket] Device is using new signaling");
+            wsLogger.info("Device is using new signaling");
             isLegacySignalingEnabled.current = false;
           }
           setupPeerConnection();
@@ -308,7 +320,7 @@ export default function KvmIdRoute() {
 
         if (!peerConnection) return;
         if (parsedMessage.type === "answer") {
-          console.log("[Websocket] Received answer");
+          wsLogger.info("Received answer");
           const readyForOffer =
             // If we're making an offer, we don't want to accept an answer
             !makingOffer &&
@@ -322,10 +334,7 @@ export default function KvmIdRoute() {
 
           // Set so we don't accept an answer while we're setting the remote description
           isSettingRemoteAnswerPending.current = parsedMessage.type === "answer";
-          console.log(
-            "[Websocket] Setting remote answer pending",
-            isSettingRemoteAnswerPending.current,
-          );
+          wsLogger.info("Setting remote answer pending", isSettingRemoteAnswerPending.current);
 
           const sd = atob(parsedMessage.data);
           const remoteSessionDescription = JSON.parse(sd);
@@ -338,8 +347,8 @@ export default function KvmIdRoute() {
           // Reset the remote answer pending flag
           isSettingRemoteAnswerPending.current = false;
         } else if (parsedMessage.type === "new-ice-candidate") {
-          console.log("[Websocket] Received new-ice-candidate");
           const candidate = parsedMessage.data;
+          wsLogger.info("Received new-ice-candidate", { candidate});
           peerConnection.addIceCandidate(candidate);
         }
       },
@@ -366,7 +375,7 @@ export default function KvmIdRoute() {
       // In device mode, old devices wont server this JS, and on newer devices legacy mode wont be enabled
       const sessionUrl = `${CLOUD_API}/webrtc/session`;
 
-      console.log("Trying to get remote session description");
+      logger.info("Trying to get remote session description");
       setLoadingMessage(
         `Getting remote session description...  ${signalingAttempts.current > 0 ? `(attempt ${signalingAttempts.current + 1})` : ""}`,
       );
@@ -379,12 +388,12 @@ export default function KvmIdRoute() {
       const json = await res.json();
       if (res.status === 401) return navigate(isOnDevice ? "/login-local" : "/login");
       if (!res.ok) {
-        console.error("Error getting SDP", { status: res.status, json });
+        logger.error("Error getting SDP", { status: res.status, json });
         cleanupAndStopReconnecting();
         return;
       }
 
-      console.log("Successfully got Remote Session Description. Setting.");
+      logger.info("Successfully got Remote Session Description. Setting.");
       setLoadingMessage("Setting remote session description...");
 
       const decodedSd = atob(json.sd);
@@ -395,13 +404,15 @@ export default function KvmIdRoute() {
   );
 
   const setupPeerConnection = useCallback(async () => {
-    console.log("[setupPeerConnection] Setting up peer connection");
+    const l = logger.getSubLogger({ name: "setupPeerConnection" });
+
+    l.info("Setting up peer connection");
     setConnectionFailed(false);
     setLoadingMessage("Connecting to device...");
 
     let pc: RTCPeerConnection;
     try {
-      console.log("[setupPeerConnection] Creating peer connection");
+      l.info("Creating peer connection");
       setLoadingMessage("Creating peer connection...");
       pc = new RTCPeerConnection({
         // We only use STUN or TURN servers if we're in the cloud
@@ -411,10 +422,10 @@ export default function KvmIdRoute() {
       });
 
       setPeerConnectionState(pc.connectionState);
-      console.log("[setupPeerConnection] Peer connection created", pc);
+      l.info("Peer connection created", pc);
       setLoadingMessage("Setting up connection to device...");
     } catch (e) {
-      console.error(`[setupPeerConnection] Error creating peer connection: ${e}`);
+      l.error(`Error creating peer connection: ${e}`);
       setTimeout(() => {
         cleanupAndStopReconnecting();
       }, 1000);
@@ -423,13 +434,13 @@ export default function KvmIdRoute() {
 
     // Set up event listeners and data channels
     pc.onconnectionstatechange = () => {
-      console.log("[setupPeerConnection] Connection state changed", pc.connectionState);
+      l.info("Connection state changed", pc.connectionState);
       setPeerConnectionState(pc.connectionState);
     };
 
     pc.onnegotiationneeded = async () => {
       try {
-        console.log("[setupPeerConnection] Creating offer");
+        l.info("Creating offer");
         makingOffer.current = true;
 
         const offer = await pc.createOffer();
@@ -439,13 +450,10 @@ export default function KvmIdRoute() {
         if (isNewSignalingEnabled) {
           sendWebRTCSignal("offer", { sd: sd });
         } else {
-          console.log("Legacy signanling. Waiting for ICE Gathering to complete...");
+          l.info("Legacy signanling. Waiting for ICE Gathering to complete...");
         }
       } catch (e) {
-        console.error(
-          `[setupPeerConnection] Error creating offer: ${e}`,
-          new Date().toISOString(),
-        );
+        l.error(`Error creating offer`, { date: new Date().toISOString(), error: e });
         cleanupAndStopReconnecting();
       } finally {
         makingOffer.current = false;
@@ -461,7 +469,7 @@ export default function KvmIdRoute() {
     pc.onicegatheringstatechange = event => {
       const pc = event.currentTarget as RTCPeerConnection;
       if (pc.iceGatheringState === "complete") {
-        console.log("ICE Gathering completed");
+        l.info("ICE Gathering completed");
         setLoadingMessage("ICE Gathering completed");
 
         if (isLegacySignalingEnabled.current) {
@@ -469,7 +477,7 @@ export default function KvmIdRoute() {
           legacyHTTPSignaling(pc);
         }
       } else if (pc.iceGatheringState === "gathering") {
-        console.log("ICE Gathering Started");
+        l.info("ICE Gathering Started");
         setLoadingMessage("Gathering ICE candidates...");
       }
     };
@@ -506,7 +514,7 @@ export default function KvmIdRoute() {
 
   useEffect(() => {
     if (peerConnectionState === "failed") {
-      console.log("Connection failed, closing peer connection");
+      logger.info("Connection failed, closing peer connection");
       cleanupAndStopReconnecting();
     }
   }, [peerConnectionState, cleanupAndStopReconnecting]);
@@ -609,13 +617,13 @@ export default function KvmIdRoute() {
     }
 
     if (resp.method === "networkState") {
-      console.log("Setting network state", resp.params);
+      logger.info("Setting network state", resp.params);
       setNetworkState(resp.params as NetworkState);
     }
 
     if (resp.method === "keyboardLedState") {
       const ledState = resp.params as KeyboardLedState;
-      console.log("Setting keyboard led state", ledState);
+      logger.info("Setting keyboard led state", ledState);
       setKeyboardLedState(ledState);
       setKeyboardLedStateSyncAvailable(true);
     }
@@ -660,20 +668,20 @@ export default function KvmIdRoute() {
   useEffect(() => {
     if (rpcDataChannel?.readyState !== "open") return;
     if (keyboardLedState !== undefined) return;
-    console.log("Requesting keyboard led state");
+    logger.info("Requesting keyboard led state");
 
     send("getKeyboardLedState", {}, resp => {
       if ("error" in resp) {
         // -32601 means the method is not supported
         if (resp.error.code === -32601) {
           setKeyboardLedStateSyncAvailable(false);
-          console.error("Failed to get keyboard led state, disabling sync", resp.error);
+          logger.error("Failed to get keyboard led state, disabling sync", resp.error);
         } else {
-          console.error("Failed to get keyboard led state", resp.error);
+          logger.error("Failed to get keyboard led state", resp.error);
         }
         return;
       }
-      console.log("Keyboard led state", resp.result);
+      logger.info("Keyboard led state", resp.result);
       setKeyboardLedState(resp.result as KeyboardLedState);
       setKeyboardLedStateSyncAvailable(true);
     });
@@ -691,7 +699,7 @@ export default function KvmIdRoute() {
   useEffect(() => {
     if (!diskChannel || !file) return;
     diskChannel.onmessage = async e => {
-      console.log("Received", e.data);
+      logger.info("Received", e.data);
       const data = JSON.parse(e.data);
       const blob = file.slice(data.start, data.end);
       const buf = await blob.arrayBuffer();
diff --git a/ui/src/routes/devices.tsx b/ui/src/routes/devices.tsx
index b6af0f0..a5d083d 100644
--- a/ui/src/routes/devices.tsx
+++ b/ui/src/routes/devices.tsx
@@ -10,7 +10,7 @@ import { LinkButton } from "@components/Button";
 import { User } from "@/hooks/stores";
 import { checkAuth } from "@/main";
 import { CLOUD_API } from "@/ui.config";
-
+import { logger } from "@/log";
 interface LoaderData {
   devices: { id: string; name: string; online: boolean; lastSeen: string }[];
   user: User;
@@ -29,7 +29,7 @@ const loader = async () => {
     const { devices } = await res.json();
     return { devices, user };
   } catch (e) {
-    console.error(e);
+    logger.error("Error loading devices", e);
     return { devices: [] };
   }
 };
diff --git a/ui/src/routes/login-local.tsx b/ui/src/routes/login-local.tsx
index 9258639..a3695d6 100644
--- a/ui/src/routes/login-local.tsx
+++ b/ui/src/routes/login-local.tsx
@@ -11,12 +11,12 @@ import { Button } from "@components/Button";
 import LogoBlueIcon from "@/assets/logo-blue.png";
 import LogoWhiteIcon from "@/assets/logo-white.svg";
 import { DEVICE_API } from "@/ui.config";
+import { logger } from "@/log";
 
 import api from "../api";
 import ExtLink from "../components/ExtLink";
 
 import { DeviceStatus } from "./welcome-local";
-
 const loader = async () => {
   const res = await api
     .GET(`${DEVICE_API}/device/status`)
@@ -44,7 +44,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
       return { error: "Invalid password" };
     }
   } catch (error) {
-    console.error(error);
+    logger.error("An error occurred while logging in", error);
     return { error: "An error occurred while logging in" };
   }
 };
diff --git a/ui/src/routes/welcome-local.mode.tsx b/ui/src/routes/welcome-local.mode.tsx
index 06ca62a..d6dfe80 100644
--- a/ui/src/routes/welcome-local.mode.tsx
+++ b/ui/src/routes/welcome-local.mode.tsx
@@ -7,13 +7,13 @@ import { Button } from "@components/Button";
 import LogoBlueIcon from "@/assets/logo-blue.png";
 import LogoWhiteIcon from "@/assets/logo-white.svg";
 import { DEVICE_API } from "@/ui.config";
+  import { logger } from "@/log";
 
 import { GridCard } from "../components/Card";
 import { cx } from "../cva.config";
 import api from "../api";
 
 import { DeviceStatus } from "./welcome-local";
-
 const loader = async () => {
   const res = await api
     .GET(`${DEVICE_API}/device/status`)
@@ -39,7 +39,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
       });
       return redirect("/");
     } catch (error) {
-      console.error("Error setting authentication mode:", error);
+      logger.error("Error setting authentication mode:", error);
       return { error: "An error occurred while setting the authentication mode" };
     }
   }
diff --git a/ui/src/routes/welcome-local.password.tsx b/ui/src/routes/welcome-local.password.tsx
index 4b2c05d..165df6b 100644
--- a/ui/src/routes/welcome-local.password.tsx
+++ b/ui/src/routes/welcome-local.password.tsx
@@ -10,11 +10,11 @@ import { Button } from "@components/Button";
 import LogoBlueIcon from "@/assets/logo-blue.png";
 import LogoWhiteIcon from "@/assets/logo-white.svg";
 import { DEVICE_API } from "@/ui.config";
+import { logger } from "@/log";
 
 import api from "../api";
 
 import { DeviceStatus } from "./welcome-local";
-
 const loader = async () => {
   const res = await api
     .GET(`${DEVICE_API}/device/status`)
@@ -45,7 +45,7 @@ const action = async ({ request }: ActionFunctionArgs) => {
       return { error: "Failed to set password" };
     }
   } catch (error) {
-    console.error("Error setting password:", error);
+    logger.error("Error setting password:", error);
     return { error: "An error occurred while setting the password" };
   }
 };