Electron
lograil 专为 Electron 双进程模型设计,开箱即用:只需导入默认的 logger,它在主进程与渲染进程中都能表现正确。通常不需要 createLogger 或任何运行时配置。
另请参阅: Web 运行时 · Node 运行时 —— 三个运行时共享相同的 API 面;本页涵盖 Electron 特有的行为。
零配置(推荐)
主进程
// main.ts
import { app, BrowserWindow } from 'electron';
import { logger } from 'lograil';
app.whenReady().then(() => {
const win = new BrowserWindow({ /* ... */ });
win.loadFile('index.html');
logger.info('app ready'); // → 控制台 + 每日滚动文件
});仅此而已。主进程会输出到控制台 和 应用 logs 目录(app.getPath('logs'),按应用名隔离)下的每日滚动文件(分别为 main.{YYYY-MM-DD}.log 与 renderer.{YYYY-MM-DD}.log),并自动通过 IPC 接收渲染进程发来的日志。
渲染进程
// renderer.ts
import { logger } from 'lograil';
logger.warn('UI 中发生了一些事');
// → 本地控制台,并经由 IPC 转发到主进程默认的渲染进程 logger 会写入本地控制台(在 DevTools 中可见),并把条目转发到主进程,最终落到 独立 的 renderer.{YYYY-MM-DD}.log 文件中——与主进程的 main.{YYYY-MM-DD}.log 相互分离。
无需配置,无需 createLogger,无需任何接线。
⚠️ 注意:渲染进程必须能访问 Electron
要让「渲染进程 → 主进程」的转发生效,渲染进程必须能够访问 electron 模块(require('electron'))。
现代 Electron 默认关闭了 nodeIntegration 并启用了 contextIsolation,因此渲染进程的主世界(main world)没有 require,也没有 Node 全局对象(包括 process)。在这种情形下,lograil 无法在渲染进程中探测到 Electron,会静默回退到 Web 运行时——日志只打印到控制台,不会到达主进程。
在渲染进程中启用 Node(最简单的方式):
new BrowserWindow({
webPreferences: {
nodeIntegration: true,
// contextIsolation: false, // 某些配置下需与 nodeIntegration 一起关闭
},
});安全提示:
nodeIntegration: true会降低隔离性。生产环境更推荐用一个preload脚本只暴露ipcRenderer并自定义桥接,或仅为受信任的内容启用 Node 集成。库的降级是安全的——若无法访问electron,它只会在本地打印日志并丢弃 IPC 发送。
安全配置(preload + contextIsolation)
采用推荐的 Electron 默认值(nodeIntegration: false、contextIsolation: true)时,渲染进程的主世界没有 require,也没有 process,因此内置的 ElectronIpcTransport(会调用 require('electron'))无法拿到 ipcRenderer。解决方法是写一个 preload 通过 contextBridge 暴露一个精简的 send 函数,再用一个小型自定义传输器调用它。完全不需要改动库代码——只用公开 API(addTransport + LOGRAIL_CHANNEL)。
preload.ts(运行在有 require 的特权上下文):
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electronLogger', {
send: (channel: string, data: unknown) => ipcRenderer.send(channel, data),
});让窗口加载该 preload:
import { app, BrowserWindow, path } from 'electron';
app.whenReady().then(() => {
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
preload: path.join(__dirname, 'preload.js'),
},
});
});main.ts——默认 logger 已通过 createElectronMainRuntime({ receiveFromRenderer: true })(默认值)自动监听渲染进程条目。无需手动调用 registerIpcReceiver,除非需要覆盖 IPC 频道:
// 仅在需要自定义频道时才需要:
import { createLogger, createElectronMainRuntime, registerIpcReceiver } from 'lograil';
const log = createLogger({
runtime: createElectronMainRuntime({ receiveFromRenderer: false }),
});
registerIpcReceiver((entry) => log.ingestEntry(entry), { channel: 'my-app:log' });renderer.ts——把 preload 桥接暴露的 ipcRenderer 注入渲染进程运行时。这是一等公民用法;无需手写传输器:
import { createLogger, createElectronRendererRuntime } from 'lograil';
declare global {
interface Window {
electronLogger?: { send: (channel: string, data: unknown) => void };
}
}
// 桥接仅在 preload 运行后存在;不存在时退回纯控制台 logger。
const log = window.electronLogger
? createLogger({
runtime: createElectronRendererRuntime({ ipcRenderer: window.electronLogger }),
})
: createLogger();
log.warn('UI event'); // → 控制台 + 经 preload 桥接转发到主进程若想保留默认的 logger 单例,也可以把桥接注册为一个传输器:
import { logger, LOGRAIL_CHANNEL, type Transport } from 'lograil';
const bridge = window.electronLogger;
if (bridge) {
const ipcTransport: Transport = {
name: 'ipc-preload',
write(entry) {
bridge.send(LOGRAIL_CHANNEL, entry);
},
};
logger.addTransport(ipcTransport);
}两种方式都能在渲染进程保留控制台日志(DevTools 可见),同时满足 Electron 的安全默认值。
进阶自定义
只有想改日志路径、禁用文件、或停止接收渲染进程日志时,才需要 createLogger + 显式运行时。
// main.ts —— 禁用文件、或不接收渲染进程日志
import { createLogger, createElectronMainRuntime } from 'lograil';
const log = createLogger({
runtime: createElectronMainRuntime({
// 日志文件用 appName main/renderer;默认目录:app.getPath('logs')
// (可用 fileTransportOptions: { dir } 覆盖)
receiveFromRenderer: true, // 默认 true —— 通过 IPC 接收渲染进程日志
}),
});// renderer.ts —— 仅本地日志,或改频道
import { createLogger, createElectronRendererRuntime } from 'lograil';
const log = createLogger({
runtime: createElectronRendererRuntime({
forwardToMain: true, // 默认 true —— 经 IPC 转发到主进程
// channel: 'my-app:log',
}),
});| 工厂 | 选项 | 作用 |
|---|---|---|
createElectronMainRuntime | fileTransportOptions | 透传给 FileTransport(模式 rotate-time) |
createElectronMainRuntime | disableFile | 仅控制台(不写文件) |
createElectronMainRuntime | receiveFromRenderer | 通过 IPC 接收渲染进程日志(默认 true) |
createElectronRendererRuntime | forwardToMain | 通过 IPC 转发到主进程(默认 true) |
createElectronRendererRuntime | channel | 覆盖 IPC 频道 |
IPC 频道
两端通过一个频道通信,该常量已导出:
import { LOGRAIL_CHANNEL } from 'lograil'; // 'lograil:log'如果你覆盖了 channel,需在渲染进程运行时与主进程接收端设置 相同 的值。若要完全掌控,可以跳过运行时辅助函数,自行调用 registerIpcReceiver:
import { registerIpcReceiver, createElectronMainRuntime } from 'lograil';
const log = createLogger({ runtime: createElectronMainRuntime({ receiveFromRenderer: false }) });
const off = registerIpcReceiver((entry) => log.ingestEntry(entry), { channel: 'my-app:log' });
// off() 停止监听端到端流程
renderer ──ElectronIpcTransport──▶ IPC ──▶ main: registerIpcReceiver
│
▼
log.ingestEntry(entry)
│
▼
main: ConsoleTransport + FileTransport- 渲染进程日志不会直接落盘——由主进程负责持久化。
- 主进程自身的日志与被接收的渲染进程日志,共享同一条管道、同一组传输器、同一个滚动文件。
- 级别过滤与插件对两者统一生效。
跨进程级别同步
渲染进程 → 主进程方向是自动的:渲染进程调用 setLevel() 时,命令会经 IPC 通道回传到主进程并应用到主 logger 的级别上,无需额外接线。主进程 → 渲染 进程方向是手动的:主进程调用 setLevel() 不会广播(每个渲染进程拥有自己的 本地 console sink 与级别),需要时用 logger.broadcastLevelToPeers() 把主进程 的当前级别推送到所有已连接的渲染进程。
// main.ts —— 自动方向(渲染 → 主)
import { logger } from 'lograil';
// 渲染进程的 setLevel() 会自动应用到此处。
// main.ts —— 手动方向(主 → 渲染)
logger.broadcastLevelToPeers(); // 把本进程级别推送到所有渲染进程// renderer.ts —— 自动方向(渲染 → 主)
import { logger } from 'lograil';
logger.setLevel('trace'); // 主进程自动收到此命令收到对等方级别命令时,getLevel() 返回对等方的级别——跨进程查询保持一致。
此功能对默认的 logger 单例和使用 createLogger() 创建的 logger 均适用。 若需在应用前拦截或拒绝级别命令,可注册自定义回调:
import { createLogger } from 'lograil';
const log = createLogger();
log.setOnLevelCommand((level) => {
// 检查或拒绝后再应用
if (level < 30) return; // 只允许 info 及以上级别
log.setLevel(level);
});