Python重定向Chromium调试stderr输出及DevToolsActivePort位置问题
我在Windows平台使用pywebview启动搭载QtWebEngine(基于Chromium内核)的Qt窗口,以调试模式启动webview时,Chromium引擎会输出如下重点关注的日志行:
DevTools listening on ws://127.0.0.1:8228/devtools/browser/<GUID>
该日志由Chromium引擎直接输出,我需要在自身应用中获取其中的调试端口与GUID值,最初的实现思路是将所有终端输出重定向至StringIO流中。
现有实现代码
目前实现全输出重定向的最小可复现代码如下:
from contextlib import redirect_stdout, redirect_stderr from io import StringIO import logging from PyQt5 import QtCore import webview def qt_message_handler(mode, context, message): # 将Qt消息重定向到stdout print(message) if __name__ == '__main__': # 安装处理器将所有Qt消息转发到stdout QtCore.qInstallMessageHandler(qt_message_handler) stream = StringIO() # 将stdout和stderr重定向到内存流 with redirect_stdout(stream), redirect_stderr(stream): # 将所有日志输出重定向到内存流 stream_handler = logging.StreamHandler(stream) loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict] for logger in loggers: for handler in logger.handlers: logger.removeHandler(handler) logger.addHandler(stream_handler) # 启动webview窗口 window = webview.create_window('Webview window') webview.start(gui='qt', debug=True)
该方案可按预期将绝大多数输出重定向到stream对象中,但唯独Chromium输出的DevTools监听日志行无法被捕获。
问题原因排查
我尝试调整QTWEBENGINE_CHROMIUM_FLAGS环境变量修改Chromium的输出行为,由于pywebview会覆写该环境变量,我直接修改了pywebview模块内的对应配置,但传入Chromium的所有参数都无法改变这一日志行的输出行为。查阅Chromium源码后确认该日志的输出逻辑如下:
std::string message = base::StringPrintf( "\nDevTools listening on ws://%s%s\n", ip_address->ToString().c_str(), browser_guid.c_str()); fprintf(stderr, "%s", message.c_str()); fflush(stderr);
该日志是直接通过fprintf写入stderr的,不受Chromium日志相关参数控制。但既然该日志明确写入stderr,为何Python层实现的stderr重定向对其无效?如何在Python中实现该日志的捕获重定向?
进一步查阅Chromium源码可知,调试端口与GUID还会被写入文件中,对应源码片段如下:
if (!output_directory.empty()) { base::FilePath path = output_directory.Append(kDevToolsActivePortFileName); std::string port_target_string = base::StringPrintf( "%d\n%s", ip_address->port(), browser_guid.c_str()); if (base::WriteFile(path, port_target_string.c_str(), static_cast<int>(port_target_string.length())) < 0) { LOG(ERROR) << "Error writing DevTools active port to file"; } }
我无法确认Chromium存储该文件的路径,请问在Windows系统下该DevToolsActivePort文件的存放位置是什么?
Python层stderr重定向失效的原因
Python的redirect_stderr本质上只是修改了Python进程内sys.stderr对象的指向,仅能捕获Python代码层面写入sys.stderr的输出。而QtWebEngine的Chromium内核是独立的原生C++模块,它调用fprintf(stderr, ...)时直接操作的是操作系统层面的标准错误文件句柄,完全不会经过Python的sys.stderr对象,因此Python层面的重定向对这部分输出完全无效。
捕获该日志的可行方案
在Windows平台可以通过两种方案实现捕获:
- 原生句柄重定向:调用Win32 API创建匿名管道,替换进程启动时的标准错误句柄,从管道端读取原生层面的stderr输出,即可捕获到Chromium直接写入的日志。具体流程为:提前调用
kernel32.CreatePipe创建一对读写管道句柄;通过SetStdHandle将进程的STD_ERROR_HANDLE替换为管道的写端,注意要允许子进程继承该句柄;单独启动一个线程持续从管道的读端读取输出内容,匹配DevTools监听行的正则即可提取端口和GUID;程序退出时关闭句柄、回收资源。 - 启动时重定向到文件:如果不想操作底层Win32 API,也可以在启动Python程序时直接用系统层面的管道把stderr重定向到文件,再从文件中实时读取匹配目标内容。
Windows下DevToolsActivePort文件的存放路径
QtWebEngine启动的Chromium实例,DevToolsActivePort文件默认存放在Chromium用户配置目录的根目录下:
- 未手动指定用户数据目录时,默认路径为
%APPDATA%\pywebview\QtWebEngine\DevToolsActivePort,对应展开后的完整路径通常是C:\Users\<当前用户名>\AppData\Roaming\pywebview\QtWebEngine\DevToolsActivePort - 如果启动时手动指定了
user_data_dir参数,则文件直接存放在指定的用户数据目录根路径下
注意该文件是Chromium启动后临时写入的,需要等webview窗口完成初始化后才能读取到内容,读取前要加文件存在判断避免报错。
内容的提问来源于stack exchange,提问作者Pikkostack

