diff --git a/cloudinit/logging.py b/cloudinit/logging.py index 59656922..a1e52840 100644 --- a/cloudinit/logging.py +++ b/cloudinit/logging.py @@ -40,6 +40,7 @@ class _BlatherLoggerAdapter(logging.LoggerAdapter): # TODO(harlowja): we should remove when we no longer have to support 2.6... if sys.version_info[0:2] == (2, 6): # pragma: nocover + from logutils.dictconfig import dictConfig class _FixedBlatherLoggerAdapter(_BlatherLoggerAdapter): """Ensures isEnabledFor() exists on adapters that are created.""" @@ -72,6 +73,7 @@ if sys.version_info[0:2] == (2, 6): # pragma: nocover self.lock = None else: + from logging.config import dictConfig _NullHandler = logging.NullHandler @@ -80,3 +82,32 @@ def getLogger(name=_BASE, extra=None): if not logger.handlers: logger.addHandler(_NullHandler()) return _BlatherLoggerAdapter(logger, extra=extra) + + +def configure_logging(log_to_console=False): + logging_config = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'standard': { + 'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s', + }, + }, + 'handlers': { + 'console': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + 'formatter': 'standard', + }, + }, + 'loggers': { + '': { + 'handlers': [], + 'level': 'DEBUG', + 'propagate': True, + }, + }, + } + if log_to_console: + logging_config['loggers']['']['handlers'].append('console') + dictConfig(logging_config) diff --git a/cloudinit/shell.py b/cloudinit/shell.py index 481d6d13..33fc9914 100644 --- a/cloudinit/shell.py +++ b/cloudinit/shell.py @@ -6,6 +6,8 @@ import argparse import sys +from cloudinit import logging + def populate_parser(parser, common, subcommands): """Populate an ArgumentParser with data rather than code @@ -48,6 +50,7 @@ def main(args=sys.argv): if not hasattr(parsed, 'func'): parser.error('too few arguments') + logging.configure_logging(log_to_console=parsed.log_to_console) parsed.func(parsed) return 0 @@ -63,6 +66,7 @@ def unimplemented_subcommand(args): COMMON_ARGS = [ + (('--log-to-console',), {'action': 'store_true', 'default': False}), (('--verbose', '-v'), {'action': 'count', 'default': 0}), ] diff --git a/cloudinit/tests/test_shell.py b/cloudinit/tests/test_shell.py index 4bd65f6c..2b115862 100644 --- a/cloudinit/tests/test_shell.py +++ b/cloudinit/tests/test_shell.py @@ -46,3 +46,18 @@ class TestMain(TestCase): self.assertRaises(SystemExit, shell.main, args=['cloud-init']) self.assertIn('cloud-init: error: too few arguments', stderr.getvalue()) + + +class TestLoggingConfiguration(TestCase): + + @mock.patch('cloudinit.shell.sys.stderr', new_callable=six.StringIO) + def test_log_to_console(self, stderr): + shell.main(args=['cloud-init', '--log-to-console', 'version']) + shell.logging.getLogger().info('test log message') + self.assertIn('test log message', stderr.getvalue()) + + @mock.patch('cloudinit.shell.sys.stderr', new_callable=six.StringIO) + def test_log_to_console_not_default(self, stderr): + shell.main(args=['cloud-init', 'version']) + shell.logging.getLogger().info('test log message') + self.assertNotIn('test log message', stderr.getvalue()) diff --git a/tox.ini b/tox.ini index 7b1c0221..1a47b3f8 100644 --- a/tox.ini +++ b/tox.ini @@ -22,6 +22,7 @@ install_command = pip install {opts} {packages} [testenv:py26] deps = {[testenv]deps} importlib + logutils [testenv:py27-coverage] commands = {envpython} {toxinidir}/tools/noproxy nosetests --with-coverage --cover-erase --cover-package=cloudinit --cover-min-percentage=90 --cover-html {posargs}