Browse Source

Use colors when logging warnings or errors, so they are more obvious.

Signed-off-by: Daniel Nephin <[email protected]>
Daniel Nephin 10 years ago
parent
commit
d836973a04
4 changed files with 66 additions and 8 deletions
  1. 23 0
      compose/cli/formatter.py
  2. 13 7
      compose/cli/main.py
  3. 1 1
      compose/config/interpolation.py
  4. 29 0
      tests/unit/cli/main_test.py

+ 23 - 0
compose/cli/formatter.py

@@ -1,10 +1,13 @@
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
+import logging
 import os
 
 import texttable
 
+from compose.cli import colors
+
 
 def get_tty_width():
     tty_size = os.popen('stty size', 'r').read().split()
@@ -15,6 +18,7 @@ def get_tty_width():
 
 
 class Formatter(object):
+    """Format tabular data for printing."""
     def table(self, headers, rows):
         table = texttable.Texttable(max_width=get_tty_width())
         table.set_cols_dtype(['t' for h in headers])
@@ -23,3 +27,22 @@ class Formatter(object):
         table.set_chars(['-', '|', '+', '-'])
 
         return table.draw()
+
+
+class ConsoleWarningFormatter(logging.Formatter):
+    """A logging.Formatter which prints WARNING and ERROR messages with
+    a prefix of the log level colored appropriate for the log level.
+    """
+
+    def get_level_message(self, record):
+        separator = ': '
+        if record.levelno == logging.WARNING:
+            return colors.yellow(record.levelname) + separator
+        if record.levelno == logging.ERROR:
+            return colors.red(record.levelname) + separator
+
+        return ''
+
+    def format(self, record):
+        message = super(ConsoleWarningFormatter, self).format(record)
+        return self.get_level_message(record) + message

+ 13 - 7
compose/cli/main.py

@@ -28,6 +28,7 @@ from .command import project_from_options
 from .docopt_command import DocoptCommand
 from .docopt_command import NoSuchCommand
 from .errors import UserError
+from .formatter import ConsoleWarningFormatter
 from .formatter import Formatter
 from .log_printer import LogPrinter
 from .utils import get_version_info
@@ -41,7 +42,7 @@ log = logging.getLogger(__name__)
 console_handler = logging.StreamHandler(sys.stderr)
 
 INSECURE_SSL_WARNING = """
-Warning: --allow-insecure-ssl is deprecated and has no effect.
+--allow-insecure-ssl is deprecated and has no effect.
 It will be removed in a future version of Compose.
 """
 
@@ -91,13 +92,18 @@ def setup_logging():
     logging.getLogger("requests").propagate = False
 
 
-def setup_console_handler(verbose):
+def setup_console_handler(handler, verbose):
+    if handler.stream.isatty():
+        format_class = ConsoleWarningFormatter
+    else:
+        format_class = logging.Formatter
+
     if verbose:
-        console_handler.setFormatter(logging.Formatter('%(name)s.%(funcName)s: %(message)s'))
-        console_handler.setLevel(logging.DEBUG)
+        handler.setFormatter(format_class('%(name)s.%(funcName)s: %(message)s'))
+        handler.setLevel(logging.DEBUG)
     else:
-        console_handler.setFormatter(logging.Formatter())
-        console_handler.setLevel(logging.INFO)
+        handler.setFormatter(format_class())
+        handler.setLevel(logging.INFO)
 
 
 # stolen from docopt master
@@ -153,7 +159,7 @@ class TopLevelCommand(DocoptCommand):
         return options
 
     def perform_command(self, options, handler, command_options):
-        setup_console_handler(options.get('--verbose'))
+        setup_console_handler(console_handler, options.get('--verbose'))
 
         if options['COMMAND'] in ('help', 'version'):
             # Skip looking up the compose file.

+ 1 - 1
compose/config/interpolation.py

@@ -78,7 +78,7 @@ class BlankDefaultDict(dict):
         except KeyError:
             if key not in self.missing_keys:
                 log.warn(
-                    "The {} variable is not set. Substituting a blank string."
+                    "The {} variable is not set. Defaulting to a blank string."
                     .format(key)
                 )
                 self.missing_keys.append(key)

+ 29 - 0
tests/unit/cli/main_test.py

@@ -1,11 +1,15 @@
 from __future__ import absolute_import
 
+import logging
+
 from compose import container
 from compose.cli.errors import UserError
+from compose.cli.formatter import ConsoleWarningFormatter
 from compose.cli.log_printer import LogPrinter
 from compose.cli.main import attach_to_logs
 from compose.cli.main import build_log_printer
 from compose.cli.main import convergence_strategy_from_opts
+from compose.cli.main import setup_console_handler
 from compose.project import Project
 from compose.service import ConvergenceStrategy
 from tests import mock
@@ -60,6 +64,31 @@ class CLIMainTestCase(unittest.TestCase):
             timeout=timeout)
 
 
+class SetupConsoleHandlerTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.stream = mock.Mock()
+        self.stream.isatty.return_value = True
+        self.handler = logging.StreamHandler(stream=self.stream)
+
+    def test_with_tty_verbose(self):
+        setup_console_handler(self.handler, True)
+        assert type(self.handler.formatter) == ConsoleWarningFormatter
+        assert '%(name)s' in self.handler.formatter._fmt
+        assert '%(funcName)s' in self.handler.formatter._fmt
+
+    def test_with_tty_not_verbose(self):
+        setup_console_handler(self.handler, False)
+        assert type(self.handler.formatter) == ConsoleWarningFormatter
+        assert '%(name)s' not in self.handler.formatter._fmt
+        assert '%(funcName)s' not in self.handler.formatter._fmt
+
+    def test_with_not_a_tty(self):
+        self.stream.isatty.return_value = False
+        setup_console_handler(self.handler, False)
+        assert type(self.handler.formatter) == logging.Formatter
+
+
 class ConvergeStrategyFromOptsTestCase(unittest.TestCase):
 
     def test_invalid_opts(self):