• Python 日志模块的定制


    Python标准logging模块中主要分为四大块内容:

    Logger: 定义应用程序使用的接口

    Handler: 将Loggers产生的日志输出到目的地

    Filter: 对Loggers产生的日志进行过滤

    Formatter: 格式化Loggers产生的日志

    其中常用的是Logger, Handler, Formatter。

    目标

    对logging模块进行封装,使其尽量达到几个目标:

    • 易用性:尽量保持python原生标准的用法。
    • 扩展性:避免将所有模块集中在一个文件中进行配置,而是对不同模块继承扩展。如此可以确保修改时尽量保持简洁性,而非混淆杂乱。
    • 文字加色:同一时间段内出现大量日志输出时,可以更清晰地识别出问题所在。
    • 输出多个目的地:在生产代码中,并非所有日志信息都输出到console或者file中,而是日志的重要级别,将那些比较重要的发送到console,将那些不太重要但以后分析问题时可能又会用到的发送到file中。
    • 非阻塞发送器:记录日志时如果涉及到网络(如socket, smtp),那么这个记录过程可能会阻塞当前线程,在python 3.2+,我们可以非常容易地设计一个非阻塞的发送器(当然了,非阻塞机制的内部实现大多都是队列)。

    目录结构

    如下的定制日志主要包括两个包,log_config和tools,目录结构如下:

    log_config 模块结构

    默认配置

    • Logging Level:

      logging.NOTSET is default.

      You can change it by passing ‘level=’ to BTLogger instance in your application code.

    • Sending Level:

      Send logging messages to console with levels of logging.INFO and higher, and simultaneously to the disk file with levels of logging.DEBUG and higher.

      You can change them by passing ‘level=’ to BTStreamHandler/BTFileHandler instance in log_config\__init__.py.

    • Log Config:

      LOG_PATH = r'C:log'
      LOG_NAME = r'mylogger.log'

      You can change them in log_config\__init__.py.

    • Console Text Color:

      DEBUG

      INFO

      WARNING

      ERROR

      CRITICAL

      You can change them by passing ‘fore_color=’ and ‘back_color=’ to decorator @text_attribute.set_textcolor(fore_color, back_color) in log_config\__init__.py.

    • There also exists a NonBlockingHandler class in log_confighandlers.py, which will use features of version 3.2+. If you unfold it, you can get a non_blocking_handler.

    Demo

    ts_log_config.py:

     1 def log_test():
     2     import log_config
     3 
     4     logger = log_config.BTLogger('mylogger')
     5 
     6     logger.debug('Debug message')
     7     logger.info('Info message')
     8     logger.warning('Warning message')
     9     logger.error('Error message')
    10     logger.critical('Critical message')
    11 
    12 def main():
    13     log_test()
    14 
    15 if __name__ == '__main__':
    16     main()

    outputs:

    代码实现

    ..log_config\__init__.py:

      1 """
      2 Wrap the classes of logging module so as to expose more flexible interfaces that application code directly uses.
      3 
      4 By default, when you do some log operations, it will log messages to console with levels of logging.INFO and higher,
      5 and simultaneously to the disk file with levels of logging.DEBUG and higher.
      6 
      7 Usages:
      8 To use log_config module, simply 'import log_config' in your code, and create a BTLogger instance, then, you can use this
      9 instance to log messages:
     10     import log_config
     11 
     12     logger = log_config.BTLogger('mylogger')
     13 
     14     logger.debug('Debug message')
     15     logger.info('Info message')
     16     logger.warning('Warning message')
     17     logger.error('Error message')
     18     logger.critical('Critical message')
     19 """
     20 
     21 import os
     22 import sys
     23 import logging
     24 from log_config import formatters
     25 from log_config import handlers
     26 from log_config import text_attribute
     27 from tools import os_tools
     28 
     29 __author__ = " "
     30 __status__ = "debugging"  # {debugging, production}
     31 __version__ = "0.1.0"  # major_version_number.minor_version_number.revision_number
     32 __date__ = " "
     33 
     34 """
     35 # Config root logger
     36 basicConfig(
     37     filename=__name__,
     38     filemode='w',
     39     format='%(asctime)s: %(levelname)s: %(message)s',
     40     datefmt='%Y-%m-%d %H:%M:%S',
     41     level='logging.NOTSET'
     42 )
     43 """
     44 
     45 # Config log path and file name.
     46 LOG_PATH = r'C:log'
     47 LOG_NAME = r'mylogger.log'
     48 filename = 'default.log' if not os_tools.makedir(LOG_PATH) else os.path.abspath(os.path.join(LOG_PATH, LOG_NAME))
     49 
     50 
     51 class BTLogger(logging.Logger):
     52     def __init__(self, name=None, level=logging.NOTSET):
     53         """
     54         Initialize the BTLogger with basic settings.
     55 
     56         :param
     57             name: Specify the logger's name. If name is None, default is root logger.
     58                    Note:
     59                         All BTLogger instances with a given name return the same logger
     60                         instance. This means that logger instances never need to be passed
     61                         between different parts of an application.
     62             level: The threshold of logging records, messages which are less severe
     63                    than level will be ignored.
     64                    If the level is set to NOTSET, than:
     65                         1> If current logger is root logger, than all messages will
     66                         be logged.
     67                         2> Or, delegate to its parent logger, until an ancestor with
     68                         a level other than NOTSET is found, or the root is reached.
     69                    Note:
     70                         The root logger is created with level logging.WARNING.
     71         :return:
     72             None
     73         """
     74         logging.Logger.__init__(self, name, level)
     75 
     76         # Create handlers
     77         # non_blocking_ch = handlers.NonBlockingHandler(handlers.BTStreamHandler(sys.stdout, logging.DEBUG)).get_handler()  # version 3.2+
     78         console_handler = handlers.BTStreamHandler(stream=sys.stdout, level=logging.INFO)
     79         file_handler = handlers.BTFileHandler(filename=filename, level=logging.DEBUG, mode='w')
     80 
     81         # Set formatters to handlers.
     82         # non_blocking_ch.setFormatter(formatters.BTStreamFormatter())  # version 3.2+
     83         console_handler.setFormatter(formatters.BTStreamFormatter())
     84         file_handler.setFormatter(formatters.BTFileFormatter())
     85 
     86         # Add the handlers to logger.
     87         # self.addHandler(non_blocking_ch)  # version 3.2+
     88         self.addHandler(console_handler)
     89         self.addHandler(file_handler)
     90 
     91     # Override methods in logging.Logger(debug, info, warning, error, critical).
     92     # @handlers.NonBlockingHandler.non_blocking     # version 3.2+
     93     @text_attribute.set_textcolor('FOREGROUND_BLACK', 'BACKGROUND_WHITE')
     94     def debug(self, msg, *args, **kwargs):
     95         self.log(logging.DEBUG, msg, *args, **kwargs)
     96 
     97     @text_attribute.set_textcolor('FOREGROUND_BLACK', 'BACKGROUND_GREEN')
     98     def info(self, msg, *args, **kwargs):
     99         self.log(logging.INFO, msg, *args, **kwargs)
    100 
    101     @text_attribute.set_textcolor('FOREGROUND_BLACK', 'BACKGROUND_YELLOW')
    102     def warning(self, msg, *args, **kwargs):
    103         self.log(logging.WARNING, msg, *args, **kwargs)
    104 
    105     warn = warning
    106 
    107     @text_attribute.set_textcolor('FOREGROUND_BLACK', 'BACKGROUND_RED')
    108     def error(self, msg, *args, **kwargs):
    109         self.log(logging.ERROR, msg, *args, **kwargs)
    110 
    111     @text_attribute.set_textcolor('FOREGROUND_BLACK', 'BACKGROUND_RED')
    112     def critical(self, msg, *args, **kwargs):
    113         self.log(logging.CRITICAL, msg, *args, **kwargs)
    114 
    115     fatal = critical
    View Code

     ..log_configformatters.py:

     1 """
     2 Formatters specify the output format of logging messages.
     3 """
     4 
     5 from logging import Formatter
     6 
     7 
     8 # Changed in version 3.2+: The 'style' parameter was added.
     9 '''
    10 class BTStreamFormatter(Formatter):
    11     def __init__(self,
    12                  fmt='[%(asctime)s] [%(levelname)s]: %(message)s',
    13                  datefmt='%Y-%m-%d %H:%M:%S', style='%'):
    14         Formatter.__init__(self, fmt, datefmt, style)
    15 
    16 
    17 class BTFileFormatter(Formatter):
    18     def __init__(self,
    19                  fmt='%(asctime)s: %(levelname)s: %(message)s',
    20                  datefmt='%Y/%m/%d %H:%M:%S', style='%'):
    21         Formatter.__init__(self, fmt, datefmt, style)
    22 '''
    23 
    24 # Be suitable for version 3.2-.
    25 class BTStreamFormatter(Formatter):
    26     def __init__(self,
    27                  fmt='[%(asctime)s] [%(levelname)s]: %(message)s',
    28                  datefmt='%Y-%m-%d %H:%M:%S'):
    29         Formatter.__init__(self, fmt, datefmt)
    30 
    31 
    32 class BTFileFormatter(Formatter):
    33     def __init__(self,
    34                  fmt='%(asctime)s: %(levelname)s: %(message)s',
    35                  datefmt='%Y/%m/%d %H:%M:%S'):
    36         Formatter.__init__(self, fmt, datefmt)
    View Code

     ..log_confighandlers.py:

     1 """
     2 Handlers send logging messages(created by BTLogger) to the specific destination.
     3 """
     4 
     5 import sys
     6 import logging
     7 from logging import StreamHandler
     8 from logging import FileHandler
     9 
    10 
    11 class BTStreamHandler(StreamHandler):
    12     """
    13     Write logging records to a stream(e.g., console).
    14 
    15     Note that this class does not close the stream automatically,
    16     as sys.stdout or sys.stderr may be used.
    17     """
    18     def __init__(self, stream=sys.stderr, level=logging.INFO):
    19         """
    20         Initialize the handler.
    21 
    22         :param
    23             stream: If stream is not specified, sys.stderr is used.
    24         :return
    25             None
    26         """
    27         StreamHandler.__init__(self, stream)
    28         self.setLevel(level)    # The threshold of handling records, default is logging.INFO.
    29 
    30 
    31 class BTFileHandler(FileHandler):
    32     """
    33     Write logging records to disk files.
    34 
    35     Inherited from logging.FileHandler, only modify the default mode('a') to ('w').
    36     """
    37     def __init__(self,
    38                  filename='default.log',
    39                  level=logging.DEBUG,   # Handle messages with levels of logging.DEBUG and higher to file.
    40                  mode='w',
    41                  encoding=None,
    42                  delay=False):
    43         FileHandler.__init__(self, filename, mode, encoding, delay)
    44         self.setLevel(level)
    45 
    46 
    47 # New in version 3.2+: QueueHandler, QueueListener.
    48 # Unfold the following block, and you will get a non_blocking_handler.
    49 '''
    50 from queue import Queue
    51 from logging.handlers import QueueHandler
    52 from logging.handlers import QueueListener
    53 from functools import wraps
    54 
    55 class NonBlockingHandler(object):
    56     """
    57     Let logging handlers do their work without blocking the thread you're logging from.
    58     Especially those network-based handler can block, e.g., SocketHandler, SMTPHandler.
    59     """
    60     def __init__(self, handler_instance):
    61         self.queue = Queue(-1)
    62         self.queue_handler = QueueHandler(self.queue)
    63         self.handler_instance = handler_instance
    64         global _queue_listener
    65         _queue_listener = QueueListener(self.queue, self.handler_instance)
    66 
    67     def get_handler(self):
    68         return self.handler_instance
    69 
    70     @classmethod
    71     def non_blocking(cls, func):
    72         @wraps(func)
    73         def wrapper(*args, **kwargs):
    74             _queue_listener.start()
    75             res = func(*args, **kwargs)
    76             _queue_listener.stop()
    77             return res
    78 
    79         return wrapper
    80 '''
    View Code

     ..log_config ext_attribute.py:

     1 """
     2 Set the text attributes in console. Currently, you can only set font color.
     3 
     4 # -----------------------------------------
     5 # Author: 
     6 # Created: 
     7 # Modified: 
     8 # Version: 0.1.0
     9 # -----------------------------------------
    10 
    11 Usages:
    12 When you want to design a function which outputs messages to console with different colors, simply use '@text_attribute.set_textcolor(fore_color, back_color)' to decorate your function, e.g.:
    13     @text_attribute.set_textcolor('FOREGROUND_BLACK', 'BACKGROUND_RED')
    14     def func(*args, **kwargs):
    15         # output some messages to console
    16         # ...
    17 
    18     func(...)
    19 """
    20 
    21 import ctypes
    22 from functools import wraps
    23 
    24 
    25 STD_INPUT_HANDLE = -10
    26 STD_OUTPUT_HANDLE = -11
    27 STD_ERROR_HANDLE = -12
    28 
    29 
    30 _CONSOLE_TEXT_COLOR = {
    31     'FOREGROUND_RED': 0x04,
    32     'FOREGROUND_GREEN': 0x02,
    33     'FOREGROUND_BLUE': 0x01,
    34     # 'FOREGROUND_INTENSITY': 0x08,
    35     'FOREGROUND_WHITE': 0x07,
    36     'FOREGROUND_BLACK': 0x00,
    37     'FOREGROUND_YELLOW': 0x06,
    38 
    39     'BACKGROUND_RED': 0x40,
    40     'BACKGROUND_GREEN': 0x20,
    41     'BACKGROUND_BLUE': 0x10,
    42     # 'BACKGROUND_INTENSITY': 0x80,
    43     'BACKGROUND_WHITE': 0x70,
    44     'BACKGROUND_BLACK': 0x00,
    45     'BACKGROUND_YELLOW': 0x60
    46 }
    47 
    48 
    49 class TextColor(object):
    50     """
    51     Set console text color.
    52     """
    53     std_output_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
    54 
    55     @staticmethod
    56     def text_color(fore_color, back_color):
    57         fore_color = 'FOREGROUND_WHITE' if fore_color not in _CONSOLE_TEXT_COLOR else fore_color
    58         back_color = 'BACKGROUND_BLACK' if back_color not in _CONSOLE_TEXT_COLOR else back_color
    59         return _CONSOLE_TEXT_COLOR[fore_color] | _CONSOLE_TEXT_COLOR[back_color]
    60 
    61     @staticmethod
    62     def set(fore_color='FOREGROUND_WHITE', back_color='BACKGROUND_BLACK', handle=std_output_handle):
    63         is_set = ctypes.windll.kernel32.SetConsoleTextAttribute(handle, TextColor.text_color(fore_color, back_color))
    64         return is_set
    65 
    66     @staticmethod
    67     def unset():
    68         return TextColor.set()
    69 
    70 
    71 def set_textcolor(fore_color='FOREGROUND_WHITE', back_color='BACKGROUND_BLACK'):
    72 
    73     def decorate(func):
    74         """
    75         A decorator which can set the color of console message, include foreground and background.
    76         :param
    77             func: The original function which will be wrapped by a wrapper.
    78         :return:
    79             wrapper
    80         """
    81         @wraps(func)
    82         def wrapper(*args, **kwargs):
    83             TextColor.set(fore_color, back_color)
    84             res = func(*args, **kwargs)
    85             TextColor.unset()
    86             return res
    87 
    88         return wrapper
    89 
    90     return decorate
    View Code

     .. ools\__init__.py: None

    .. oolsos_tools.py:

     1 """
     2 Originated from os module.
     3 
     4 # -----------------------------------------
     5 # Author: 
     6 # Created: 
     7 # Modified:
     8 # Version: 0.1.0
     9 # -----------------------------------------
    10 """
    11 
    12 import os
    13 
    14 
    15 def makedir(path):
    16     """
    17     Recursive directory creation, include all intermediate-level directories needed to contain the leaf directory.
    18 
    19     If specific path already exists, do nothing. Or path is not a directory, return False.
    20     """
    21     try:
    22         os.makedirs(path, 0o777)
    23         # os.makedirs(path, 0o777, False) # Version 3.2+
    24     except OSError:
    25         if not os.path.isdir(path):      # Path is not a valid directory.
    26             return False                 # Path already exists.
    27         return True
    28     else:                                # Path does not exist.
    29         return True
    View Code
  • 相关阅读:
    EasyUI datagrid动态加载json数据
    Java缓存机制
    爬虫入门 手写一个Java爬虫
    java解决前后台跨域问题
    HttpUrlConnection 基础使用
    聊聊spring-boot-starter-data-redis的配置变更
    Linux命令: 结束命令
    Linux其他: GitBash
    Python: 字典dict: 相同点
    Python: 字典dict: zip()
  • 原文地址:https://www.cnblogs.com/benxintuzi/p/5263303.html
Copyright © 2020-2023  润新知