Repository: qutebrowser/qutebrowser

Issue:
:messages command
There should be a command like `:messages` in vim to view a list of past error messages



Discussion:

Resolving patch:
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -1339,6 +1339,27 @@ def show_help(self, tab=False, bg=False, window=False, topic=None):
         url = QUrl('qute://help/{}'.format(path))
         self._open(url, tab, bg, window)
 
+    @cmdutils.register(instance='command-dispatcher', scope='window')
+    def messages(self, level='error', plain=False, tab=False, bg=False,
+                 window=False):
+        """Show a log of past messages.
+
+        Args:
+            level: Include messages with `level` or higher severity.
+                   Valid values: vdebug, debug, info, warning, error, critical.
+            plain: Whether to show plaintext (as opposed to html).
+            tab: Open in a new tab.
+            bg: Open in a background tab.
+            window: Open in a new window.
+        """
+        if level.upper() not in log.LOG_LEVELS:
+            raise cmdexc.CommandError("Invalid log level {}!".format(level))
+        if plain:
+            url = QUrl('qute://plainlog?level={}'.format(level))
+        else:
+            url = QUrl('qute://log?level={}'.format(level))
+        self._open(url, tab, bg, window)
+
     @cmdutils.register(instance='command-dispatcher',
                        modes=[KeyMode.insert], hide=True, scope='window')
     def open_editor(self):
diff --git a/qutebrowser/browser/network/qutescheme.py b/qutebrowser/browser/network/qutescheme.py
--- a/qutebrowser/browser/network/qutescheme.py
+++ b/qutebrowser/browser/network/qutescheme.py
@@ -26,6 +26,7 @@
 import functools
 import configparser
 import mimetypes
+import urllib.parse
 
 from PyQt5.QtCore import pyqtSlot, QObject
 from PyQt5.QtNetwork import QNetworkReply
@@ -158,23 +159,36 @@ def qute_version(_win_id, _request):
 
 
 @add_handler('plainlog')
-def qute_plainlog(_win_id, _request):
-    """Handler for qute:plainlog. Return HTML content as bytes."""
+def qute_plainlog(_win_id, request):
+    """Handler for qute:plainlog. Return HTML content as bytes.
+
+    An optional query parameter specifies the minimum log level to print.
+    For example, qute://log?level=warning prints warnings and errors.
+    Level can be one of: vdebug, debug, info, warning, error, critical.
+    """
     if log.ram_handler is None:
         text = "Log output was disabled."
     else:
-        text = log.ram_handler.dump_log()
+        level = urllib.parse.parse_qs(request.url().query()).get('level')[0]
+        text = log.ram_handler.dump_log(html=False, level=level)
     html = jinja.render('pre.html', title='log', content=text)
     return html.encode('UTF-8', errors='xmlcharrefreplace')
 
 
 @add_handler('log')
-def qute_log(_win_id, _request):
-    """Handler for qute:log. Return HTML content as bytes."""
+def qute_log(_win_id, request):
+    """Handler for qute:log. Return HTML content as bytes.
+
+    An optional query parameter specifies the minimum log level to print.
+    For example, qute://log?level=warning prints warnings and errors.
+    Level can be one of: vdebug, debug, info, warning, error, critical.
+    """
     if log.ram_handler is None:
         html_log = None
     else:
-        html_log = log.ram_handler.dump_log(html=True)
+        level = urllib.parse.parse_qs(request.url().query()).get('level')[0]
+        html_log = log.ram_handler.dump_log(html=True, level=level)
+
     html = jinja.render('log.html', title='log', content=html_log)
     return html.encode('UTF-8', errors='xmlcharrefreplace')
 
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -64,13 +64,21 @@
     'CRITICAL': 'red',
 }
 
-
 # We first monkey-patch logging to support our VDEBUG level before getting the
 # loggers.  Based on http://stackoverflow.com/a/13638084
 VDEBUG_LEVEL = 9
 logging.addLevelName(VDEBUG_LEVEL, 'VDEBUG')
 logging.VDEBUG = VDEBUG_LEVEL
 
