1. Solve the problem of repeated printing if handlers are added multiple times. Make a judgment in the __add_handlers method.
2. The instance of logger type is returned by get_logger_and_add_handlers and get_logger_without_handlers, no longer use the proxy mode to add the debug info warning error critical method in this class, and solve the problem that the lineno of the log fomatter shows the number of lines related to the method of this class, not the specific printing The number of lines of code in the log location.
# coding=utf8 """ @author bfzs """ import os import unittest import logging from logging.handlers import RotatingFileHandler if os.name == 'posix': from cloghandler import ConcurrentRotatingFileHandler format_dict = { 1: logging.Formatter('log time [%(asctime)s] - log name [%(name)s] - file [%(filename)s] - line [%(lineno)d] - log level [ %(levelname)s】 - log information【%(message)s】', "%Y-%m-%d %H:%M:%S"), 2: logging.Formatter('log time [%(asctime)s] - log name [%(name)s] - file [%(filename)s] - line [%(lineno)d] - log level [ %(levelname)s】 - log information【%(message)s】', "%Y-%m-%d %H:%M:%S"), 3: logging.Formatter('log time [%(asctime)s] - log name [%(name)s] - file [%(filename)s] - line [%(lineno)d] - log level [ %(levelname)s】 - log information【%(message)s】', "%Y-%m-%d %H:%M:%S"), 4: logging.Formatter('log time [%(asctime)s] - log name [%(name)s] - file [%(filename)s] - line [%(lineno)d] - log level [ %(levelname)s】 - log information【%(message)s】', "%Y-%m-%d %H:%M:%S"), 5: logging.Formatter('log time [%(asctime)s] - log name [%(name)s] - file [%(filename)s] - line [%(lineno)d] - log level [ %(levelname)s】 - log information【%(message)s】', "%Y-%m-%d %H:%M:%S"), } class LogLevelException(Exception): def __init__(self, log_level): err = 'The set log level is {0}, the setting is wrong, please set it to a number in the range of 1 2 3 4 5'.format(log_level) Exception.__init__(self, err) class LogManager(object): """ A log class for creating and capturing logs, and supports printing logs to the console and writing to log files. """ def __init__(self, logger_name=None): """ :param logger_name: log name, when it is None, print all logs """ self. logger = logging. getLogger(logger_name) self._logger_level = None self._is_add_stream_handler = None self._log_path = None self._log_filename = None self._log_file_size = None self._formatter = None def get_logger_and_add_handlers(self, log_level_int=1, is_add_stream_handler=True, log_path=None, log_filename=None, log_file_size=10): """ :param log_level_int: Log output level, set to 1 2 3 4 5, corresponding to output DEBUG, INFO, WARNING, ERROR, CRITICAL logs :param is_add_stream_handler: Whether to print the log to the console :param log_path: Set the folder path for storing logs :param log_filename: The name of the log, only when log_path and log_filename are not None, it will be written to the log file. :param log_file_size : log size, unit M, default 10M :type logger_name :str :type log_level_int :int :type is_add_stream_handler :bool :type log_path :str :type log_filename :str :type log_file_size :int """ self.__check_log_level(log_level_int) self._logger_level = self.__transform_logger_level(log_level_int) self._is_add_stream_handler = is_add_stream_handler self._log_path = log_path self._log_filename = log_filename self._log_file_size = log_file_size self._formatter = format_dict[log_level_int] self.__set_logger_level() self.__add_handlers() return self. logger def get_logger_without_handlers(self): """Returns a logger without hanlers""" return self. logger def __set_logger_level(self): self.logger.setLevel(self._logger_level) @staticmethod def __check_log_level(log_level_int): if log_level_int not in [1, 2, 3, 4, 5]: raise LogLevelException(log_level_int) @staticmethod def __transform_logger_level(log_level_int): logger_level = None if log_level_int == 1: logger_level = logging. DEBUG elif log_level_int == 2: logger_level = logging. INFO elif log_level_int == 3: logger_level = logging. WARNING elif log_level_int == 4: logger_level = logging.ERROR elif log_level_int == 5: logger_level = logging. CRITICAL return logger_level def __add_handlers(self): if self._is_add_stream_handler: for h in self. logger. handlers: if isinstance(h, logging. StreamHandler): break else: self.__add_stream_handler() if all([self._log_path, self._log_filename]): for h in self. logger. handlers: if os.name == 'nt': if isinstance(h, RotatingFileHandler): break if os.name == 'posix': if isinstance(h, (RotatingFileHandler, ConcurrentRotatingFileHandler)): break else: self.__add_file_handler() def __add_stream_handler(self): """ Logs are displayed to the console """ stream_handler = logging. StreamHandler() stream_handler.setLevel(self._logger_level) stream_handler.setFormatter(self._formatter) self. logger. addHandler(stream_handler) def __add_file_handler(self): """ Logs are written to the log file """ if not os.path.exists(self._log_path): os.makedirs(self._log_path) log_file = os.path.join(self._log_path, self._log_filename) os_name = os.name rotate_file_handler = None if os_name == 'nt': # Use this under windows, non-process safe rotate_file_handler = RotatingFileHandler(log_file, mode="a", maxBytes=self._log_file_size * 1024 * 1024, backupCount=10, encoding="utf-8") if os_name == 'posix': # ConcurrentRotatingFileHandler can be used under linux, a process-safe log method rotate_file_handler = ConcurrentRotatingFileHandler(log_file, mode="a", maxBytes=self._log_file_size * 1024 * 1024, backupCount=10, encoding="utf-8") rotate_file_handler.setLevel(self._logger_level) rotate_file_handler.setFormatter(self._formatter) self. logger. addHandler(rotate_file_handler) import time class Test(unittest. TestCase): # @unittest.skip def test_repeat_add_handlers_(self): """Test repeated adding handlers""" test_log = LogManager('test').get_logger_and_add_handlers(1, log_path='../logs', log_filename='test.log') test_log = LogManager('test').get_logger_and_add_handlers(1, log_path='../logs', log_filename='test.log') test_log = LogManager('test').get_logger_and_add_handlers(1, log_path='../logs', log_filename='test.log') test_log = LogManager('test').get_logger_and_add_handlers(1, log_path='../logs', log_filename='test.log') print('The following sentence will not be printed four times and written to the log four times') time. sleep(1) test_log.debug('This sentence will not be printed four times and written to the log four times') def test_get_logger_without_hanlders(self): """Test logs without handlers""" log = LogManager('test2').get_logger_without_handlers() print('The following sentence will not be printed') time. sleep(1) log.info('This sentence will not be printed') def test_add_handlers(self): """In this way, you can write debug and info level logs at any specific place, and you only need to specify the level at the main gate to filter, which is very convenient""" log0 = LogManager('test3').get_logger_and_add_handlers(2) log1 = LogManager('test3').get_logger_without_handlers() print('The following sentence is info level and can be printed out') time. sleep(1) log1.info('This sentence is info level, it can be printed out') print('The following sentence is debug level and cannot be printed') time. sleep(1) log1.debug('This sentence is debug level and cannot be printed out') def test_only_write_log_to_file(self): """Only write to log file""" log5 = LogManager('test5').get_logger_and_add_handlers(is_add_stream_handler=False, log_path='../logs', log_filename='test5.log') print('The following sentence is only written to the file') log5.debug('This sentence is only written to the file') if __name__ == "__main__": unittest. main()
3. Add a judgment in __add_handlers. If the log already has handlers of this type, it will not be added.
No matter how many times the log handler is added, it will not be printed repeatedly. This is because the log instance is used as a global variable in the module, and the module is repeatedly imported in multiple places in the project, resulting in repeated printing of the log, which can be solved very well.