Electron netLog 模块实战:为 Session 网络事件录制日志(含 --log-net-log 开关与源码剖析)
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
在排查 Electron 应用的网络问题时,netLog模块提供了一种原生的、基于 Chromium 网络栈的抓包手段:它能把某个 session 在任意时间段内发生的全部网络事件以 JSON 格式落盘,供事后分析。本文以 docs/api/net-log.md 为骨架,完整覆盖startLogging/stopLogging/currentlyLogging三个 API 的参数与语义、全程抓包的--log-net-log命令行开关,并结合 C++ 实现、JS 包装层 与 官方测试,讲清楚这些 API 在底层究竟做了什么、有哪些边界条件和错误路径。
模块概览与快速上手
netLog是主进程(Main process)模块,用于"为 session 记录网络事件"。最简用法如下(继承自官方文档):
const { app, netLog } = require('electron') app.whenReady().then(async () => { await netLog.startLogging('/path/to/net-log') // After some network events const path = await netLog.stopLogging() console.log('Net-logs written to', path) })需要注意两条前提:
- 所有方法都只能在
app的ready事件之后调用(文档中的 NOTE 明确标注); - 独立的
netLog模块实际上是session模块上netLog属性的"门面"。从 lib/browser/api/net-log.ts 可以看到,每个方法内部都先判断app.isReady(),然后把调用转发给session.defaultSession.netLog。文件头部的注释也说明该独立模块已被标记为"Deprecate and remove standalone netLog module, it is now a property of session module",即netLog现已是session的属性。因此在实际应用中,更推荐直接使用session.fromPartition(...).netLog来精确控制要记录哪个会话的网络事件。C++ 侧对应地通过 electron_api_session.cc 将netLog属性挂到Session对象上(.SetProperty("netLog", &Session::NetLog)),每个ElectronBrowserContext各持有一个NetLog实例。
netLog.startLogging(path[, options])
pathstring - 网络日志的写入文件路径。optionsObject (optional)captureModestring (optional) - 决定捕获哪些种类的数据。默认只捕获请求的元数据(metadata)。设为includeSensitive时会包含 cookie 和认证数据;设为everything时会包含 socket 上流转的全部字节。可选值:default、includeSensitive、everything。maxFileSizenumber (optional) - 当日志增长超过该大小时自动停止记录。默认为不限制(unlimited)。
返回Promise<void>,当 net log 开始记录后 resolve。
captureMode 三档的底层映射
captureMode字符串并不是 Electron 自定义的概念,而是直接映射到 Chromium 网络栈的net::NetLogCaptureMode枚举。electron_api_net_log.cc 中注册了一个 gin 类型转换器,逐一完成字符串到枚举的映射:
if (type == "default") *out = net::NetLogCaptureMode::kDefault; else if (type == "includeSensitive") *out = net::NetLogCaptureMode::kIncludeSensitive; else if (type == "everything") *out = net::NetLogCaptureMode::kEverything; else return false;转换器遇到非法值时返回false,上层随即抛出"Invalid value for captureMode"的 TypeError。spec/api-net-log-spec.ts 中的测试印证了这一行为:传入{ captureMode: 'aoeu' }、{ maxFileSize: null }或空path都会同步抛错。
三档模式的实际差异也被测试逐档验证过:
includeSensitive下,测试用net.request手动设置Cookie: foo=<uuid>头发起请求,stopLogging后读取 dump 文件,断言文件内容确实包含foo=<uuid>(见 api-net-log-spec.ts 第 90-106 行);everything下,测试发起 POST 并在 body 中写入随机 UUID,然后断言 dump JSON 的events数组中存在某条事件,其params.bytes(base64 解码后)包含该 UUID 字节(见 api-net-log-spec.ts 第 108-127 行)。
由此可以确认:日志文件是一个 JSON 结构,顶层有events数组,每条事件带params;everything模式的 socket 字节以 base64 形式存放在params.bytes中。
maxFileSize 的默认值与"单实例"约束
C++ 实现中,两个选项都有明确的默认值(electron_api_net_log.cc 第 96-124 行):
net::NetLogCaptureMode capture_mode = net::NetLogCaptureMode::kDefault; uint64_t max_file_size = network::mojom::NetLogExporter::kUnlimitedFileSize;即文档中"maxFileSize默认为 unlimited"在源码中的落点是kUnlimitedFileSize常量。此外还有两条容易踩坑的运行时约束,测试均有覆盖:
- 同一 session 同时只能有一个 net log 在跑。
StartLogging开头检查if (net_log_exporter_)已存在,直接抛出"There is already a net log running"的 TypeError; stopLogging没有对应的进行中记录时会 reject,错误信息为"No net log in progress"(见 StopLogging 实现 与 spec 第 78-80 行)。
落盘流程:Mojo NetLogExporter 与文件任务线程
从源码结构看,startLogging的完整调用链(StartLogging / StartNetLogAfterCreateFile)是:
- 校验参数:
path为空字符串时抛出"The first parameter must be a valid string";解析options中的captureMode和maxFileSize; - 建立 Mojo 管道:通过
browser_context_->GetDefaultStoragePartition()->GetNetworkContext()拿到该 session 对应的NetworkContext,调用CreateNetLogExporter(...)创建一个network::mojom::NetLogExporter远程端——真正的事件采集与写文件逻辑由 Chromium 的 network service 承担,Electron 只负责传递参数和接收回调; - 异步创建输出文件:文件打开操作被投递到一个专用的
SequencedTaskRunner上(base::ThreadPool::CreateSequencedTaskRunner,带base::MayBlock()与SKIP_ON_SHUTDOWN标志)。源码注释解释了原因:该 runner 上的任务只做同步文件 I/O(检查路径、设置文件权限),而这些操作在关闭阶段可以安全跳过,因为FileNetLogObserver的 API 并不要求它们已完成; - 启动导出:文件创建成功后调用
net_log_exporter_->Start(file, custom_constants, capture_mode, net::NetLogFileFormat::kJson, max_file_size, ...),注意日志格式被固定为kJson;custom_constants中写入了进程命令行字符串和"Electron <版本>"的 channel 信息,便于事后分析时确认日志来源; - Promise 收敛:启动回调
NetLogStarted收到 Chromium 返回的net::OK才 resolve;若文件创建失败则以base::File::ErrorToString(error_details)为消息 reject,Mojo 管道断开(OnConnectionError)时则以"Failed to start net log exporter"reject。
这个设计的实际含义:await startLogging()成功返回,意味着网络 service 端的 exporter 已经真正开始捕获事件,而不是"任务已提交"。
netLog.stopLogging()
返回Promise<void>,当日志刷新(flush)到磁盘后 resolve。停止记录网络事件;如果不主动调用,net log 会在应用退出时自动结束。
C++ 侧的一个实现细节值得注意:Stop的回调通过std::move(net_log_exporter_)把 Mojo 远程端整体移入回调闭包(第 207-218 行),以保证在 Promise resolve 之前 mojo pointer 存活,同时让成员变量置空——这也是currentlyLogging能在stopLogging之后立即变为false的原因(IsCurrentlyLogging只是返回!!net_log_exporter_)。
测试 spec/api-net-log-spec.ts 第 68-76 行 验证了最基础的生命周期:startLogging后currentlyLogging为true,stopLogging之后 dump 文件确实存在于磁盘。
netLog.currentlyLogging(只读)
boolean属性,表示当前是否正在记录网络日志。JS 侧包装(lib/browser/api/net-log.ts)在app未 ready 时直接返回false;C++ 侧则查询是否存在活跃的net_log_exporter_。该属性适合用于条件分支,例如"只有尚未开启记录时才启动"。
命令行开关--log-net-log=path:覆盖整个应用生命周期
如果需要从进程启动那一刻就开始记录(而不是等到ready之后),可以在启动 Electron 时传入--log-net-log=path开关,它"使 net log 事件被保存,并写入path"(见 docs/api/command-line-switches.md)。在应用内部也可以用app.commandLine.appendSwitch('log-net-log', path)达到同样效果。
测试夹具 spec/fixtures/api/net-log/main.js 展示了完整的组合玩法:
const { app, net, session } = require('electron'); if (process.env.TEST_DUMP_FILE) { app.commandLine.appendSwitch('log-net-log', process.env.TEST_DUMP_FILE); } // ... app.whenReady().then(async () => { const netLog = session.defaultSession.netLog; if (process.env.TEST_DUMP_FILE_DYNAMIC) { await netLog.startLogging(process.env.TEST_DUMP_FILE_DYNAMIC); } await request(); if (process.env.TEST_MANUAL_STOP) { await netLog.stopLogging(); } app.quit(); });围绕这段代码,api-net-log-spec.ts 设计了三个进程级测试,覆盖了三种生命周期组合:
| 场景 | 开关 | API 调用 | 验证点 |
|---|---|---|---|
| 仅命令行开关 | --log-net-log | 无 | 应用退出后 dump 文件自动落盘 |
| 开关 + API 并存 | --log-net-log | startLogging+stopLogging | 两份 dump 文件同时生成,互不干扰 |
| 仅 API | 无 | 仅startLogging | 不调用stopLogging时,退出时自动结束并落盘 |
注意这三个进程级测试用ifit(process.platform !== 'linux')做了平台限定——Linux 平台上不会执行,其余平台(macOS、Windows)上执行。
实用建议与边界总结
结合文档与源码,使用netLog时的注意事项:
- 时机:
startLogging/stopLogging必须在appready 之后调用;要捕获启动阶段的网络活动,用--log-net-log开关(或在ready前appendSwitch); - 作用范围是 session 级:每个
ElectronBrowserContext(partition)有独立的NetLog实例,记录session.fromPartition('p1')的日志不会影响defaultSession; - 敏感数据:
includeSensitive会写入 cookie 与认证数据,everything会写入 socket 明文字节——日志文件本质上是明文敏感信息的载体,仅应在本机调试环境使用,不要随包分发; - 磁盘控制:长时间记录或高频流量场景下建议显式设置
maxFileSize,避免日志无限增长(默认kUnlimitedFileSize); - 格式与分析:dump 文件是 Chromium netlog 的标准 JSON 格式(顶层
events数组 + 自定义常量区,其中包含 Electron 版本与完整命令行),可直接用 Chromium 的 netlog 查看器打开分析; - 错误处理:
stopLogging在无进行中记录时 reject、重复startLogging抛 TypeError、文件路径不可写时以底层文件错误 reject,生产代码中应对这些 Promise 做catch处理。
相关源码与测试索引
| 内容 | 路径 |
|---|---|
| API 文档 | docs/api/net-log.md |
| JS 包装层(委托给 session) | lib/browser/api/net-log.ts |
| C++ 核心实现(Mojo exporter 调用链) | shell/browser/api/electron_api_net_log.cc |
| C++ 头文件(类结构定义) | shell/browser/api/electron_api_net_log.h |
--log-net-log开关说明 | docs/api/command-line-switches.md |
| 单元/进程级测试 | spec/api-net-log-spec.ts |
| 测试夹具(开关 + API 组合示例) | spec/fixtures/api/net-log/main.js |
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考