Troubleshooting & FAQ
A self-check list for the most common "why am I not seeing logs?" situations.
1. Console output causes infinite recursion / stack overflow
Symptom: the app hangs or throws Maximum call stack size exceeded as soon as you log.
Cause: a transport (or a formatter/plugin) calls console.log/console.error while you have redirected the console into lograil via redirectConsole(). Note: lograil does not redirect console.* by default — this only happens when you explicitly call logger.redirectConsole(). The redirected console.* calls back into the logger → loop.
Check
- Did you call
redirectConsole()? If so, remember it capturesconsole.*and re-emits them through the logger. Your own transports/plugins must not callconsole.*for the same levels. If you did not call it, skip this section — your recursion is coming from elsewhere (e.g. a transport callingconsole). - Inside
onErroror a custom transport, use the raw console (console.error) —lograildeliberately keeps a pre-redirect reference for exactly this, but if you capturedconsoleyourself, useprocess.stderror a dedicated stderr sink instead.
Fix: in error handlers and custom sinks, write to process.stderr/a file, or use a transport that doesn't round-trip through the redirected console.
2. Browser bundler error: can't resolve node:fs / node:path / node:os
Symptom: webpack / Vite / Rollup reports Module not found: Can't resolve 'node:fs' or Failed to resolve import "node:fs/promises" when bundling a page that imports lograil.
Cause: an outdated lograil version. Modern releases never resolve Node built-ins directly — they go through an internal shims layer that the browser field swaps for a browser stub at build time, so the import resolves cleanly (verified by bundler integration tests).
Fix: upgrade lograil to the latest version. If you're already on a recent release and still see the error, check that your bundler doesn't force-resolve browser mappings off, or that you aren't importing lograil from an unbundled node_modules path outside your build (e.g. a CDN that bypasses the browser field).
3. Renderer process produces no logs
Symptom: logging works in the main process but the renderer is silent (or renderer logs never reach the main-process renderer.log file).
Background: with the default Electron runtime this is automatic — the renderer logger forwards to the main process over IPC, and the main logger writes a separate renderer.{date}.log. You normally do nothing. If it isn't working, one of these is true: you opted out of the automatic wiring, you built the logger with a non-Electron runtime, or the renderer can't reach the electron module and silently fell back to the Web runtime.
Check
- Are you using the default
logger/createLogger()(no explicitruntime)? If so, forwarding + receiving are wired for you — noElectronIpcTransportorregisterIpcReceivercall is needed. - If you opted out (
createElectronRendererRuntime({ forwardToMain: false })orcreateElectronMainRuntime({ receiveFromRenderer: false })) or use a custom runtime: did you manually addElectronIpcTransportin the renderer and callregisterIpcReceiver((entry) => logger.ingestEntry(entry))in the main? - Is the channel correct? Both sides default to
lograil:log(LOGRAIL_CHANNEL); if you override it, override on both sides. - Packaged app with
contextIsolation: true+nodeIntegration: false? The renderer can'trequire('electron'), so lograil can't detect Electron and falls back to the Web runtime (console only). Use a preload bridge and pass it viacreateElectronRendererRuntime({ ipcRenderer })(see the Electron guide). - Rule out a level filter:
logger.setLevel('trace')temporarily.
See the Electron guide.
4. OTLP endpoint doesn't receive anything
Symptom: OtlpTransport is added but the collector shows no logs.
Check
- Is the endpoint reachable from the process that owns the transport? (In an Electron renderer the network egress may be restricted; prefer sending from the main process, or forward renderer logs via
ElectronIpcTransport.) OtlpTransportbatches (batchSize, default 100) and flushes asynchronously. For a quick test, callawait logger.flush()at the end of your run, or lowerbatchSize.fetchfailures are reported throughonError(default:console.error). Add an explicitonErrorto see HTTP/connection errors.- Confirm the collector URL: OTLP/HTTP JSON expects
…/v1/logs. A 404/401 usually means the path or auth header is wrong. - Firewall / proxy: the request is a normal
fetch; ensure egress is allowed.
5. Log files aren't rotating
Symptom: a single file keeps growing, or no dated files appear.
Check
FileTransportonly works where the NodefsAPI exists — the main process / Node runtime, not a browser or renderer Web Worker.rotate-timemode (default) writes<appName>.{YYYY-MM-DD}.0.log. Check the date stamp format and that the directory is writable. The directory is created automatically (mkdir -p).- Rotation triggers when a write would exceed
maxSize(rotate-size/single-truncate), or cross thehour/dayboundary (rotate-time), or when yourshouldRotatepredicate returnstrue(rotate-custom). A tinymaxSize/ high volume makes rotation happen fast; verifymaxFilesallows more than one generation. - The file handle opens on the first actual write. If nothing is logged, no file is created — that's expected.
6. Logs appear out of order / duplicated across transports
lograil shares one immutable, frozen entry across all transports (zero-copy). Per-transport async writes each have their own queue, so a slow transport may lag behind a fast one — ordering is guaranteed per transport, not globally across transports. If you need strict global order, use a single transport or make all writes synchronous. Duplicate lines usually mean the same transport was added twice, or redirectConsole() is double-emitting — check getTransports().
7. Notes when passing entries across processes (IPC)
Entries are structured-cloned when sent over Electron IPC (the renderer transport ) After an entry crosses the boundary it is a new, independent object on the other side. Don't rely on identity; rely on the field values. See Immutability.