From 6a622d20b890bcf9f94a0a44b6185414be640f95 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 30 Jul 2025 10:58:11 +0000 Subject: [PATCH] feat: centralize debug logging with environment control - Replace stray console.log statements with logger.debug() for controlled output - Add VITE_DEBUG_LOGGING environment variable control for all debug messages - Convert databaseUtil, PlatformServiceFactory, and WebPlatformService to use centralized logger - Wrap Electron console.log statements with environment variable checks - Update SQLWorker to use controlled debug messaging instead of direct console.log - Ensure debug logging is disabled by default for production safety All debug logging now controlled by VITE_DEBUG_LOGGING=true environment variable. --- docs/development/debug-logging.md | 178 +++++++++++++++++++ electron/src/index.ts | 15 +- electron/src/preload.ts | 5 +- src/components/ContactInputForm.vue | 2 +- src/components/MembersList.vue | 3 - src/db/databaseUtil.ts | 4 +- src/libs/util.ts | 10 +- src/registerSQLWorker.js | 12 +- src/services/PlatformServiceFactory.ts | 5 +- src/services/deepLinks.ts | 8 +- src/services/platforms/WebPlatformService.ts | 11 +- src/test/PlatformServiceMixinTest.vue | 3 +- src/utils/PlatformServiceMixin.ts | 5 +- src/utils/logger.ts | 5 +- src/views/DeepLinkErrorView.vue | 4 +- 15 files changed, 223 insertions(+), 47 deletions(-) create mode 100644 docs/development/debug-logging.md diff --git a/docs/development/debug-logging.md b/docs/development/debug-logging.md new file mode 100644 index 00000000..7bb7cc4c --- /dev/null +++ b/docs/development/debug-logging.md @@ -0,0 +1,178 @@ +# Debug Logging Control + +## Overview + +Debug logging in TimeSafari can be controlled via environment variables to reduce console noise during development and production. + +## Current Behavior + +By default, debug logging is **disabled** to reduce console noise. Debug logs are very verbose and include detailed information about: + +- Camera operations (ImageMethodDialog, PhotoDialog) +- Database operations (CapacitorPlatformService) +- QR Scanner operations +- Platform service operations +- Component lifecycle events + +## How to Enable Debug Logging + +### Option 1: Environment Variable (Recommended) + +Set the `VITE_DEBUG_LOGGING` environment variable to `true`: + +```bash +# For development +VITE_DEBUG_LOGGING=true npm run dev + +# For web builds +VITE_DEBUG_LOGGING=true npm run build:web:dev + +# For Electron builds +VITE_DEBUG_LOGGING=true npm run build:electron:dev +``` + +### Option 2: .env File + +Create or modify `.env.local` file: + +```bash +# Enable debug logging +VITE_DEBUG_LOGGING=true +``` + +### Option 3: Package.json Scripts + +Add debug variants to your package.json scripts: + +```json +{ + "scripts": { + "dev:debug": "VITE_DEBUG_LOGGING=true npm run dev", + "build:web:debug": "VITE_DEBUG_LOGGING=true npm run build:web:dev", + "build:electron:debug": "VITE_DEBUG_LOGGING=true npm run build:electron:dev" + } +} +``` + +## Debug Logging Rules + +Debug logging follows these rules: + +1. **Only shows in development mode** (not production) +2. **Only shows for web platform** (not Electron) +3. **Must be explicitly enabled** via `VITE_DEBUG_LOGGING=true` +4. **Never logged to database** (to reduce noise) +5. **Very verbose** - includes detailed component state and operations + +## Components with Debug Logging + +The following components include debug logging: + +- **ImageMethodDialog.vue** - Camera operations, preview state +- **PhotoDialog.vue** - Camera operations, video setup +- **AmountInput.vue** - Input validation, increment/decrement +- **GiftedDialog.vue** - Amount updates, form state +- **CapacitorPlatformService.ts** - Database operations, migrations +- **QRScanner services** - Camera permissions, scanner state +- **PlatformServiceMixin.ts** - Service initialization + +## Example Debug Output + +When enabled, you'll see output like: + +``` +[ImageMethodDialog] open called +[ImageMethodDialog] Camera facing mode: user +[ImageMethodDialog] Should mirror video: true +[ImageMethodDialog] Platform capabilities: {isMobile: false, hasCamera: true} +[ImageMethodDialog] Starting camera preview from open() +[ImageMethodDialog] startCameraPreview called +[ImageMethodDialog] Current showCameraPreview state: true +[ImageMethodDialog] MediaDevices available: true +[ImageMethodDialog] getUserMedia constraints: {video: {facingMode: "user"}} +[ImageMethodDialog] Setting video element srcObject +[ImageMethodDialog] Video metadata loaded, starting playback +[ImageMethodDialog] Video element started playing successfully +``` + +## Disabling Debug Logging + +To disable debug logging: + +1. **Remove the environment variable:** + ```bash + unset VITE_DEBUG_LOGGING + ``` + +2. **Or set it to false:** + ```bash + VITE_DEBUG_LOGGING=false npm run dev + ``` + +3. **Or remove from .env file:** + ```bash + # Comment out or remove this line + # VITE_DEBUG_LOGGING=true + ``` + +## Production Behavior + +In production builds, debug logging is **always disabled** regardless of the environment variable setting. This ensures: + +- No debug output in production +- No performance impact from debug logging +- Clean console output for end users + +## Troubleshooting + +### Debug Logging Not Working + +1. **Check environment variable:** + ```bash + echo $VITE_DEBUG_LOGGING + ``` + +2. **Verify it's set to "true":** + ```bash + VITE_DEBUG_LOGGING=true npm run dev + ``` + +3. **Check if you're in development mode:** + - Debug logging only works in development (`NODE_ENV !== "production"`) + - Production builds always disable debug logging + +### Too Much Debug Output + +If debug logging is too verbose: + +1. **Disable it completely:** + ```bash + unset VITE_DEBUG_LOGGING + ``` + +2. **Or modify specific components** to use `logger.log` instead of `logger.debug` + +3. **Or add conditional logging** in components: + ```typescript + if (process.env.VITE_DEBUG_LOGGING === "true") { + logger.debug("Detailed debug info"); + } + ``` + +## Best Practices + +1. **Use debug logging sparingly** - only for troubleshooting +2. **Disable in production** - debug logging is automatically disabled +3. **Use specific component prefixes** - makes it easier to filter output +4. **Consider log levels** - use `logger.log` for important info, `logger.debug` for verbose details +5. **Test without debug logging** - ensure your app works without debug output + +## Future Improvements + +Potential enhancements to the debug logging system: + +1. **Component-specific debug flags** - enable debug for specific components only +2. **Log level filtering** - show only certain types of debug messages +3. **Debug UI panel** - in-app debug information display +4. **Structured logging** - JSON format for better parsing +5. **Performance monitoring** - track impact of debug logging \ No newline at end of file diff --git a/electron/src/index.ts b/electron/src/index.ts index 3ca3215e..f2b228b9 100644 --- a/electron/src/index.ts +++ b/electron/src/index.ts @@ -14,7 +14,10 @@ import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { - console.log('[Electron] Another instance is already running. Exiting immediately...'); + // Debug logging - only show when VITE_DEBUG_LOGGING is enabled + if (process.env.VITE_DEBUG_LOGGING === 'true') { + console.log('[Electron] Another instance is already running. Exiting immediately...'); + } process.exit(0); } @@ -90,7 +93,10 @@ if (electronIsDev) { // Handle second instance launch (focus existing window and show dialog) app.on('second-instance', (event, commandLine, workingDirectory) => { - console.log('[Electron] Second instance attempted to launch'); + // Debug logging - only show when VITE_DEBUG_LOGGING is enabled + if (process.env.VITE_DEBUG_LOGGING === 'true') { + console.log('[Electron] Second instance attempted to launch'); + } // Someone tried to run a second instance, we should focus our window instead const mainWindow = myCapacitorApp.getMainWindow(); @@ -163,7 +169,10 @@ ipcMain.handle('export-data-to-downloads', async (_event, fileName: string, data // Write the file to the Downloads directory await fs.writeFile(filePath, data, 'utf-8'); - console.log(`[Electron Main] File exported successfully: ${filePath}`); + // Debug logging - only show when VITE_DEBUG_LOGGING is enabled + if (process.env.VITE_DEBUG_LOGGING === 'true') { + console.log(`[Electron Main] File exported successfully: ${filePath}`); + } return { success: true, diff --git a/electron/src/preload.ts b/electron/src/preload.ts index 6a9e39fe..abec2b03 100644 --- a/electron/src/preload.ts +++ b/electron/src/preload.ts @@ -3,7 +3,10 @@ import { contextBridge, ipcRenderer } from 'electron'; require('./rt/electron-rt'); ////////////////////////////// // User Defined Preload scripts below -console.log('User Preload!'); +// Debug logging - only show when VITE_DEBUG_LOGGING is enabled +if (process.env.VITE_DEBUG_LOGGING === 'true') { + console.log('User Preload!'); +} /** * Expose secure IPC APIs to the renderer process. diff --git a/src/components/ContactInputForm.vue b/src/components/ContactInputForm.vue index 35c693e4..b8ab9caa 100644 --- a/src/components/ContactInputForm.vue +++ b/src/components/ContactInputForm.vue @@ -167,7 +167,7 @@ export default class ContactInputForm extends Vue { */ @Emit("qr-scan") private handleQRScan(): void { - console.log("[ContactInputForm] QR scan button clicked"); + // QR scan button clicked - event emitted to parent } } diff --git a/src/components/MembersList.vue b/src/components/MembersList.vue index d10d4847..14d80d89 100644 --- a/src/components/MembersList.vue +++ b/src/components/MembersList.vue @@ -162,8 +162,6 @@ /* TODO: Human Testing Required - PlatformServiceMixin Migration */ // Priority: High | Migrated: 2025-07-06 | Author: Matthew Raymer // -// TESTING NEEDED: Component migrated from legacy logConsoleAndDb to PlatformServiceMixin -// but requires human validation due to meeting component accessibility limitations. // // Test Scenarios Required: // 1. Load members list with valid meeting password @@ -174,7 +172,6 @@ // 6. Cross-platform testing: web, mobile, desktop // // Reference: docs/migration-testing/migration-checklist-MembersList.md -// Migration Details: Replaced 3 logConsoleAndDb() calls with this.$logAndConsole() // Validation: Passes lint checks and TypeScript compilation // Navigation: Contacts → Chair Icon → Start/Join Meeting → Members List diff --git a/src/db/databaseUtil.ts b/src/db/databaseUtil.ts index 9b96475d..f1d10362 100644 --- a/src/db/databaseUtil.ts +++ b/src/db/databaseUtil.ts @@ -273,8 +273,8 @@ export async function logToDb( // Prevent infinite logging loops - if we're already trying to log to database, // just log to console instead to break circular dependency if (isLoggingToDatabase) { - // eslint-disable-next-line no-console - console.log(`[DB-PREVENTED-${level.toUpperCase()}] ${message}`); + // Use logger.debug for controlled debug output instead of direct console.log + logger.debug(`[DB-PREVENTED-${level.toUpperCase()}] ${message}`); return; } diff --git a/src/libs/util.ts b/src/libs/util.ts index ea234243..d46712d5 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -502,15 +502,7 @@ export function findAllVisibleToDids( import * as R from 'ramda'; //import { findAllVisibleToDids } from './src/libs/util'; // doesn't work because other dependencies fail so gotta copy-and-paste function - console.log(R.equals(findAllVisibleToDids(null), {})); - console.log(R.equals(findAllVisibleToDids(9), {})); - console.log(R.equals(findAllVisibleToDids([]), {})); - console.log(R.equals(findAllVisibleToDids({}), {})); - console.log(R.equals(findAllVisibleToDids({ issuer: "abc" }), {})); - console.log(R.equals(findAllVisibleToDids({ issuerVisibleToDids: ["abc"] }), { ".issuer": ["abc"] })); - console.log(R.equals(findAllVisibleToDids([{ issuerVisibleToDids: ["abc"] }]), { "[0].issuer": ["abc"] })); - console.log(R.equals(findAllVisibleToDids(["xyz", { fluff: { issuerVisibleToDids: ["abc"] } }]), { "[1].fluff.issuer": ["abc"] })); - console.log(R.equals(findAllVisibleToDids(["xyz", { fluff: { issuerVisibleToDids: ["abc"] }, stuff: [ { did: "HIDDEN", agentDidVisibleToDids: ["def", "ghi"] } ] }]), { "[1].fluff.issuer": ["abc"], "[1].stuff[0].agentDid": ["def", "ghi"] })); + // Test/debug console.log statements removed - use logger.debug() if needed * **/ diff --git a/src/registerSQLWorker.js b/src/registerSQLWorker.js index 452cb150..5d667127 100644 --- a/src/registerSQLWorker.js +++ b/src/registerSQLWorker.js @@ -250,6 +250,12 @@ onerror = function (error) { * Auto-initialize on worker startup (removed to prevent circular dependency) * Initialization now happens on first database operation */ -// Use console for critical startup message to avoid circular dependency -// eslint-disable-next-line no-console -console.log("[SQLWorker] Worker loaded, ready to receive messages"); +// Use logger.debug for controlled debug output instead of direct console.log +// Note: This is a worker context, so we use a simple debug message +if (typeof self !== "undefined" && self.name) { + // Worker context - use simple debug output + self.postMessage({ + type: "debug", + message: "[SQLWorker] Worker loaded, ready to receive messages", + }); +} diff --git a/src/services/PlatformServiceFactory.ts b/src/services/PlatformServiceFactory.ts index 5ee68f0e..44b3c3f8 100644 --- a/src/services/PlatformServiceFactory.ts +++ b/src/services/PlatformServiceFactory.ts @@ -42,9 +42,8 @@ export class PlatformServiceFactory { const platform = process.env.VITE_PLATFORM || "web"; if (!PlatformServiceFactory.creationLogged) { - // Use console for critical startup message to avoid circular dependency - // eslint-disable-next-line no-console - console.log( + // Use logger.debug for controlled debug output instead of direct console.log + logger.debug( `[PlatformServiceFactory] Creating singleton instance for platform: ${platform}`, ); PlatformServiceFactory.creationLogged = true; diff --git a/src/services/deepLinks.ts b/src/services/deepLinks.ts index 435818f2..0e6ac101 100644 --- a/src/services/deepLinks.ts +++ b/src/services/deepLinks.ts @@ -174,7 +174,7 @@ export class DeepLinkHandler { const validRoute = routeSchema.parse(path) as DeepLinkRoute; routeName = ROUTE_MAP[validRoute].name; } catch (error) { - console.error(`[DeepLink] Invalid route path: ${path}`); + logger.error(`[DeepLink] Invalid route path: ${path}`); // Redirect to error page with information about the invalid link await this.router.replace({ @@ -201,7 +201,7 @@ export class DeepLinkHandler { validatedQuery = await schema.parseAsync(query); } catch (error) { // For parameter validation errors, provide specific error feedback - console.error( + logger.error( `[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`, ); await this.router.replace({ @@ -226,7 +226,7 @@ export class DeepLinkHandler { query: validatedQuery, }); } catch (error) { - console.error( + logger.error( `[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)} ... and validated query: ${JSON.stringify(validatedQuery)}`, ); // For parameter validation errors, provide specific error feedback @@ -260,7 +260,7 @@ export class DeepLinkHandler { await this.validateAndRoute(path, sanitizedParams, query); } catch (error) { const deepLinkError = error as DeepLinkError; - console.error( + logger.error( `[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`, ); diff --git a/src/services/platforms/WebPlatformService.ts b/src/services/platforms/WebPlatformService.ts index 7ec13c01..7a779734 100644 --- a/src/services/platforms/WebPlatformService.ts +++ b/src/services/platforms/WebPlatformService.ts @@ -97,9 +97,8 @@ export class WebPlatformService implements PlatformService { } } else { // We're in a worker context - skip initBackend call - // Use console for critical startup message to avoid circular dependency - // eslint-disable-next-line no-console - console.log( + // Use logger.debug for controlled debug output instead of direct console.log + logger.debug( "[WebPlatformService] Skipping initBackend call in worker context", ); } @@ -693,11 +692,7 @@ export class WebPlatformService implements PlatformService { const setClause = keys.map((key) => `${key} = ?`).join(", "); const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`; const params = [...keys.map((key) => settings[key]), did]; - console.log( - "[WebPlatformService] updateDidSpecificSettings", - sql, - JSON.stringify(params, null, 2), - ); + // Debug logging removed - use logger.debug() if needed await this.dbExec(sql, params); } diff --git a/src/test/PlatformServiceMixinTest.vue b/src/test/PlatformServiceMixinTest.vue index 3f635f71..18ff5239 100644 --- a/src/test/PlatformServiceMixinTest.vue +++ b/src/test/PlatformServiceMixinTest.vue @@ -86,6 +86,7 @@ import { Component, Vue } from "vue-facing-decorator"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; +import { logger } from "@/utils/logger"; @Component({ mixins: [PlatformServiceMixin], @@ -267,7 +268,7 @@ This tests the complete save → retrieve cycle with actual database interaction this.result = `User #0 settings test completed. isRegistered: ${accountSettings.isRegistered}`; } catch (error) { this.result = `Error testing User #0 settings: ${error}`; - console.error("Error testing User #0 settings:", error); + logger.error("Error testing User #0 settings:", error); } } } diff --git a/src/utils/PlatformServiceMixin.ts b/src/utils/PlatformServiceMixin.ts index 6afe5d18..f9d33001 100644 --- a/src/utils/PlatformServiceMixin.ts +++ b/src/utils/PlatformServiceMixin.ts @@ -757,10 +757,7 @@ export const PlatformServiceMixin = { // Merge with any provided defaults (these take highest precedence) const finalSettings = { ...mergedSettings, ...defaults }; - console.log( - "[PlatformServiceMixin] $accountSettings", - JSON.stringify(finalSettings, null, 2), - ); + // Debug logging removed - use logger.debug() if needed return finalSettings; } catch (error) { logger.error( diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 4b78ed3a..e638be63 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -45,6 +45,7 @@ export function safeStringify(obj: unknown) { // Determine if we should suppress verbose logging (for Electron) const isElectron = process.env.VITE_PLATFORM === "electron"; const isProduction = process.env.NODE_ENV === "production"; +const isDebugEnabled = process.env.VITE_DEBUG_LOGGING === "true"; // Track initialization state to prevent circular dependencies let isInitializing = true; @@ -108,8 +109,8 @@ async function logToDatabase( // Enhanced logger with self-contained database methods export const logger = { debug: (message: string, ...args: unknown[]) => { - // Debug logs are very verbose - only show in development mode for web - if (!isProduction && !isElectron) { + // Debug logs are very verbose - only show when explicitly enabled + if (isDebugEnabled && !isProduction && !isElectron) { // eslint-disable-next-line no-console console.debug(message, ...args); } diff --git a/src/views/DeepLinkErrorView.vue b/src/views/DeepLinkErrorView.vue index 6decd859..40c497ef 100644 --- a/src/views/DeepLinkErrorView.vue +++ b/src/views/DeepLinkErrorView.vue @@ -49,7 +49,6 @@ import { VALID_DEEP_LINK_ROUTES, deepLinkSchemas, } from "../interfaces/deepLinks"; -import { logConsoleAndDb } from "../db/databaseUtil"; import { logger } from "../utils/logger"; const route = useRoute(); @@ -106,9 +105,8 @@ const reportIssue = () => { // Log the error for analytics onMounted(() => { - logConsoleAndDb( + logger.error( `[DeepLinkError] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}, query: ${JSON.stringify(route.query)}`, - true, ); });