diff --git a/src/sonic-py-common/sonic_py_common/logger.py b/src/sonic-py-common/sonic_py_common/logger.py index fa86221d6d75..5a8aeaa173d7 100644 --- a/src/sonic-py-common/sonic_py_common/logger.py +++ b/src/sonic-py-common/sonic_py_common/logger.py @@ -26,20 +26,49 @@ class Logger(object): DEFAULT_LOG_FACILITY = LOG_FACILITY_USER DEFAULT_LOG_OPTION = LOG_OPTION_NDELAY - def __init__(self, log_identifier=None, log_facility=DEFAULT_LOG_FACILITY, log_option=DEFAULT_LOG_OPTION): - self._syslog = syslog - + def __init__(self, log_identifier=None, log_facility=DEFAULT_LOG_FACILITY, log_option=DEFAULT_LOG_OPTION, + enable_set_log_level_on_fly=False, db_name=None): if log_identifier is None: log_identifier = os.path.basename(sys.argv[0]) + if db_name is None: + self.db_name = log_identifier + else: + self.db_name = db_name + self.enable_set_log_level_on_fly = enable_set_log_level_on_fly # Initialize syslog - self._syslog.openlog(ident=log_identifier, logoption=log_option, facility=log_facility) + syslog.openlog(ident=log_identifier, logoption=log_option, facility=log_facility) - # Set the default minimum log priority to LOG_PRIORITY_NOTICE - self.set_min_log_priority(self.LOG_PRIORITY_NOTICE) + self._logger = None def __del__(self): - self._syslog.closelog() + syslog.closelog() + + @property + def logger(self): + if self._logger is None: + try: + from swsscommon.swsscommon import Logger as SwssLogger + except ImportError: + # Workaround for unit test. In some SONiC Python package, it mocked + # swsscommon lib for unit test purpose, but it does not contain Logger + # class. To make those unit test happy, here provides a MagicMock object. + if sys.version_info.major == 3: + from unittest import mock + else: + # Expect the 'mock' package for python 2 + # https://pypi.python.org/pypi/mock + import mock + SwssLogger = mock.MagicMock() + instance = mock.MagicMock() + SwssLogger.getInstance.return_value = instance + instance.getMinPrio.return_value = syslog.LOG_NOTICE + self._logger = SwssLogger.getInstance() + if self.enable_set_log_level_on_fly: + # Performance warning: linkToDbNative will potentially create a new thread. + # The thread listens to CONFIG DB for log level changes. + self._logger.linkToDbNative(self.db_name, 'NOTICE') + return self._logger # # Methods for setting minimum log priority @@ -53,7 +82,7 @@ def set_min_log_priority(self, priority): Args: priority: The minimum priority at which to log messages """ - self._min_log_priority = priority + self.logger.setMinPrio(priority) def set_min_log_priority_error(self): """ @@ -90,9 +119,9 @@ def set_min_log_priority_debug(self): # def log(self, priority, msg, also_print_to_console=False): - if self._min_log_priority >= priority: + if self.logger.getMinPrio() >= priority: # Send message to syslog - self._syslog.syslog(priority, msg) + self.logger.write(priority, msg) # Send message to console if also_print_to_console: diff --git a/src/sonic-py-common/tests/logger_test.py b/src/sonic-py-common/tests/logger_test.py new file mode 100644 index 000000000000..5003844e6626 --- /dev/null +++ b/src/sonic-py-common/tests/logger_test.py @@ -0,0 +1,53 @@ +import sys + +if sys.version_info.major == 3: + from unittest import mock +else: + # Expect the 'mock' package for python 2 + # https://pypi.python.org/pypi/mock + import mock + +from sonic_py_common import logger + + +class TestLogger: + def test_log(self): + log = logger.Logger() + mock_log = mock.MagicMock() + mock_log.write = mock.MagicMock() + mock_log.getMinPrio.return_value = logger.Logger.LOG_PRIORITY_DEBUG + + log._logger = mock_log + log.set_min_log_priority(logger.Logger.LOG_PRIORITY_DEBUG) + + log.log_debug('debug message') + mock_log.write.assert_called_with(logger.Logger.LOG_PRIORITY_DEBUG, 'debug message') + + log.log_info('info message') + mock_log.write.assert_called_with(logger.Logger.LOG_PRIORITY_INFO, 'info message') + + log.log_notice('notice message') + mock_log.write.assert_called_with(logger.Logger.LOG_PRIORITY_NOTICE, 'notice message') + + log.log_error('error message') + mock_log.write.assert_called_with(logger.Logger.LOG_PRIORITY_ERROR, 'error message') + + log.log_warning('warning message') + mock_log.write.assert_called_with(logger.Logger.LOG_PRIORITY_WARNING, 'warning message') + + mock_log.getMinPrio.return_value = logger.Logger.LOG_PRIORITY_INFO + mock_log.write.reset_mock() + log.log_debug('debug message') + mock_log.write.assert_not_called() + + log.log_info('info message', also_print_to_console=True) + log.log_info('info message') + mock_log.write.assert_called_with(logger.Logger.LOG_PRIORITY_INFO, 'info message') + + def test_set_log_level(self): + log = logger.Logger() + log.set_min_log_priority_error() + log.set_min_log_priority_warning() + log.set_min_log_priority_notice() + log.set_min_log_priority_info() + log.set_min_log_priority_debug()