-
Notifications
You must be signed in to change notification settings - Fork 0
2.3 Logging System
ClioDeck has three logging-adjacent mechanisms, not two as an earlier version of this page said — and one of the three previously documented here turned out to be dead code.
-
Global console filter (
src/shared/console-filter.ts) — intercepts every rawconsole.*call, and is imported by bothsrc/renderer/src/main.tsxandsrc/main/index.ts. But its env-var branching (CLIODESK_DEBUG/CLIODESK_LOG_LEVEL) only has an observable effect in the main process: theBrowserWindowis created withcontextIsolation: true, nodeIntegration: false, sandbox: true(src/main/index.ts, unconditionally, dev and prod alike), so the renderer has noprocessglobal at runtime — this file's ownisProduction()/isDebugEnabled()checks (typeof process !== 'undefined') can never be true there, env vars or not. Separately, Vite'sesbuild.puresetting (see below) already strips rawconsole.log/info/debugcalls from the production renderer bundle regardless.restoreConsole/rawConsole/getFilterStatusare real exports of this file, but in the renderer they operate on a filter that was never actually engaged. -
Renderer logger (
src/renderer/src/utils/logger.ts) — the structured logger actually imported throughout the renderer (stores, components:MainLayout.tsx,similarityStore.ts,editorStore.ts, and more —grep -rn "import { logger }" src/renderer/shows every hit resolves here). See "Renderer Logger (for Developers)" below for its real API. -
Main-process logger (
src/main/utils/logger.ts) — used throughout the main process (IPC handlers, services). Different API (logger.info(module, action, data)), different output format ([2026-…] INFO [ipc] fs:read-directory {...}), and — important — it does not readCLIODESK_LOG_LEVELor any other env var. It hardcodeslevel = 'info'and nothing in the codebase ever calls itssetLevel(). SettingCLIODESK_LOG_LEVEL=debugraises verbosity for the console filter only; main-processdebug-level logs from IPC handlers stay suppressed regardless.
@shared/logger (src/shared/logger.ts) is dead code. An earlier
version of this page documented it as "the" renderer logger, with a
logger.debug(context, message, data) API and a createContextLogger()
helper. Neither has any call site anywhere in the repo outside the file's
own definition (grep -rln "@shared/logger" src/ backend/ returns only
src/shared/logger.ts itself) — every real renderer import goes through
src/renderer/src/utils/logger.ts instead, a different file with a
different API. If you see this page's older text elsewhere describing
logger.createContextLogger, it no longer reflects real code.
If you're debugging something in src/main/ (IPC, services), the env vars below won't help — there is currently no way to raise that logger's level short of editing src/main/utils/logger.ts.
| Environment | console.log | console.info | console.warn | console.error |
|---|---|---|---|---|
| Development | Shown | Shown | Shown | Shown |
| Production | Filtered | Filtered | Shown | Shown |
In production, only console.warn and console.error are displayed to reduce console noise.
⚠️ These env vars do NOT revive the renderer logger'slog/info/debugcalls in a packaged build — only rawconsole.*calls. The renderer logger's prod-gate (isProd = import.meta.env?.PROD === true,src/renderer/src/utils/logger.ts:28) is a build-time Vite constant, not a runtime check. Worse,vite.config.mts:79marksconsole.log,console.info, andconsole.debugaspurefor esbuild — in a production build, the real branch ofisProd ? noop : realCallis physically removed from the shipped bundle before the app ever runs. No environment variable can bring it back. If you're debugging a packaged build and relying onlogger.debug()/logger.log()/logger.info()calls (see "Renderer Logger" below), you will get silence — build from source in development mode instead, or add temporaryrawConsole.log()calls (see "Restoring Logs" below), which bypass this entirely.The env vars below genuinely do work for the separate console-filter mechanism (raw, un-wrapped
console.log/console.infocalls) — but only in the main process. The renderer is sandboxed (contextIsolation, nonodeIntegration) and has noprocessglobal, so the filter's own env-var checks can never fire there; and any raw console call that survives esbuild'spurestripping in a renderer production build is vanishingly rare to begin with. Either way, they never affect this renderer logger's own methods.
# macOS / Linux
CLIODESK_DEBUG=1 /path/to/ClioDeck.app/Contents/MacOS/ClioDeck
# Windows
set CLIODESK_DEBUG=1
"C:\Program Files\ClioDeck\ClioDeck.exe"# Available levels: debug, info, warn, error
CLIODESK_LOG_LEVEL=debug /path/to/ClioDeckDEBUG=1 /path/to/ClioDeckBy default, Electron DevTools are disabled in production.
To enable them, use the same environment variables:
# macOS / Linux
CLIODESK_DEBUG=1 /path/to/ClioDeck.app/Contents/MacOS/ClioDeck
# Windows
set CLIODESK_DEBUG=1
"C:\Program Files\ClioDeck\ClioDeck.exe"This enables both:
- Debug logs (
console.log,console.info) - Electron DevTools
For renderer code, use src/renderer/src/utils/logger.ts — this is the
one actually imported throughout the renderer, not @shared/logger:
import { logger } from '../utils/logger'; // path relative to caller
// Variadic, mirrors console.* — stripped in production (both by esbuild's
// `pure` annotation on console.log/info/debug in vite.config.mts, and by
// an explicit no-op gate inside the module itself)
logger.log('Debug message', { data });
logger.info('Important information');
logger.debug('Verbose detail');
// warn/error always emit, in prod too — diagnostic surface for triage
logger.warn('Warning');
logger.error('MyComponent', 'Something failed', error);
// Typed channels, kept for existing call sites
logger.ipc('fs:read-directory', { dirPath });
logger.store('similarityStore', 'analyze', { segments: 12 });
logger.component('SimilarityCard', 'Inserting citation', { zoteroKey });-
log/info/debugare compiled out of the production bundle entirely (not just hidden — removed), so expensive argument construction never runs in prod either -
warn/erroralways reach the console, in every environment - Typed channel helpers (
ipc/store/component/error) give consistent, greppable prefixes without extra ceremony
For code under src/main/ (IPC handlers, services), use the separate main-process logger instead:
import { logger } from '../../utils/logger.js';
logger.info('ipc', 'fs:read-directory', { dirPath });
logger.error('ipc', 'fs:read-directory', { error: error.message });
// Measuring duration:
const elapsed = logger.startTimer();
await someWork();
logger.info('ipc', 'pdf:index', { durationMs: elapsed() });Output: [2026-02-19T20:00:00.000Z] INFO [ipc] fs:read-directory { dirPath: "/home/user" }. Level defaults to info and cannot currently be changed at runtime — see the caveat above.
src/shared/
├── logger.ts # Dead code — no import anywhere outside this file
└── console-filter.ts # Global console.* filter, env-var-controlled (live)
src/renderer/src/utils/
└── logger.ts # Real renderer logger — what every renderer import resolves to
src/main/utils/
└── logger.ts # Main-process logger, fixed at 'info', no env var
The console filter is automatically imported at application startup:
- Main process:
src/main/index.ts - Renderer process:
src/renderer/src/main.tsx
Environment is automatically detected via:
process.env.NODE_ENV === 'production'process.env.ELECTRON_IS_PACKAGED === 'true'
import { restoreConsole, rawConsole } from '@shared/console-filter';
// Restore all console.*
restoreConsole();
// Or use rawConsole to bypass filter
rawConsole.log('This message will always be displayed');import { getFilterStatus } from '@shared/console-filter';
const status = getFilterStatus();
console.log(status);
// { isProduction: true, isDebugEnabled: false, isFiltering: true }