Create log configuration class

Configuration of logging gets triggered twice.  The first time it
uses the CLI options when the application is started and second
it uses the configuration file after that is read.  The state of
the logging needs to be saved from the first to the second time,
so I created a class.

Implements: blueprint logging-migration

Change-Id: I7b8d1a3b6fd128e98cafd7c16009c7b694a52146
This commit is contained in:
TerryHowe 2015-08-10 12:33:28 -06:00 committed by Terry Howe
parent 6c46355734
commit 85a03945f0
4 changed files with 177 additions and 250 deletions

View File

@ -11,9 +11,10 @@
# under the License.
#
"""Context and Formatter"""
"""Application logging"""
import logging
import sys
import warnings
@ -99,76 +100,71 @@ class _FileFormatter(logging.Formatter):
logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT)
def setup_handler_logging_level(handler_type, level):
"""Setup of the handler for set the logging level
class LogConfigurator(object):
:param handler_type: type of logging handler
:param level: logging level
:return: None
"""
# Set the handler logging level of FileHandler(--log-file)
# and StreamHandler
for h in logging.getLogger('').handlers:
if type(h) is handler_type:
h.setLevel(level)
_CONSOLE_MESSAGE_FORMAT = '%(message)s'
def __init__(self, options):
self.root_logger = logging.getLogger('')
self.root_logger.setLevel(logging.DEBUG)
def setup_logging(shell, cloud_config):
"""Get one cloud configuration from configuration file and setup logging
# Force verbose_level 3 on --debug
self.dump_trace = False
if options.debug:
options.verbose_level = 3
self.dump_trace = True
:param shell: instance of openstackclient shell
:param cloud_config:
instance of the cloud specified by --os-cloud
in the configuration file
:return: None
"""
# Always send higher-level messages to the console via stderr
self.console_logger = logging.StreamHandler(sys.stderr)
log_level = log_level_from_options(options)
self.console_logger.setLevel(log_level)
formatter = logging.Formatter(self._CONSOLE_MESSAGE_FORMAT)
self.console_logger.setFormatter(formatter)
self.root_logger.addHandler(self.console_logger)
log_level = log_level_from_config(cloud_config.config)
set_warning_filter(log_level)
# Set the warning filter now
set_warning_filter(log_level)
log_file = cloud_config.config.get('log_file', None)
if log_file:
# setup the logging context
formatter = _FileFormatter(config=cloud_config)
# setup the logging handler
log_handler = _setup_handler_for_logging(
logging.FileHandler,
log_level,
file_name=log_file,
formatter=formatter,
)
if log_level == logging.DEBUG:
# DEBUG only.
# setup the operation_log
shell.enable_operation_logging = True
shell.operation_log.setLevel(logging.DEBUG)
shell.operation_log.addHandler(log_handler)
# Set up logging to a file
self.file_logger = None
log_file = options.log_file
if log_file:
self.file_logger = logging.FileHandler(filename=log_file)
self.file_logger.setFormatter(_FileFormatter(options=options))
self.file_logger.setLevel(log_level)
self.root_logger.addHandler(self.file_logger)
# Requests logs some stuff at INFO that we don't want
# unless we have DEBUG
requests_log = logging.getLogger("requests")
def _setup_handler_for_logging(handler_type, level, file_name, formatter):
"""Setup of the handler
# Other modules we don't want DEBUG output for
cliff_log = logging.getLogger('cliff')
stevedore_log = logging.getLogger('stevedore')
iso8601_log = logging.getLogger("iso8601")
Setup of the handler for addition of the logging handler,
changes of the logging format, change of the logging level,
if options.debug:
# --debug forces traceback
requests_log.setLevel(logging.DEBUG)
else:
requests_log.setLevel(logging.ERROR)
:param handler_type: type of logging handler
:param level: logging level
:param file_name: name of log-file
:param formatter: instance of logging.Formatter
:return: logging handler
"""
cliff_log.setLevel(logging.ERROR)
stevedore_log.setLevel(logging.ERROR)
iso8601_log.setLevel(logging.ERROR)
root_logger = logging.getLogger('')
handler = None
# Setup handler for FileHandler(--os-cloud)
handler = logging.FileHandler(
filename=file_name,
)
handler.setFormatter(formatter)
handler.setLevel(level)
def configure(self, cloud_config):
log_level = log_level_from_config(cloud_config.config)
set_warning_filter(log_level)
self.dump_trace = cloud_config.config.get('debug', self.dump_trace)
self.console_logger.setLevel(log_level)
# If both `--log-file` and `--os-cloud` are specified,
# the log is output to each file.
root_logger.addHandler(handler)
return handler
log_file = cloud_config.config.get('log_file', None)
if log_file:
if not self.file_logger:
self.file_logger = logging.FileHandler(filename=log_file)
formatter = _FileFormatter(cloud_config=cloud_config)
self.file_logger.setFormatter(formatter)
self.file_logger.setFormatter(_FileFormatter(config=cloud_config))
self.file_logger.setLevel(log_level)
self.root_logger.addHandler(self.file_logger)

