I have a pywebview, that launches a Qt window utilizing the QtWebEngine with Chromium on Windows.
When starting the webview in debug mode, the following output is of particular interest for me:
DevTools listening on ws://127.0.0.1:8228/devtools/browser/<GUID>
This line is output by the Chromium engine itself and I want to make the debug port and GUID available in my application. For that, my idea is to redirect all terminal outputs to a StringIO
stream.
Here is a minimum example of what I have done so far, to redirect all outputs:
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):
# Redirect message to stdout
print(message)
if __name__ == '__main__':
# Let the handler redirect all qt messages to stdout
QtCore.qInstallMessageHandler(qt_message_handler)
stream = StringIO()
# Redirect stdout and stderr to stream
with redirect_stdout(stream), redirect_stderr(stream):
# Redirect all loging outputs to 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)
# Start the webview window
window = webview.create_window('Webview window')
webview.start(gui='qt', debug=True)
Ths approach redirects all outputs to the stream object as intended except the one line output by Chromium mentioned at the beginning.
So i played around with the QTWEBENGINE_CHROMIUM_FLAGS
environment variable. Since pywebview
overrides this environment variable, I changed it directly in the pywebview
module. However no argument passed to Chromium by this environment variable seems to change the output behaviour of this specific line. Looking at the Chromium source code, this makes sense:
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);
Since the message is directly printed to stderr
no Chromium logging flags have an influence. But it's clearly evident, that the message gets printed to stderr
, so why does a redirect of stderr
in Python have no effect on this message? Is there any way to redirect this message in python?
A further look into the Chromium source code shows, that the debug port and GUID are also written into a file:
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";
}
}
I just can't figure out where Chromium stores this data. Where can this file be located on Windows?
I have made some findings that, while not directly answering the question, do lead to a conclusion:
Why is the stderr
of Chromium not redirected? This is because Chromium is launched in a child process by the QtWebEngine. While it would be possible, to get the output of a child process in Python, it would require to mess around with the QtWebEngine. At this point I decided, that this is not worth the effort.
Regarding my bonus question, I found out that the file containting the debug port and GUID should be named DevToolsActivePort
. However, this file is not created when using the QtWebEngine and I haven't found out yet, why this is the case. See this question for a possible answer in the future: QtWebEngine: DevToolsActivePort file is missing
How did I solve my issue? I found out, that a query exists which returns the websocket url among other things: http://<debug_server_url>/json/version
(see this answer). While you can determine the debug port within the QtWebEngine, I solved it for my pywebview application similar to this:
import requests
from PySide6 import QtCore
import webview
debug_server_url = None
def qt_message_handler(mode, context, message):
# Check if the message is the one containing the debug server url
if message.startswith('Remote debugging server started successfully.'):
# Debug server url is at the end of the message
global debug_server_url
debug_server_url = message.split()[-1]
def on_loaded():
if debug_server_url is not None:
websocket_debugger_url = requests.get(debug_server_url + '/json/version').json()['webSocketDebuggerUrl']
print(websocket_debugger_url)
if __name__ == '__main__':
# Define a custom qt message handler
QtCore.qInstallMessageHandler(qt_message_handler)
window = webview.create_window('Webview window')
window.events.loaded += on_loaded
webview.start(gui='qt', debug=True)