You know that moment when you’re debugging an Electron app at 11:30 PM, and your console is drowning in a sea of [IPC Main] Resizing window to 350x700 messages? Like trying to find a specific conversation in a crowded restaurant where everyone’s talking at once.
That was me last week, squinting at terminal output that looked like someone had dumped a dictionary into a blender. Every window resize, every hotkey press, every “Looking for model at…” message cluttering up the important stuff. I needed signal, not noise.
TLDR: What You’ll Gain
- Clean console output that only shows what matters
- 15-minute implementation using environment variables
- Systematic approach to logging levels that scales
- Copy-paste patterns for any TypeScript/Electron project
- Bottom Line: Stop drowning in debug noise and start seeing actual problems
The Problem That Costs Us Time
Here’s the thing about building an Electron app with real-time audio processing: everything logs. The window resizes when settings change. Hotkeys fire constantly during testing. Binary path checks happen on every startup. Recording state changes flood the console.
When you’re trying to debug why your silence timeout setting isn’t persisting (spoiler: it was a React state management issue), the last thing you need is 47 lines of “Resizing window” messages obscuring the actual problem.
Most developers solve this with console.log archaeology - commenting out logs, adding temporary ones, then forgetting to clean them up. It’s like debugging with a blindfold on, taking it off occasionally to peek at what’s happening.
The Approach: Environment-Driven Verbose Mode
The solution isn’t removing logs - it’s making them contextual. We need two modes:
- Normal mode: Only show errors, warnings, and critical state changes
- Verbose mode: Show everything when we’re actively debugging
The pattern is simple: check an environment variable before logging non-critical messages.
private verboseLogging: boolean = process.env.VOICECATCHER_VERBOSE === 'true';
private verboseLog(message: string): void {
if (this.verboseLogging) {
this.logger.log(message);
}
}
Then replace noisy logs:
// Before: Always logs
this.logger.log(`Resizing window to ${width}x${height}`);
// After: Only logs in verbose mode
this.verboseLog(`Resizing window to ${width}x${height}`);
The Implementation
I identified nine categories of logs that were cluttering our debugging:
- Window resizing - Every settings panel toggle
- Hotkey pressed events - Constant during testing
- Recording save locations - Useful but not critical
- Binary file existence checks - Startup noise
- Model file lookups - More startup noise
- Recording start events - Verbose state tracking
- Recording state changes - Tray icon updates
- Audio function calls - Internal flow tracking
Here’s how I implemented it across our codebase:
IPC Handler (Window Management)
export class IpcHandler {
private verboseLogging: boolean = process.env.VOICECATCHER_VERBOSE === 'true';
private verboseLog(message: string): void {
if (this.verboseLogging) {
this.deps.logger.log(message);
}
}
// Window resize handler
ipcMain.on('window:resize', (_event, { width, height }) => {
const mainWindow = this.deps.getMainWindow();
if (mainWindow && !mainWindow.isDestroyed()) {
this.verboseLog(`[IPC Main] Resizing window to ${width}x${height}`);
// ... resize logic
}
});
}
Hotkey Manager (User Input)
export class ElectronHotkeyManager {
private verboseLogging: boolean = process.env.VOICECATCHER_VERBOSE === 'true';
private verboseLog(message: string): void {
if (this.verboseLogging) {
this.deps.logger.log(message);
}
}
public registerHotkey(hotkeyString: string): boolean {
const success = globalShortcut.register(electronHotkey, () => {
this.verboseLog(`Electron hotkey ${electronHotkey} pressed`);
// ... hotkey logic
});
}
}
Binary Paths (System Integration)
function debugLog(message: string) {
// Only log if verbose mode is enabled
if (process.env.VOICECATCHER_VERBOSE !== 'true') {
return;
}
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [BINARY_PATHS] ${message}`;
console.log(logMessage);
}
Recording Controller (Core Logic)
export class RecordingController {
private verboseLogging: boolean = process.env.VOICECATCHER_VERBOSE === 'true';
private verboseLog(message: string): void {
if (this.verboseLogging) {
this.logger.log(message);
}
}
public async handleHotkeyInteraction(): Promise<boolean> {
this.verboseLog(`Will save recording to: ${tempAudioPath}`);
this.verboseLog(
`🎙️ RECORDING START - Using cached settings: silenceTimeoutMs=${this.settings.silenceTimeoutMs}`
);
this.verboseLog('Calling recordAudio function...');
// ... recording logic
}
}
Main Process (State Management)
const verboseLogging: boolean = process.env.VOICECATCHER_VERBOSE === 'true';
function verboseLog(message: string): void {
if (verboseLogging) {
mainLog(message);
}
}
recordingController.setCallbacks({
onStatusChange: (status: 'idle' | 'recording' | 'transcribing') => {
verboseLog(`Recording state changed to: ${status}`);
// ... state change logic
},
});
The Human Reality
I’ll be honest - this felt like busy work at first. “Just add some if statements,” I thought. “How hard could it be?”
Then I started actually implementing it and realized I had verbose logs scattered across eight different files, each with their own logging patterns. Some used class methods, others used standalone functions. Some had existing verbose systems, others didn’t.
The breakthrough came when I established the pattern: every class gets a verboseLogging boolean and a verboseLog method. Standalone functions check the environment variable directly. Consistency over cleverness.
The moment I ran the app in normal mode and saw a clean console with just the essential messages? That’s when it clicked. This wasn’t about the code - it was about cognitive load. Clean logs mean faster debugging, which means shipping features instead of hunting ghosts.
The Business Impact
Time Saved: What used to be 10 minutes of scrolling through logs to find the actual error is now immediate. That’s 2+ hours per week just in debugging efficiency.
Development Velocity: Clean logs mean faster iteration cycles. When you can see problems immediately, you fix them immediately.
Onboarding: New developers (or future me) can understand what’s happening without drowning in implementation details.
Production Debugging: When something breaks in production, verbose mode gives us surgical precision without the noise.
ROI Analysis: 15 minutes of implementation saves 2+ hours weekly. That’s 100+ hours annually for a solo builder - equivalent to 2.5 weeks of focused development time.
What This Means for Us
This is bigger than just logging. It’s about building systems that scale with complexity instead of drowning in it.
Every feature we add generates more logs. Every integration creates more noise. Without intentional information architecture, our debugging experience degrades with every commit.
The verbose logging pattern gives us a framework for managing complexity:
- Default to quiet: Only show what matters for normal operation
- Opt into noise: Enable detailed logging when actively debugging
- Consistent patterns: Same approach across all components
- Environment-driven: No code changes to switch modes
For solo builders, this is competitive advantage. While other developers are drowning in their own logs, we’re shipping features because we can see problems clearly.
Copy-Paste Implementation Guide
1. Add to package.json:
{
"scripts": {
"start:verbose": "VOICECATCHER_VERBOSE=true npm run start",
"dev:verbose": "VOICECATCHER_VERBOSE=true npm run dev"
}
}
2. Class-based pattern:
export class YourClass {
private verboseLogging: boolean = process.env.YOUR_APP_VERBOSE === 'true';
private verboseLog(message: string): void {
if (this.verboseLogging) {
this.logger.log(message);
}
}
public someMethod(): void {
this.verboseLog('Detailed implementation step');
// ... your logic
}
}
3. Function-based pattern:
function debugLog(message: string) {
if (process.env.YOUR_APP_VERBOSE !== 'true') {
return;
}
console.log(`[DEBUG] ${message}`);
}
4. Usage:
# Normal mode - clean output
npm run start
# Verbose mode - everything
npm run start:verbose
Decision Framework: When to Use Verbose Logging
Always verbose:
- Implementation details (function calls, state transitions)
- User interaction tracking (clicks, hovers, inputs)
- System integration checks (file existence, path resolution)
- Performance metrics and timing data
Never verbose:
- Errors and warnings (always show these)
- User-facing status updates
- Critical state changes
- Security-related events
Context-dependent:
- Network requests (verbose for debugging, quiet for normal)
- File operations (verbose for development, quiet for production)
- Configuration loading (verbose during setup, quiet during runtime)
The goal isn’t perfect categorization - it’s consistent application of the pattern. When in doubt, make it verbose. You can always promote important logs to normal mode later.
This 15-minute investment in logging discipline pays dividends every single day. Clean logs mean clear thinking, which means better software.
Now go implement this in your own projects. Your future debugging self will thank you.