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

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2015-10-29 14:06:50 -04:00
parent 6cd9bab35b
commit d836973a04
4 changed files with 67 additions and 9 deletions

View File

@ -1,10 +1,13 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import logging
import os import os
import texttable import texttable
from compose.cli import colors
def get_tty_width(): def get_tty_width():
tty_size = os.popen('stty size', 'r').read().split() tty_size = os.popen('stty size', 'r').read().split()
@ -15,6 +18,7 @@ def get_tty_width():
class Formatter(object): class Formatter(object):
"""Format tabular data for printing."""
def table(self, headers, rows): def table(self, headers, rows):
table = texttable.Texttable(max_width=get_tty_width()) table = texttable.Texttable(max_width=get_tty_width())
table.set_cols_dtype(['t' for h in headers]) table.set_cols_dtype(['t' for h in headers])
@ -23,3 +27,22 @@ class Formatter(object):
table.set_chars(['-', '|', '+', '-']) table.set_chars(['-', '|', '+', '-'])
return table.draw() 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

View File

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

View File

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

View File

@ -1,11 +1,15 @@
from __future__ import absolute_import from __future__ import absolute_import
import logging
from compose import container from compose import container
from compose.cli.errors import UserError from compose.cli.errors import UserError
from compose.cli.formatter import ConsoleWarningFormatter
from compose.cli.log_printer import LogPrinter from compose.cli.log_printer import LogPrinter
from compose.cli.main import attach_to_logs from compose.cli.main import attach_to_logs
from compose.cli.main import build_log_printer from compose.cli.main import build_log_printer
from compose.cli.main import convergence_strategy_from_opts from compose.cli.main import convergence_strategy_from_opts
from compose.cli.main import setup_console_handler
from compose.project import Project from compose.project import Project
from compose.service import ConvergenceStrategy from compose.service import ConvergenceStrategy
from tests import mock from tests import mock
@ -60,6 +64,31 @@ class CLIMainTestCase(unittest.TestCase):
timeout=timeout) 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): class ConvergeStrategyFromOptsTestCase(unittest.TestCase):
def test_invalid_opts(self): def test_invalid_opts(self):