View File

@ -94,60 +94,12 @@ class OpenStackShell(app.App):
self.verify = True
self.client_manager = None
# Operation log
self.enable_operation_logging = False
self.command_options = None
def configure_logging(self):
"""Configure logging for the app
Cliff sets some defaults we don't want so re-work it a bit
"""
if self.options.debug:
# --debug forces verbose_level 3
# Set this here so cliff.app.configure_logging() can work
self.options.verbose_level = 3
super(OpenStackShell, self).configure_logging()
# Set logging to the requested level
log_level = context.log_level_from_options(self.options)
context.set_warning_filter(log_level)
# Set the handler logging level of FileHandler(--log-file)
# and StreamHandler
if self.options.log_file:
context.setup_handler_logging_level(logging.FileHandler, log_level)
context.setup_handler_logging_level(logging.StreamHandler, log_level)
# Requests logs some stuff at INFO that we don't want
# unless we have DEBUG
requests_log = logging.getLogger("requests")
# Other modules we don't want DEBUG output for
cliff_log = logging.getLogger('cliff')
stevedore_log = logging.getLogger('stevedore')
iso8601_log = logging.getLogger("iso8601")
if self.options.debug:
# --debug forces traceback
self.dump_stack_trace = True
requests_log.setLevel(logging.DEBUG)
else:
self.dump_stack_trace = False
requests_log.setLevel(logging.ERROR)
cliff_log.setLevel(logging.ERROR)
stevedore_log.setLevel(logging.ERROR)
iso8601_log.setLevel(logging.ERROR)
# Operation logging
self.operation_log = logging.getLogger("operation_log")
self.operation_log.setLevel(logging.ERROR)
self.operation_log.propagate = False
"""Configure logging for the app."""
self.log_configurator = context.LogConfigurator(self.options)
self.dump_stack_trace = self.log_configurator.dump_trace
def run(self, argv):
ret_val = 1
@ -162,8 +114,6 @@ class OpenStackShell(app.App):
self.log.error(traceback.format_exc(e))
else:
self.log.error('Exception raised: ' + str(e))
if self.enable_operation_logging:
self.operation_log.error(traceback.format_exc(e))
return ret_val
@ -287,9 +237,8 @@ class OpenStackShell(app.App):
argparse=self.options,
)
# Set up every time record log in file and logging start
context.setup_logging(self, self.cloud)
self.log_configurator.configure(self.cloud)
self.dump_stack_trace = self.log_configurator.dump_trace
self.log.info("START with options: %s", self.command_options)
self.log.debug("options: %s", self.options)
self.log.debug("defaults: %s", cc.defaults)

View File

