Source code for khoros.utils.log_utils

# -*- coding: utf-8 -*-
"""
:Module:            khoros.utils.log_utils
:Synopsis:          Collection of logging utilities and functions
:Usage:             ``from khoros.utils import log_utils``
:Example:           ``logger = log_utils.initialize_logging(__name__)``
:Created By:        Jeff Shurtliff
:Last Modified:     Jeff Shurtliff
:Modified Date:     23 May 2022
"""

import os
import sys
import logging
import logging.handlers
from pathlib import Path

LOGGING_DEFAULTS = {
    'logger_name': __name__,
    'log_level': 'info',
    'formatter': logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s'),
    'date_format': '%Y-%m-%d %I:%M:%S',
}
HANDLER_DEFAULTS = {
    'file_log_level': 'info',
    'console_log_level': 'warning',
    'syslog_log_level': 'info',
    'syslog_address': 'localhost',
    'syslog_port': 514,
}


[docs] def initialize_logging(logger_name=None, log_level=None, formatter=None, debug=None, no_output=None, file_output=None, file_log_level=None, log_file=None, overwrite_log_files=None, console_output=None, console_log_level=None, syslog_output=None, syslog_log_level=None, syslog_address=None, syslog_port=None): """This function initializes logging for the khoros library.""" # TODO: Complete the docstring above with parameters logger_name, log_levels, formatter = _apply_defaults(logger_name, formatter, debug, log_level, file_log_level, console_log_level, syslog_log_level) log_level, file_log_level, console_log_level, syslog_log_level = _get_log_levels_from_dict(log_levels) logger = logging.getLogger(logger_name) logger = _set_logging_level(logger, log_level) logger = _add_handlers(logger, formatter, no_output, file_output, file_log_level, log_file, overwrite_log_files, console_output, console_log_level, syslog_output, syslog_log_level, syslog_address, syslog_port) return logger
[docs] class LessThanFilter(logging.Filter): """This class allows filters to be set to limit log levels to only less than a specified level. .. versionadded:: 3.0.0 .. seealso:: `Zoey Greer <https://stackoverflow.com/users/5124424/zoey-greer>`_ is the original author of this class which was provided on `Stack Overflow <https://stackoverflow.com/a/31459386>`_. """ def __init__(self, exclusive_maximum, name=""): """This method instantiates the :py:class:`khoros.utils.log_utils.LessThanFilter` class object. .. versionadded:: 3.0.0 """ super(LessThanFilter, self).__init__(name) self.max_level = exclusive_maximum
[docs] def filter(self, record): """This method returns a Boolean integer value indicating whether or not a message should be logged. .. versionadded:: 3.0.0 .. note:: A non-zero return indicates that the message will be logged. """ return 1 if record.levelno < self.max_level else 0
def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _console_level, _syslog_level): """This function applies default values to the configuration settings if not explicitly defined. .. versionadded:: 3.0.0 :param _logger_name: The name of the logger instance :type _logger_name: str, None :param _formatter: The log format to utilize for the logger instance :type _formatter: str, None :param _debug: Defines if debug mode is enabled :type _debug: bool, None :param _log_level: The general logging level for the logger instance :type _log_level: str, None :returns: The values that will be used for the configuration settings """ _log_levels = { 'general': _log_level, 'file': _file_level, 'console': _console_level, 'syslog': _syslog_level, } _logger_name = LOGGING_DEFAULTS.get('logger_name') if not _logger_name else _logger_name if _debug: for _log_type in _log_levels: _log_levels[_log_type] = 'debug' else: if _log_level: for _lvl_type, _lvl_value in _log_levels.items(): if _lvl_type != 'general' and _lvl_value is None: _log_levels[_lvl_type] = _log_level else: _log_level = LOGGING_DEFAULTS.get('log_level') if _formatter and isinstance(_formatter, str): _formatter = logging.Formatter(_formatter) _formatter = LOGGING_DEFAULTS.get('formatter') if not _formatter else _formatter return _logger_name, _log_levels, _formatter def _get_log_levels_from_dict(_log_levels): """This function returns the individual log level values from a dictionary. .. versionadded:: 3.0.0 :param _log_levels: Dictionary containing log levels for different handlers :type _log_levels: dict :returns: Individual string values for each handler """ _general = _log_levels.get('general') _file = _log_levels.get('file') _console = _log_levels.get('console') _syslog = _log_levels.get('syslog') return _general, _file, _console, _syslog def _set_logging_level(_logger, _log_level): """This function sets the logging level for a :py:class:`logging.Logger` instance. .. versionadded:: 3.0.0 :param _logger: The :py:class:`logging.Logger` instance :type _logger: Logger :param _log_level: The log level as a string (``debug``, ``info``, ``warning``, ``error`` or ``critical``) :type _log_level: str :returns: The :py:class:`logging.Logger` instance with a logging level set where applicable """ if _log_level == 'debug': _logger.setLevel(logging.DEBUG) elif _log_level == 'info': _logger.setLevel(logging.INFO) elif _log_level == 'warning': _logger.setLevel(logging.WARNING) elif _log_level == 'error': _logger.setLevel(logging.ERROR) elif _log_level == 'critical': _logger.setLevel(logging.CRITICAL) return _logger def _add_handlers(_logger, _formatter, _no_output, _file_output, _file_log_level, _log_file, _overwrite_log_files, _console_output, _console_log_level, _syslog_output, _syslog_log_level, _syslog_address, _syslog_port): # TODO: Add docstring if _no_output or not any((_file_output, _console_output, _syslog_output)): _logger.addHandler(logging.NullHandler()) else: if _file_output: # Add the FileHandler to the Logger object _logger = _add_file_handler(_logger, _file_log_level, _log_file, _overwrite_log_files, _formatter) if _console_output: # Add the StreamHandler to the Logger object _logger = _add_stream_handler(_logger, _console_log_level, _formatter) if _syslog_output: # Add the SyslogHandler to the Logger object _logger = _add_syslog_handler(_logger, _syslog_log_level, _formatter, _syslog_address, _syslog_port) return _logger def _add_file_handler(_logger, _log_level, _log_file, _overwrite, _formatter): """This function adds a :py:class:`logging.FileHandler` to the :py:class:`logging.Logger` instance. .. versionadded:: 3.0.0 :param _logger: The :py:class:`logging.Logger` instance :type _logger: Logger :param _log_level: The log level to set for the handler :type _log_level: str :param _log_file: The log file (as a file name or a file path) to which messages should be written .. note:: If a file path isn't provided then the default directory is the home directory of the user instantiating the :py:class:`logging.Logger` object. If a file name is also no provided then it will default to using ``khoros.log`` as the file name. :param _overwrite: Determines if messages should be appended to the file (default) or overwrite it :type _overwrite: bool :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handler :type _formatter: Formatter :returns: The :py:class:`logging.Logger` instance with the added :py:class:`logging.FileHandler` """ # Define the log file to use _home_dir = str(Path.home()) if _log_file: if not any((('/' in _log_file), ('\\' in _log_file))): _log_file = os.path.join(_home_dir, _log_file) else: _log_file = os.path.join(_home_dir, 'khoros.log') # Identify if log file should be overwritten _write_mode = 'w' if _overwrite else 'a' # Instantiate the handler _handler = logging.FileHandler(_log_file, _write_mode) _log_level = HANDLER_DEFAULTS.get('file_log_level') if not _log_level else _log_level _handler = _set_logging_level(_handler, _log_level) _handler.setFormatter(_formatter) # Add the handler to the logger _logger.addHandler(_handler) return _logger def _add_stream_handler(_logger, _log_level, _formatter): """This function adds a :py:class:`logging.StreamHandler` to the :py:class:`logging.Logger` instance. .. versionadded:: 3.0.0 :param _logger: The :py:class:`logging.Logger` instance :type _logger: Logger :param _log_level: The log level to set for the handler :type _log_level: str :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handler :type _formatter: Formatter :returns: The :py:class:`logging.Logger` instance with the added :py:class:`logging.StreamHandler` """ _log_level = HANDLER_DEFAULTS.get('console_log_level') if not _log_level else _log_level _stdout_levels = ['DEBUG', 'INFO'] if _log_level.upper() in _stdout_levels: _logger = _add_split_stream_handlers(_logger, _log_level, _formatter) else: _handler = logging.StreamHandler() _handler = _set_logging_level(_handler, _log_level) _handler.setFormatter(_formatter) _logger.addHandler(_handler) return _logger def _add_split_stream_handlers(_logger, _log_level, _formatter): """This function splits messages into q ``stdout`` or ``stderr`` handler depending on the log level. .. versionadded:: 3.0.0 .. seealso:: Refer to the documentation for the :py:class:`khoros.utils.log_utils.LessThanFilter` for more information on how this filtering is implemented and for credit to the original author. :param _logger: The :py:class:`logging.Logger` instance :type _logger: Logger :param _log_level: The log level provided for the stream handler (i.e. console output) :type _log_level: str :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handlers :type _formatter: Formatter :returns: The logger instance with the two handlers added """ # Configure and add the STDOUT handler _stdout_handler = logging.StreamHandler(sys.stdout) _stdout_handler = _set_logging_level(_stdout_handler, _log_level) _stdout_handler.addFilter(LessThanFilter(logging.WARNING)) _stdout_handler.setFormatter(_formatter) _logger.addHandler(_stdout_handler) # Configure and add the STDERR handler _stderr_handler = logging.StreamHandler(sys.stderr) _stderr_handler.setLevel(logging.WARNING) _stderr_handler.setFormatter(_formatter) _logger.addHandler(_stderr_handler) # Return the logger with the added handlers return _logger def _add_syslog_handler(_logger, _log_level, _formatter, _address, _port): # TODO: Add docstring _log_level = HANDLER_DEFAULTS.get('syslog_log_level') if not _log_level else _log_level _address = HANDLER_DEFAULTS.get('syslog_address') if not _address else _address _port = HANDLER_DEFAULTS.get('syslog_port') if not _port else _port _handler = logging.handlers.SysLogHandler(address=(_address, _port)) _handler = _set_logging_level(_handler, _log_level) _handler.setFormatter(_formatter) _logger.addHandler(_handler) return _logger