做Electron开发的朋友应该都遇到过这种诡异情况:开发模式下一切正常,一打包就出事。我最近维护一个老项目,Electron 8.x配electron-builder 22.14,里头的日志模块叫logset,负责初始化electron-log,统一控制日志级别、输出目录和滚动策略。开发时终端里日志刷刷地打,落盘也正常。可一旦用electron-builder打成Windows安装包,装到测试机上一跑,logset的配置就跟没加载一样,日志文件一个都不生成。这个问题表面看很小,但部署到用户那边之后,想收集反馈信息就完全抓瞎——拿不到日志等于少了一只眼睛。
我连着折腾了两天,把整条链路从头到尾捋了一遍,发现问题根本不在logset的代码逻辑,而是Electron打包后运行环境发生了几处底层变化,直接把这个模块"架空"了。这篇文章就把我踩过的坑、改过的代码、验过的配置完整记录下来。内容重点针对旧版Electron(9.x及以下、electron-builder 22.x前后这个组合),新版Electron思路相通,但少数配置字段和行为有差异。如果你手里正好也是一个老Electron项目,日志模块打包后不工作,这篇应该能帮你少走不少弯路。
1. 开发者模式下一切正常,打包后logset直接哑火:到底哪里变了
先说结论:logset本身没有写错,是它的运行环境在打包前后发生了剧烈变化。很多人在这个坑里浪费大量时间,就是因为一直盯着日志模块的代码看,没有跳出"代码没问题"的思维定式。Electron打包之后,应用已经不是从源码目录直接运行的了,而是从一个只读的归档文件里解出来跑。这一层变化,对日志模块这种要落盘的组件来说是致命的。
1.1 应用运行环境的三大差异
开发模式和生产打包模式之间,至少有三个本质区别,任何一个都可能让logset失效。
第一个是文件系统的形态变了。开发模式下,你的代码以真实文件的形式存在于项目目录里,fs模块可以随意读写。打包之后,绝大多数业务代码会被压缩封装进一个名为app.asar的归档文件,electron-builder在构建时把整个应用目录塞进这个文件。asar内部路径对Node.js的fs模块是透明可读的,也就是说fs.readFileSync能读asan内部的文件,但fs.createWriteStream这类写入操作完全不支持。日志模块要向文件写入,如果路径解析到了asar内部的虚拟目录,那自然一个字都写不进去。
第二个是当前工作目录变了。开发模式下你用npm start或者electron .启动,进程的工作目录通常是项目根目录,任何相对路径都很好使。打包之后,安装版应用的工作目录是安装目录,绿色版是解压目录,使用快捷方式启动时还可能是别的路径。如果logset里用了类似path.join(__dirname, 'logs')的写法,开发时这个相对路径落在源码目录里没问题,打包后__dirname指向的是app.asar下的虚拟路径,日志不可能写进去。
第三个是进程权限变了。开发模式下应用跑在当前登录用户的账户下,对用户目录和项目目录有完全写权限。打包安装后,如果装到Windows的C:\Program Files这类系统保护目录里,非管理员权限下应用对安装目录只有读权限,任何试图在安装目录下创建文件的日志操作都会被系统拒绝。
把这三个差异放在一张表里,问题就非常直观了:
| 环境维度 | 开发模式 | 打包生产模式 |
|---|---|---|
| 代码存在形式 | 明文真实文件 | app.asar归档,只读 |
| 当前工作目录 | 项目根目录 | 安装目录/解压目录 |
| 日志目标目录 | 用户可写,相对路径好使 | 需要显式定位到用户数据目录 |
| 写文件权限 | 完全可控 | 受系统目录保护策略限制 |
1.2 asar归档是日志写入的第一道坎
很多人不熟悉asar内部机制,这里稍微展开说一下。asar不是压缩包,它是一个将多个文件拼接而成的归档格式,每个文件在归档内都有一个目录索引。Electron运行时加载了一份fs补丁,让fs模块对asar内部的路径读取透明化。这意味着你可以用fs.readFileSync(path.join(__dirname, 'config.json'))读到归档内的配置,完全不用解包。
但问题恰好出在这个透明化上。写操作在asar内是不存在的,如果你用fs.createWriteStream往一个asar内部路径写文件,Electron的行为是抛出一个只读文件系统的错误。然而很多日志库会静默吞掉这个错误——比如electron-log在写文件失败时默认不会弹出任何提示,只在内存中保留日志。这就是"logset配置了,但看不到任何日志文件"的经典原因:它一直在尝试写信一个不存在于可写文件系统中的路径。
1.3 旧版Electron的信号:注意这几处API行为
老版本Electron还有一个容易踩的点:部分API的行为在打包后和开发时不一致。以app.getPath('userData')为例,开发模式下它返回的是`%APPDATA%/<应用