@ -13,7 +13,6 @@
import logging
import mock
import os
from openstackclient.common import context
from openstackclient.tests import utils
@ -21,47 +20,6 @@ from openstackclient.tests import utils
class TestContext(utils.TestCase):
TEST_LOG_FILE = "/tmp/test_log_file"
def setUp(self):
super(TestContext, self).setUp()
def tearDown(self):
super(TestContext, self).tearDown()
if os.path.exists(self.TEST_LOG_FILE):
os.remove(self.TEST_LOG_FILE)
def setup_handler_logging_level(self):
handler_type = logging.FileHandler
handler = logging.FileHandler(filename=self.TEST_LOG_FILE)
handler.setLevel(logging.ERROR)
logging.getLogger('').addHandler(handler)
context.setup_handler_logging_level(handler_type, logging.INFO)
self.log.info("test log")
ld = open(self.TEST_LOG_FILE)
line = ld.readlines()
ld.close()
if os.path.exists(self.TEST_LOG_FILE):
os.remove(self.TEST_LOG_FILE)
self.assertGreaterEqual(line.find("test log"), 0)
@mock.patch("openstackclient.common.context._setup_handler_for_logging")
def test_setup_logging(self, setuph):
setuph.return_value = mock.MagicMock()
shell = mock.MagicMock()
cloud_config = mock.MagicMock()
cloud_config.auth = {
'project_name': 'heart-o-gold',
'username': 'zaphod'
}
cloud_config.config = {
'log_level': 'debug',
'log_file': self.TEST_LOG_FILE,
'cloud': 'megadodo'
}
context.setup_logging(shell, cloud_config)
self.assertEqual(True, shell.enable_operation_logging)
def test_log_level_from_options(self):
opts = mock.Mock()
opts.verbose_level = 0
@ -132,3 +90,113 @@ class TestFileFormatter(utils.TestCase):
self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [cloudy usernamey projecty] %(message)s'),
formatter.fmt)
class TestLogConfigurator(utils.TestCase):
def setUp(self):
super(TestLogConfigurator, self).setUp()
self.options = mock.Mock()
self.options.verbose_level = 1
self.options.log_file = None
self.options.debug = False
self.root_logger = mock.Mock()
self.root_logger.setLevel = mock.Mock()
self.root_logger.addHandler = mock.Mock()
self.requests_log = mock.Mock()
self.requests_log.setLevel = mock.Mock()
self.cliff_log = mock.Mock()
self.cliff_log.setLevel = mock.Mock()
self.stevedore_log = mock.Mock()
self.stevedore_log.setLevel = mock.Mock()
self.iso8601_log = mock.Mock()
self.iso8601_log.setLevel = mock.Mock()
self.loggers = [
self.root_logger,
self.requests_log,
self.cliff_log,
self.stevedore_log,
self.iso8601_log]
@mock.patch('logging.StreamHandler')
@mock.patch('logging.getLogger')
@mock.patch('openstackclient.common.context.set_warning_filter')
def test_init(self, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
console_logger = mock.Mock()
console_logger.setFormatter = mock.Mock()
console_logger.setLevel = mock.Mock()
handle.return_value = console_logger
configurator = context.LogConfigurator(self.options)
getLogger.assert_called_with('iso8601') # last call
warning_filter.assert_called_with(logging.WARNING)
self.root_logger.setLevel.assert_called_with(logging.DEBUG)
self.root_logger.addHandler.assert_called_with(console_logger)
self.requests_log.setLevel.assert_called_with(logging.ERROR)
self.cliff_log.setLevel.assert_called_with(logging.ERROR)
self.stevedore_log.setLevel.assert_called_with(logging.ERROR)
self.iso8601_log.setLevel.assert_called_with(logging.ERROR)
self.assertEqual(False, configurator.dump_trace)
@mock.patch('logging.getLogger')
@mock.patch('openstackclient.common.context.set_warning_filter')
def test_init_no_debug(self, warning_filter, getLogger):
getLogger.side_effect = self.loggers
self.options.debug = True
configurator = context.LogConfigurator(self.options)
warning_filter.assert_called_with(logging.DEBUG)
self.requests_log.setLevel.assert_called_with(logging.DEBUG)
self.assertEqual(True, configurator.dump_trace)
@mock.patch('logging.FileHandler')
@mock.patch('logging.getLogger')
@mock.patch('openstackclient.common.context.set_warning_filter')
@mock.patch('openstackclient.common.context._FileFormatter')
def test_init_log_file(self, formatter, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
self.options.log_file = '/tmp/log_file'
file_logger = mock.Mock()
file_logger.setFormatter = mock.Mock()
file_logger.setLevel = mock.Mock()
handle.return_value = file_logger
mock_formatter = mock.Mock()
formatter.return_value = mock_formatter
context.LogConfigurator(self.options)
handle.assert_called_with(filename=self.options.log_file)
self.root_logger.addHandler.assert_called_with(file_logger)
file_logger.setFormatter.assert_called_with(mock_formatter)
file_logger.setLevel.assert_called_with(logging.WARNING)
@mock.patch('logging.FileHandler')
@mock.patch('logging.getLogger')
@mock.patch('openstackclient.common.context.set_warning_filter')
@mock.patch('openstackclient.common.context._FileFormatter')
def test_configure(self, formatter, warning_filter, getLogger, handle):
getLogger.side_effect = self.loggers
configurator = context.LogConfigurator(self.options)
cloud_config = mock.Mock()
config_log = '/tmp/config_log'
cloud_config.config = {
'log_file': config_log,
'verbose_level': 1,
'log_level': 'info'}
file_logger = mock.Mock()
file_logger.setFormatter = mock.Mock()
file_logger.setLevel = mock.Mock()
handle.return_value = file_logger
mock_formatter = mock.Mock()
formatter.return_value = mock_formatter
configurator.configure(cloud_config)
warning_filter.assert_called_with(logging.INFO)
handle.assert_called_with(filename=config_log)
self.root_logger.addHandler.assert_called_with(file_logger)
file_logger.setFormatter.assert_called_with(mock_formatter)
file_logger.setLevel.assert_called_with(logging.INFO)
self.assertEqual(False, configurator.dump_trace)

View File

@ -623,12 +623,9 @@ class TestShellCli(TestShell):
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
@mock.patch("openstackclient.common.context._setup_handler_for_logging")
def test_shell_args_cloud_public(self, setup_handler, config_mock,
public_mock):
def test_shell_args_cloud_public(self, config_mock, public_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
public_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
setup_handler.return_value = mock.MagicMock()
_shell = make_shell()
fake_execute(
@ -666,12 +663,9 @@ class TestShellCli(TestShell):
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
@mock.patch("openstackclient.common.context._setup_handler_for_logging")
def test_shell_args_precedence(self, setup_handler, config_mock,
vendor_mock):
def test_shell_args_precedence(self, config_mock, vendor_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
setup_handler.return_value = mock.MagicMock()
_shell = make_shell()
# Test command option overriding config file value
@ -723,12 +717,9 @@ class TestShellCliEnv(TestShell):
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
@mock.patch("openstackclient.common.context._setup_handler_for_logging")
def test_shell_args_precedence_1(self, setup_handler, config_mock,
vendor_mock):
def test_shell_args_precedence_1(self, config_mock, vendor_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
setup_handler.return_value = mock.MagicMock()
_shell = make_shell()
# Test env var
@ -767,12 +758,9 @@ class TestShellCliEnv(TestShell):
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
@mock.patch("openstackclient.common.context._setup_handler_for_logging")
def test_shell_args_precedence_2(self, setup_handler, config_mock,
vendor_mock):
def test_shell_args_precedence_2(self, config_mock, vendor_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
setup_handler.return_value = mock.MagicMock()
_shell = make_shell()
# Test command option overriding config file value
@ -810,77 +798,3 @@ class TestShellCliEnv(TestShell):
'krikkit',
_shell.cloud.config['region_name'],
)
class TestShellCliLogging(TestShell):
def setUp(self):
super(TestShellCliLogging, self).setUp()
def tearDown(self):
super(TestShellCliLogging, self).tearDown()
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
@mock.patch("openstackclient.common.context._setup_handler_for_logging")
def test_shell_args_precedence_1(self, setup_handler, config_mock,
vendor_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
setup_handler.return_value = mock.MagicMock()
_shell = make_shell()
# These come from clouds.yaml
fake_execute(
_shell,
"--os-cloud megacloud list user",
)
self.assertEqual(
'megacloud',
_shell.cloud.name,
)
self.assertEqual(
'/tmp/test_log_file',
_shell.cloud.config['log_file'],
)
self.assertEqual(
'debug',
_shell.cloud.config['log_level'],
)
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
def test_shell_args_precedence_2(self, config_mock, vendor_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
_shell = make_shell()
# Test operation_log_file not set
fake_execute(
_shell,
"--os-cloud scc list user",
)
self.assertEqual(
False,
_shell.enable_operation_logging,
)
@mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file")
@mock.patch("os_client_config.config.OpenStackConfig._load_config_file")
@mock.patch("openstackclient.common.context._setup_handler_for_logging")
def test_shell_args_precedence_3(self, setup_handler, config_mock,
vendor_mock):
config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2))
vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1))
setup_handler.return_value = mock.MagicMock()
_shell = make_shell()
# Test enable_operation_logging set
fake_execute(
_shell,
"--os-cloud megacloud list user",
)
self.assertEqual(
True,
_shell.enable_operation_logging,
)