Search code examples
pythonchromiumqtwebenginepywebview

Redirect debug output of Chromium in Python


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?

Bonus question:

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?


Solution

  • I have made some findings that, while not directly answering the question, do lead to a conclusion:

    1. 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.

    2. 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

    3. 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)