summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2023-07-21 14:52:24 +0200
committerGitHub <noreply@github.com>2023-07-21 14:52:24 +0200
commit1387b0598b90501dfa1dc8e4cbe5e1d0d05cd048 (patch)
treec10b53a21a316dee9d2b42af3ea737df29df2339
parent059a280e4ec11898148df78578b7a29cc2bd41d3 (diff)
parent01ab247a410488f86ac57cf07e22cca5e62bc92c (diff)
downloadqutebrowser-1387b0598b90501dfa1dc8e4cbe5e1d0d05cd048.tar.gz
qutebrowser-1387b0598b90501dfa1dc8e4cbe5e1d0d05cd048.zip
Merge pull request #7772 from pylbrecht/logfilter
Don't crash on --logfilter at startup
-rw-r--r--qutebrowser/browser/network/pac.py4
-rw-r--r--qutebrowser/browser/qtnetworkdownloads.py4
-rw-r--r--qutebrowser/browser/webkit/network/networkmanager.py4
-rw-r--r--qutebrowser/misc/earlyinit.py18
-rw-r--r--qutebrowser/misc/httpclient.py4
-rw-r--r--qutebrowser/misc/quitter.py4
-rw-r--r--qutebrowser/qt/machinery.py3
-rw-r--r--qutebrowser/utils/log.py213
-rw-r--r--qutebrowser/utils/qtlog.py241
-rw-r--r--tests/end2end/test_invocations.py13
-rw-r--r--tests/unit/utils/test_log.py57
-rw-r--r--tests/unit/utils/test_qtlog.py82
12 files changed, 369 insertions, 278 deletions
diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py
index 54c1195af..162e1c5d0 100644
--- a/qutebrowser/browser/network/pac.py
+++ b/qutebrowser/browser/network/pac.py
@@ -28,7 +28,7 @@ from qutebrowser.qt.network import (QNetworkProxy, QNetworkRequest, QHostInfo,
QHostAddress)
from qutebrowser.qt.qml import QJSEngine, QJSValue
-from qutebrowser.utils import log, utils, qtutils, resources, urlutils
+from qutebrowser.utils import log, qtlog, utils, qtutils, resources, urlutils
class ParseProxyError(Exception):
@@ -258,7 +258,7 @@ class PACFetcher(QObject):
url.setScheme(url.scheme()[len(pac_prefix):])
self._pac_url = url
- with log.disable_qt_msghandler():
+ with qtlog.disable_qt_msghandler():
# WORKAROUND for a hang when messages are printed, see our
# NetworkAccessManager subclass for details.
self._manager: Optional[QNetworkAccessManager] = QNetworkAccessManager()
diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py
index cd4a75351..0b20b3785 100644
--- a/qutebrowser/browser/qtnetworkdownloads.py
+++ b/qutebrowser/browser/qtnetworkdownloads.py
@@ -29,7 +29,7 @@ from qutebrowser.qt.widgets import QApplication
from qutebrowser.qt.network import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from qutebrowser.config import config, websettings
-from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug, objreg
+from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug, objreg, qtlog
from qutebrowser.misc import quitter
from qutebrowser.browser import downloads
from qutebrowser.browser.webkit import http
@@ -121,7 +121,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
self._reply.errorOccurred.disconnect()
self._reply.readyRead.disconnect()
- with log.hide_qt_warning('QNetworkReplyImplPrivate::error: Internal '
+ with qtlog.hide_qt_warning('QNetworkReplyImplPrivate::error: Internal '
'problem, this method must only be called '
'once.'):
# See https://codereview.qt-project.org/#/c/107863/
diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py
index 91e609456..4c1c767ec 100644
--- a/qutebrowser/browser/webkit/network/networkmanager.py
+++ b/qutebrowser/browser/webkit/network/networkmanager.py
@@ -28,7 +28,7 @@ from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkReply, QSslCo
from qutebrowser.config import config
from qutebrowser.utils import (message, log, usertypes, utils, objreg,
- urlutils, debug)
+ urlutils, debug, qtlog)
from qutebrowser.browser import shared
from qutebrowser.browser.network import proxy as proxymod
from qutebrowser.extensions import interceptors
@@ -156,7 +156,7 @@ class NetworkManager(QNetworkAccessManager):
def __init__(self, *, win_id, tab_id, private, parent=None):
log.init.debug("Initializing NetworkManager")
- with log.disable_qt_msghandler():
+ with qtlog.disable_qt_msghandler():
# WORKAROUND for a hang when a message is printed - See:
# https://www.riverbankcomputing.com/pipermail/pyqt/2014-November/035045.html
#
diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py
index a0265d653..57e821784 100644
--- a/qutebrowser/misc/earlyinit.py
+++ b/qutebrowser/misc/earlyinit.py
@@ -298,7 +298,17 @@ def init_log(args):
from qutebrowser.utils import log
log.init_log(args)
log.init.debug("Log initialized.")
- log.init.debug(str(machinery.INFO))
+
+
+def init_qtlog(args):
+ """Initialize Qt logging.
+
+ Args:
+ args: The argparse namespace.
+ """
+ from qutebrowser.utils import log, qtlog
+ qtlog.init(args)
+ log.init.debug("Qt log initialized.")
def check_optimize_flag():
@@ -333,16 +343,18 @@ def early_init(args):
Args:
args: The argparse namespace.
"""
+ # Init logging as early as possible
+ init_log(args)
# First we initialize the faulthandler as early as possible, so we
# theoretically could catch segfaults occurring later during earlyinit.
init_faulthandler()
# Then we configure the selected Qt wrapper
info = machinery.init(args)
+ # Init Qt logging after machinery is initialized
+ init_qtlog(args)
# Here we check if QtCore is available, and if not, print a message to the
# console or via Tk.
check_qt_available(info)
- # Init logging as early as possible
- init_log(args)
# Now we can be sure QtCore is available, so we can print dialogs on
# errors, so people only using the GUI notice them as well.
check_libraries()
diff --git a/qutebrowser/misc/httpclient.py b/qutebrowser/misc/httpclient.py
index 2bb152b03..45d491996 100644
--- a/qutebrowser/misc/httpclient.py
+++ b/qutebrowser/misc/httpclient.py
@@ -25,7 +25,7 @@ from qutebrowser.qt.core import pyqtSignal, QObject, QTimer
from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkRequest,
QNetworkReply)
-from qutebrowser.utils import log
+from qutebrowser.utils import qtlog
class HTTPRequest(QNetworkRequest):
@@ -59,7 +59,7 @@ class HTTPClient(QObject):
def __init__(self, parent=None):
super().__init__(parent)
- with log.disable_qt_msghandler():
+ with qtlog.disable_qt_msghandler():
# WORKAROUND for a hang when messages are printed, see our
# NetworkAccessManager subclass for details.
self._nam = QNetworkAccessManager(self)
diff --git a/qutebrowser/misc/quitter.py b/qutebrowser/misc/quitter.py
index d502efa65..59e2d552f 100644
--- a/qutebrowser/misc/quitter.py
+++ b/qutebrowser/misc/quitter.py
@@ -37,7 +37,7 @@ except ImportError:
import qutebrowser
from qutebrowser.api import cmdutils
-from qutebrowser.utils import log
+from qutebrowser.utils import log, qtlog
from qutebrowser.misc import sessions, ipc, objects
from qutebrowser.mainwindow import prompt
from qutebrowser.completion.models import miscmodels
@@ -304,5 +304,5 @@ def init(args: argparse.Namespace) -> None:
"""Initialize the global Quitter instance."""
global instance
instance = Quitter(args=args, parent=objects.qapp)
- instance.shutting_down.connect(log.shutdown_log)
+ instance.shutting_down.connect(qtlog.shutdown_log)
objects.qapp.lastWindowClosed.connect(instance.on_last_window_closed)
diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py
index 4e88a0634..e626edcb4 100644
--- a/qutebrowser/qt/machinery.py
+++ b/qutebrowser/qt/machinery.py
@@ -18,6 +18,8 @@ import importlib
import dataclasses
from typing import Optional, Dict
+from qutebrowser.utils import log
+
# Packagers: Patch the line below to change the default wrapper for Qt 6 packages, e.g.:
# sed -i 's/_DEFAULT_WRAPPER = "PyQt5"/_DEFAULT_WRAPPER = "PyQt6"/' qutebrowser/qt/machinery.py
#
@@ -280,6 +282,7 @@ def init(args: argparse.Namespace) -> SelectionInfo:
info = _select_wrapper(args)
if info.wrapper is not None:
_set_globals(info)
+ log.init.debug(str(info))
# If info is None here (no Qt wrapper available), we'll show an error later
# in earlyinit.py.
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 5ac150702..f2a6c396d 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -24,17 +24,16 @@ import logging
import contextlib
import collections
import copy
-import faulthandler
-import traceback
import warnings
import json
import inspect
import argparse
from typing import (TYPE_CHECKING, Any, Iterator, Mapping, MutableSequence,
- Optional, Set, Tuple, Union, TextIO, Literal, cast, Callable)
+ Optional, Set, Tuple, Union, TextIO, Literal, cast)
+
+# NOTE: This is a Qt-free zone! All imports related to Qt logging should be done in
+# qutebrowser.utils.qtlog (see https://github.com/qutebrowser/qutebrowser/issues/7769).
-from qutebrowser.qt import core as qtcore
-from qutebrowser.qt import machinery
# Optional imports
try:
import colorama
@@ -209,15 +208,9 @@ def init_log(args: argparse.Namespace) -> None:
root.setLevel(logging.NOTSET)
logging.captureWarnings(True)
_init_py_warnings()
- qtcore.qInstallMessageHandler(qt_message_handler)
_log_inited = True
-@qtcore.pyqtSlot()
-def shutdown_log() -> None:
- qtcore.qInstallMessageHandler(None)
-
-
def _init_py_warnings() -> None:
"""Initialize Python warning handling."""
assert _args is not None
@@ -232,29 +225,6 @@ def _init_py_warnings() -> None:
@contextlib.contextmanager
-def disable_qt_msghandler() -> Iterator[None]:
- """Contextmanager which temporarily disables the Qt message handler."""
- old_handler = qtcore.qInstallMessageHandler(None)
- if machinery.IS_QT6:
- # cast str to Optional[str] to be compatible with PyQt6 type hints for
- # qInstallMessageHandler
- old_handler = cast(
- Optional[
- Callable[
- [qtcore.QtMsgType, qtcore.QMessageLogContext, Optional[str]],
- None
- ]
- ],
- old_handler,
- )
-
- try:
- yield
- finally:
- qtcore.qInstallMessageHandler(old_handler)
-
-
-@contextlib.contextmanager
def py_warning_filter(
action:
Literal['default', 'error', 'ignore', 'always', 'module', 'once'] = 'ignore',
@@ -391,163 +361,6 @@ def change_console_formatter(level: int) -> None:
assert isinstance(old_formatter, JSONFormatter), old_formatter
-def qt_message_handler(msg_type: qtcore.QtMsgType,
- context: qtcore.QMessageLogContext,
- msg: Optional[str]) -> None:
- """Qt message handler to redirect qWarning etc. to the logging system.
-
- Args:
- msg_type: The level of the message.
- context: The source code location of the message.
- msg: The message text.
- """
- # Mapping from Qt logging levels to the matching logging module levels.
- # Note we map critical to ERROR as it's actually "just" an error, and fatal
- # to critical.
- qt_to_logging = {
- qtcore.QtMsgType.QtDebugMsg: logging.DEBUG,
- qtcore.QtMsgType.QtWarningMsg: logging.WARNING,
- qtcore.QtMsgType.QtCriticalMsg: logging.ERROR,
- qtcore.QtMsgType.QtFatalMsg: logging.CRITICAL,
- qtcore.QtMsgType.QtInfoMsg: logging.INFO,
- }
-
- # Change levels of some well-known messages to debug so they don't get
- # shown to the user.
- #
- # If a message starts with any text in suppressed_msgs, it's not logged as
- # error.
- suppressed_msgs = [
- # PNGs in Qt with broken color profile
- # https://bugreports.qt.io/browse/QTBUG-39788
- ('libpng warning: iCCP: Not recognizing known sRGB profile that has '
- 'been edited'),
- 'libpng warning: iCCP: known incorrect sRGB profile',
- # Hopefully harmless warning
- 'OpenType support missing for script ',
- # Error if a QNetworkReply gets two different errors set. Harmless Qt
- # bug on some pages.
- # https://bugreports.qt.io/browse/QTBUG-30298
- ('QNetworkReplyImplPrivate::error: Internal problem, this method must '
- 'only be called once.'),
- # Sometimes indicates missing text, but most of the time harmless
- 'load glyph failed ',
- # Harmless, see https://bugreports.qt.io/browse/QTBUG-42479
- ('content-type missing in HTTP POST, defaulting to '
- 'application/x-www-form-urlencoded. '
- 'Use QNetworkRequest::setHeader() to fix this problem.'),
- # https://bugreports.qt.io/browse/QTBUG-43118
- 'Using blocking call!',
- # Hopefully harmless
- ('"Method "GetAll" with signature "s" on interface '
- '"org.freedesktop.DBus.Properties" doesn\'t exist'),
- ('"Method \\"GetAll\\" with signature \\"s\\" on interface '
- '\\"org.freedesktop.DBus.Properties\\" doesn\'t exist\\n"'),
- 'WOFF support requires QtWebKit to be built with zlib support.',
- # Weird Enlightment/GTK X extensions
- 'QXcbWindow: Unhandled client message: "_E_',
- 'QXcbWindow: Unhandled client message: "_ECORE_',
- 'QXcbWindow: Unhandled client message: "_GTK_',
- # Happens on AppVeyor CI
- 'SetProcessDpiAwareness failed:',
- # https://bugreports.qt.io/browse/QTBUG-49174
- ('QObject::connect: Cannot connect (null)::stateChanged('
- 'QNetworkSession::State) to '
- 'QNetworkReplyHttpImpl::_q_networkSessionStateChanged('
- 'QNetworkSession::State)'),
- # https://bugreports.qt.io/browse/QTBUG-53989
- ("Image of format '' blocked because it is not considered safe. If "
- "you are sure it is safe to do so, you can white-list the format by "
- "setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST="),
- # Installing Qt from the installer may cause it looking for SSL3 or
- # OpenSSL 1.0 which may not be available on the system
- "QSslSocket: cannot resolve ",
- "QSslSocket: cannot call unresolved function ",
- # When enabling debugging with QtWebEngine
- ("Remote debugging server started successfully. Try pointing a "
- "Chromium-based browser to "),
- # https://github.com/qutebrowser/qutebrowser/issues/1287
- "QXcbClipboard: SelectionRequest too old",
- # https://github.com/qutebrowser/qutebrowser/issues/2071
- 'QXcbWindow: Unhandled client message: ""',
- # https://codereview.qt-project.org/176831
- "QObject::disconnect: Unexpected null parameter",
- # https://bugreports.qt.io/browse/QTBUG-76391
- "Attribute Qt::AA_ShareOpenGLContexts must be set before "
- "QCoreApplication is created.",
- # Qt 6.4 beta 1: https://bugreports.qt.io/browse/QTBUG-104741
- "GL format 0 is not supported",
- ]
- # not using utils.is_mac here, because we can't be sure we can successfully
- # import the utils module here.
- if sys.platform == 'darwin':
- suppressed_msgs += [
- # https://bugreports.qt.io/browse/QTBUG-47154
- ('virtual void QSslSocketBackendPrivate::transmit() SSLRead '
- 'failed with: -9805'),
- ]
-
- if not msg:
- msg = "Logged empty message!"
-
- if any(msg.strip().startswith(pattern) for pattern in suppressed_msgs):
- level = logging.DEBUG
- elif context.category == "qt.webenginecontext" and (
- msg.strip().startswith("GL Type: ") or # Qt 6.3
- msg.strip().startswith("GLImplementation:") # Qt 6.2
- ):
- level = logging.DEBUG
- else:
- level = qt_to_logging[msg_type]
-
- if context.line is None:
- lineno = -1 # type: ignore[unreachable]
- else:
- lineno = context.line
-
- if context.function is None:
- func = 'none' # type: ignore[unreachable]
- elif ':' in context.function:
- func = '"{}"'.format(context.function)
- else:
- func = context.function
-
- if context.category is None or context.category == 'default':
- name = 'qt'
- else:
- name = 'qt-' + context.category
- if msg.splitlines()[0] == ('This application failed to start because it '
- 'could not find or load the Qt platform plugin '
- '"xcb".'):
- # Handle this message specially.
- msg += ("\n\nOn Archlinux, this should fix the problem:\n"
- " pacman -S libxkbcommon-x11")
- faulthandler.disable()
-
- assert _args is not None
- if _args.debug:
- stack: Optional[str] = ''.join(traceback.format_stack())
- else:
- stack = None
-
- record = qt.makeRecord(name=name, level=level, fn=context.file, lno=lineno,
- msg=msg, args=(), exc_info=None, func=func,
- sinfo=stack)
- qt.handle(record)
-
-
-@contextlib.contextmanager
-def hide_qt_warning(pattern: str, logger: str = 'qt') -> Iterator[None]:
- """Hide Qt warnings matching the given regex."""
- log_filter = QtWarningFilter(pattern)
- logger_obj = logging.getLogger(logger)
- logger_obj.addFilter(log_filter)
- try:
- yield
- finally:
- logger_obj.removeFilter(log_filter)
-
-
def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
"""Initialize logging settings from the config.
@@ -578,24 +391,6 @@ def init_from_config(conf: 'configmodule.ConfigContainer') -> None:
change_console_formatter(level)
-class QtWarningFilter(logging.Filter):
-
- """Filter to filter Qt warnings.
-
- Attributes:
- _pattern: The start of the message.
- """
-
- def __init__(self, pattern: str) -> None:
- super().__init__()
- self._pattern = pattern
-
- def filter(self, record: logging.LogRecord) -> bool:
- """Determine if the specified record is to be logged."""
- do_log = not record.msg.strip().startswith(self._pattern)
- return do_log
-
-
class InvalidLogFilterError(Exception):
"""Raised when an invalid filter string is passed to LogFilter.parse()."""
diff --git a/qutebrowser/utils/qtlog.py b/qutebrowser/utils/qtlog.py
new file mode 100644
index 000000000..15e124b79
--- /dev/null
+++ b/qutebrowser/utils/qtlog.py
@@ -0,0 +1,241 @@
+# Copyright 2014-2023 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+#
+# This file is part of qutebrowser.
+#
+# qutebrowser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# qutebrowser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
+
+"""Loggers and utilities related to Qt logging."""
+
+import argparse
+import contextlib
+import faulthandler
+import logging
+import sys
+import traceback
+from typing import Iterator, Optional, Callable, cast
+
+from qutebrowser.qt import core as qtcore, machinery
+from qutebrowser.utils import log
+
+_args = None
+
+
+def init(args: argparse.Namespace) -> None:
+ """Install Qt message handler based on the argparse namespace passed."""
+ global _args
+ _args = args
+ qtcore.qInstallMessageHandler(qt_message_handler)
+
+
+@qtcore.pyqtSlot()
+def shutdown_log() -> None:
+ qtcore.qInstallMessageHandler(None)
+
+
+@contextlib.contextmanager
+def disable_qt_msghandler() -> Iterator[None]:
+ """Contextmanager which temporarily disables the Qt message handler."""
+ old_handler = qtcore.qInstallMessageHandler(None)
+ if machinery.IS_QT6:
+ # cast str to Optional[str] to be compatible with PyQt6 type hints for
+ # qInstallMessageHandler
+ old_handler = cast(
+ Optional[
+ Callable[
+ [qtcore.QtMsgType, qtcore.QMessageLogContext, Optional[str]],
+ None
+ ]
+ ],
+ old_handler,
+ )
+
+ try:
+ yield
+ finally:
+ qtcore.qInstallMessageHandler(old_handler)
+
+
+def qt_message_handler(msg_type: qtcore.QtMsgType,
+ context: qtcore.QMessageLogContext,
+ msg: Optional[str]) -> None:
+ """Qt message handler to redirect qWarning etc. to the logging system.
+
+ Args:
+ msg_type: The level of the message.
+ context: The source code location of the message.
+ msg: The message text.
+ """
+ # Mapping from Qt logging levels to the matching logging module levels.
+ # Note we map critical to ERROR as it's actually "just" an error, and fatal
+ # to critical.
+ qt_to_logging = {
+ qtcore.QtMsgType.QtDebugMsg: logging.DEBUG,
+ qtcore.QtMsgType.QtWarningMsg: logging.WARNING,
+ qtcore.QtMsgType.QtCriticalMsg: logging.ERROR,
+ qtcore.QtMsgType.QtFatalMsg: logging.CRITICAL,
+ qtcore.QtMsgType.QtInfoMsg: logging.INFO,
+ }
+
+ # Change levels of some well-known messages to debug so they don't get
+ # shown to the user.
+ #
+ # If a message starts with any text in suppressed_msgs, it's not logged as
+ # error.
+ suppressed_msgs = [
+ # PNGs in Qt with broken color profile
+ # https://bugreports.qt.io/browse/QTBUG-39788
+ ('libpng warning: iCCP: Not recognizing known sRGB profile that has '
+ 'been edited'),
+ 'libpng warning: iCCP: known incorrect sRGB profile',
+ # Hopefully harmless warning
+ 'OpenType support missing for script ',
+ # Error if a QNetworkReply gets two different errors set. Harmless Qt
+ # bug on some pages.
+ # https://bugreports.qt.io/browse/QTBUG-30298
+ ('QNetworkReplyImplPrivate::error: Internal problem, this method must '
+ 'only be called once.'),
+ # Sometimes indicates missing text, but most of the time harmless
+ 'load glyph failed ',
+ # Harmless, see https://bugreports.qt.io/browse/QTBUG-42479
+ ('content-type missing in HTTP POST, defaulting to '
+ 'application/x-www-form-urlencoded. '
+ 'Use QNetworkRequest::setHeader() to fix this problem.'),
+ # https://bugreports.qt.io/browse/QTBUG-43118
+ 'Using blocking call!',
+ # Hopefully harmless
+ ('"Method "GetAll" with signature "s" on interface '
+ '"org.freedesktop.DBus.Properties" doesn\'t exist'),
+ ('"Method \\"GetAll\\" with signature \\"s\\" on interface '
+ '\\"org.freedesktop.DBus.Properties\\" doesn\'t exist\\n"'),
+ 'WOFF support requires QtWebKit to be built with zlib support.',
+ # Weird Enlightment/GTK X extensions
+ 'QXcbWindow: Unhandled client message: "_E_',
+ 'QXcbWindow: Unhandled client message: "_ECORE_',
+ 'QXcbWindow: Unhandled client message: "_GTK_',
+ # Happens on AppVeyor CI
+ 'SetProcessDpiAwareness failed:',
+ # https://bugreports.qt.io/browse/QTBUG-49174
+ ('QObject::connect: Cannot connect (null)::stateChanged('
+ 'QNetworkSession::State) to '
+ 'QNetworkReplyHttpImpl::_q_networkSessionStateChanged('
+ 'QNetworkSession::State)'),
+ # https://bugreports.qt.io/browse/QTBUG-53989
+ ("Image of format '' blocked because it is not considered safe. If "
+ "you are sure it is safe to do so, you can white-list the format by "
+ "setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST="),
+ # Installing Qt from the installer may cause it looking for SSL3 or
+ # OpenSSL 1.0 which may not be available on the system
+ "QSslSocket: cannot resolve ",
+ "QSslSocket: cannot call unresolved function ",
+ # When enabling debugging with QtWebEngine
+ ("Remote debugging server started successfully. Try pointing a "
+ "Chromium-based browser to "),
+ # https://github.com/qutebrowser/qutebrowser/issues/1287
+ "QXcbClipboard: SelectionRequest too old",
+ # https://github.com/qutebrowser/qutebrowser/issues/2071
+ 'QXcbWindow: Unhandled client message: ""',
+ # https://codereview.qt-project.org/176831
+ "QObject::disconnect: Unexpected null parameter",
+ # https://bugreports.qt.io/browse/QTBUG-76391
+ "Attribute Qt::AA_ShareOpenGLContexts must be set before "
+ "QCoreApplication is created.",
+ # Qt 6.4 beta 1: https://bugreports.qt.io/browse/QTBUG-104741
+ "GL format 0 is not supported",
+ ]
+ # not using utils.is_mac here, because we can't be sure we can successfully
+ # import the utils module here.
+ if sys.platform == 'darwin':
+ suppressed_msgs += [
+ # https://bugreports.qt.io/browse/QTBUG-47154
+ ('virtual void QSslSocketBackendPrivate::transmit() SSLRead '
+ 'failed with: -9805'),
+ ]
+
+ if not msg:
+ msg = "Logged empty message!"
+
+ if any(msg.strip().startswith(pattern) for pattern in suppressed_msgs):
+ level = logging.DEBUG
+ elif context.category == "qt.webenginecontext" and (
+ msg.strip().startswith("GL Type: ") or # Qt 6.3
+ msg.strip().startswith("GLImplementation:") # Qt 6.2
+ ):
+ level = logging.DEBUG
+ else:
+ level = qt_to_logging[msg_type]
+
+ if context.line is None:
+ lineno = -1 # type: ignore[unreachable]
+ else:
+ lineno = context.line
+
+ if context.function is None:
+ func = 'none' # type: ignore[unreachable]
+ elif ':' in context.function:
+ func = '"{}"'.format(context.function)
+ else:
+ func = context.function
+
+ if context.category is None or context.category == 'default':
+ name = 'qt'
+ else:
+ name = 'qt-' + context.category
+ if msg.splitlines()[0] == ('This application failed to start because it '
+ 'could not find or load the Qt platform plugin '
+ '"xcb".'):
+ # Handle this message specially.
+ msg += ("\n\nOn Archlinux, this should fix the problem:\n"
+ " pacman -S libxkbcommon-x11")
+ faulthandler.disable()
+
+ assert _args is not None
+ if _args.debug:
+ stack: Optional[str] = ''.join(traceback.format_stack())
+ else:
+ stack = None
+
+ record = log.qt.makeRecord(name=name, level=level, fn=context.file, lno=lineno,
+ msg=msg, args=(), exc_info=None, func=func,
+ sinfo=stack)
+ log.qt.handle(record)
+
+
+class QtWarningFilter(logging.Filter):
+
+ """Filter to filter Qt warnings.
+
+ Attributes:
+ _pattern: The start of the message.
+ """
+
+ def __init__(self, pattern: str) -> None:
+ super().__init__()
+ self._pattern = pattern
+
+ def filter(self, record: logging.LogRecord) -> bool:
+ """Determine if the specified record is to be logged."""
+ do_log = not record.msg.strip().startswith(self._pattern)
+ return do_log
+
+
+@contextlib.contextmanager
+def hide_qt_warning(pattern: str, logger: str = 'qt') -> Iterator[None]:
+ """Hide Qt warnings matching the given regex."""
+ log_filter = QtWarningFilter(pattern)
+ logger_obj = logging.getLogger(logger)
+ logger_obj.addFilter(log_filter)
+ try:
+ yield
+ finally:
+ logger_obj.removeFilter(log_filter)
diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py
index 56524a031..caa86dfbb 100644
--- a/tests/end2end/test_invocations.py
+++ b/tests/end2end/test_invocations.py
@@ -25,6 +25,7 @@ import importlib
import re
import json
import platform
+from contextlib import nullcontext as does_not_raise
import pytest
from qutebrowser.qt.core import QProcess, QPoint
@@ -916,3 +917,15 @@ def test_sandboxing(
status = dict(line.split("\t") for line in lines)
assert status == expected_status
+
+
+@pytest.mark.not_frozen
+def test_logfilter_arg_does_not_crash(request, quteproc_new):
+ args = ['--temp-basedir', '--debug', '--logfilter', 'commands, init, ipc, webview']
+
+ with does_not_raise():
+ quteproc_new.start(args=args + _base_args(request.config))
+
+ # Waiting for quit to make sure no other warning is emitted
+ quteproc_new.send_cmd(':quit')
+ quteproc_new.wait_for_quit()
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 51b014f81..6eb1c4e4f 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -22,11 +22,9 @@ import argparse
import itertools
import sys
import warnings
-import dataclasses
import pytest
import _pytest.logging # pylint: disable=import-private-name
-from qutebrowser.qt import core as qtcore
from qutebrowser import qutebrowser
from qutebrowser.utils import log
@@ -241,7 +239,7 @@ class TestInitLog:
@pytest.fixture(autouse=True)
def setup(self, mocker):
- mocker.patch('qutebrowser.utils.log.qtcore.qInstallMessageHandler',
+ mocker.patch('qutebrowser.utils.qtlog.qtcore.qInstallMessageHandler',
autospec=True)
yield
# Make sure logging is in a sensible default state
@@ -342,35 +340,6 @@ class TestInitLog:
assert log.console_filter.names == {'misc'}
-class TestHideQtWarning:
-
- """Tests for hide_qt_warning/QtWarningFilter."""
-
- @pytest.fixture
- def qt_logger(self):
- return logging.getLogger('qt-tests')
-
- def test_unfiltered(self, qt_logger, caplog):
- with log.hide_qt_warning("World", 'qt-tests'):
- with caplog.at_level(logging.WARNING, 'qt-tests'):
- qt_logger.warning("Hello World")
- assert len(caplog.records) == 1
- record = caplog.records[0]
- assert record.levelname == 'WARNING'
- assert record.message == "Hello World"
-
- @pytest.mark.parametrize('line', [
- "Hello", # exact match
- "Hello World", # match at start of line
- " Hello World ", # match with spaces
- ])
- def test_filtered(self, qt_logger, caplog, line):
- with log.hide_qt_warning("Hello", 'qt-tests'):
- with caplog.at_level(logging.WARNING, 'qt-tests'):
- qt_logger.warning(line)
- assert not caplog.records
-
-
@pytest.mark.parametrize('suffix, expected', [
('', 'STUB: test_stub'),
('foo', 'STUB: test_stub (foo)'),
@@ -405,27 +374,3 @@ def test_warning_still_errors():
# Mainly a sanity check after the tests messing with warnings above.
with pytest.raises(UserWarning):
warnings.warn("error", UserWarning)
-
-
-class TestQtMessageHandler:
-
- @dataclasses.dataclass
- class Context:
-
- """Fake QMessageLogContext."""
-
- function: str = None
- category: str = None
- file: str = None
- line: int = None
-
- @pytest.fixture(autouse=True)
- def init_args(self):
- parser = qutebrowser.get_argparser()
- args = parser.parse_args([])
- log.init_log(args)
-
- def test_empty_message(self, caplog):
- """Make sure there's no crash with an empty message."""
- log.qt_message_handler(qtcore.QtMsgType.QtDebugMsg, self.Context(), "")
- assert caplog.messages == ["Logged empty message!"]
diff --git a/tests/unit/utils/test_qtlog.py b/tests/unit/utils/test_qtlog.py
new file mode 100644
index 000000000..3dd62b9a9
--- /dev/null
+++ b/tests/unit/utils/test_qtlog.py
@@ -0,0 +1,82 @@
+# Copyright 2014-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+#
+# This file is part of qutebrowser.
+#
+# qutebrowser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# qutebrowser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
+
+
+"""Tests for qutebrowser.utils.qtlog."""
+
+import dataclasses
+import logging
+
+import pytest
+
+from qutebrowser import qutebrowser
+from qutebrowser.utils import qtlog
+
+from qutebrowser.qt import core as qtcore
+
+
+class TestQtMessageHandler:
+
+ @dataclasses.dataclass
+ class Context:
+
+ """Fake QMessageLogContext."""
+
+ function: str = None
+ category: str = None
+ file: str = None
+ line: int = None
+
+ @pytest.fixture(autouse=True)
+ def init_args(self):
+ parser = qutebrowser.get_argparser()
+ args = parser.parse_args([])
+ qtlog.init(args)
+
+ def test_empty_message(self, caplog):
+ """Make sure there's no crash with an empty message."""
+ qtlog.qt_message_handler(qtcore.QtMsgType.QtDebugMsg, self.Context(), "")
+ assert caplog.messages == ["Logged empty message!"]
+
+
+class TestHideQtWarning:
+
+ """Tests for hide_qt_warning/QtWarningFilter."""
+
+ @pytest.fixture
+ def qt_logger(self):
+ return logging.getLogger('qt-tests')
+
+ def test_unfiltered(self, qt_logger, caplog):
+ with qtlog.hide_qt_warning("World", 'qt-tests'):
+ with caplog.at_level(logging.WARNING, 'qt-tests'):
+ qt_logger.warning("Hello World")
+ assert len(caplog.records) == 1
+ record = caplog.records[0]
+ assert record.levelname == 'WARNING'
+ assert record.message == "Hello World"
+
+ @pytest.mark.parametrize('line', [
+ "Hello", # exact match
+ "Hello World", # match at start of line
+ " Hello World ", # match with spaces
+ ])
+ def test_filtered(self, qt_logger, caplog, line):
+ with qtlog.hide_qt_warning("Hello", 'qt-tests'):
+ with caplog.at_level(logging.WARNING, 'qt-tests'):
+ qt_logger.warning(line)
+ assert not caplog.records