Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python logger enhancement to support setting log level at real time #17321

Closed
49 changes: 39 additions & 10 deletions src/sonic-py-common/sonic_py_common/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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:
Expand Down
53 changes: 53 additions & 0 deletions src/sonic-py-common/tests/logger_test.py
Original file line number Diff line number Diff line change
@@ -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()