+LOG_LEVELS = {
+    'VDEBUG': logging.VDEBUG,
+    'DEBUG': logging.DEBUG,
+    'INFO': logging.INFO,
+    'WARNING': logging.WARNING,
+    'ERROR': logging.ERROR,
+    'CRITICAL': logging.CRITICAL,
+}
+
 
 def vdebug(self, msg, *args, **kwargs):
     """Log with a VDEBUG level.
@@ -429,13 +437,14 @@ def emit(self, record):
             # We don't log VDEBUG to RAM.
             self._data.append(record)
 
-    def dump_log(self, html=False):
+    def dump_log(self, html=False, level='vdebug'):
         """Dump the complete formatted log data as as string.
 
         FIXME: We should do all the HTML formatter via jinja2.
         (probably obsolete when moving to a widget for logging,
         https://github.com/The-Compiler/qutebrowser/issues/34
         """
+        minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL)
         lines = []
         fmt = self.html_formatter.format if html else self.format
         self.acquire()
@@ -444,7 +453,8 @@ def dump_log(self, html=False):
         finally:
             self.release()
         for record in records:
-            lines.append(fmt(record))
+            if record.levelno >= minlevel:
+                lines.append(fmt(record))
         return '\n'.join(lines)
 
 


Test patch:
diff --git a/tests/integration/features/conftest.py b/tests/integration/features/conftest.py
--- a/tests/integration/features/conftest.py
+++ b/tests/integration/features/conftest.py
@@ -366,6 +366,14 @@ def check_contents_plain(quteproc, text):
     assert text in content
 
 
+@bdd.then(bdd.parsers.parse('the page should not contain the plaintext '
+                            '"{text}"'))
+def check_not_contents_plain(quteproc, text):
+    """Check the current page's content based on a substring."""
+    content = quteproc.get_content().strip()
+    assert text not in content
+
+
 @bdd.then(bdd.parsers.parse('the json on the page should be:\n{text}'))
 def check_contents_json(quteproc, text):
     """Check the current page's content as json."""
diff --git a/tests/integration/features/misc.feature b/tests/integration/features/misc.feature
--- a/tests/integration/features/misc.feature
+++ b/tests/integration/features/misc.feature
@@ -342,7 +342,7 @@ Feature: Various utility commands.
     Scenario: Running :pyeval with --quiet
         When I run :debug-pyeval --quiet 1+1
         Then "pyeval output: 2" should be logged
-        
+
     ## https://github.com/The-Compiler/qutebrowser/issues/504
 
     Scenario: Focusing download widget via Tab
@@ -367,3 +367,42 @@ Feature: Various utility commands.
         When I set network -> custom-headers to {"X-Qute-Test": "testvalue"}
         And I open headers
         Then the header X-Qute-Test should be set to testvalue
+
+    ## :messages
+
+    Scenario: Showing error messages
+        When I run :message-error the-error-message
+        And I run :message-warning the-warning-message
+        And I run :message-info the-info-message
+        And I run :messages
+        Then the error "the-error-message" should be shown
+        And the warning "the-warning-message" should be shown
+        And the page should contain the plaintext "the-error-message"
+        And the page should not contain the plaintext "the-warning-message"
+        And the page should not contain the plaintext "the-info-message"
+
+    Scenario: Showing messages of type 'warning' or greater
+        When I run :message-error the-error-message
+        And I run :message-warning the-warning-message
+        And I run :message-info the-info-message
+        And I run :messages warning
+        Then the error "the-error-message" should be shown
+        And the warning "the-warning-message" should be shown
+        And the page should contain the plaintext "the-error-message"
+        And the page should contain the plaintext "the-warning-message"
+        And the page should not contain the plaintext "the-info-message"
+
+    Scenario: Showing messages of type 'info' or greater
+        When I run :message-error the-error-message
+        And I run :message-warning the-warning-message
+        And I run :message-info the-info-message
+        And I run :messages info
+        Then the error "the-error-message" should be shown
+        And the warning "the-warning-message" should be shown
+        And the page should contain the plaintext "the-error-message"
+        And the page should contain the plaintext "the-warning-message"
+        And the page should contain the plaintext "the-info-message"
+
+    Scenario: Showing messages of an invalid level
+        When I run :messages cataclysmic
+        Then the error "Invalid log level cataclysmic!" should be shown