zoukankan      html  css  js  c++  java
  • Python之logging模块

    什么是日志

     

    日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述(比如:每个事件发生时的数据都是不同的)。开发者还会区分事件的重要性,重要性也被称为等级严重性

    什么时候使用日志

     

    日志(logging)模块提供了一系列的函数(debug、info、warning、critical)来适应不同的应用场景。想要决定何时使用日志,请看下表,其中显示了对于每个通用任务集合来说最好的工具。

    想要执行的任务此任务最好的工具
    对于命令行或程序的应用,结果显示在控制台 print()
    在对程序的普通操作发生时提交事件报告(比如:状态监控和错误调查) logging.info() 函数(当有诊断目的需要详细输出信息时使用 logging.debug() 函数)
    提出一个警告信息基于一个特殊的运行时事件 warnings.warn() 位于代码库中,该事件是可以避免的,需要修改客户端应用以消除告警。logging.warning() 不需要修改客户端应用,但是该事件还是需要引起关注
    对一个特殊的运行时事件报告错误 引发异常
    报告错误而不引发异常(如在长时间运行中的服务端进程的错误处理) logging.error()logging.exception() 或 logging.critical() 分别适用于特定的错误及应用领域

    下表展示了logging中,日志功能应以所追踪事件级别或严重性而定。各级别适用性如下(以严重性递减):

    等级数值描述
    CRITICAL 50 严重的错误,表明程序已经不能继续执行
    FATAL(不推荐) 50 FATAL是CRITICAL的别名。
    ERROR 40 由于严重的问题,程序的某些功能已经不能正常执行
    WARNING 30 表明已经或即将发生的意外(例如磁盘空间不足)。但程序仍按照预期运行
    WARN(不推荐) 30 WARN是WANING的简写形式。
    INFO 20 确认程序按预期运行
    DEBUG 10 细节信息,在我们调试程序是使用
    NOTSET 0 不设置

    默认的级别是WARNING,意味着只会追踪该级别及以上的事件,除非更改日志配置。

    快速上手

     

    basicConfig

     

    一个简单的示例,将结果输出到控制台:

    import logging
    logging.warning('info level')  # WARNING:root:info levelb

    将结果输出到文件:

    import logging
    logging.basicConfig(filename='test.log', level=20)
    logging.log(10, '级别为10的一条日志')
    logging.log(20, '级别为20的一条日志')

    上例中,通过basicConfig方法将日志输出到test.log文件,并且设置只有级别大于等于20的才会写入到该日志文件。也就是说,上例中,第一条级别为10的日志将不会写入到文件。并且,需要注意的是,如果你查看日志文件,如果出现乱码的话,请检查编码方式。

    上例中,在basicConfig方法中,级别20也可以这样指定:

    logging.basicConfig(filename='test.log', level=logging.INFO) 
    logging.debug('debug level: 10')
    logging.info('info level: 20')
    logging.warning('warning level: 30')
    logging.error('error level: 40')
    logging.critical('critical level: 50')

    上例中只有级别大于等于20的将会被写入文件。logging.INFO其实代表的就是20,这是Python在源码中帮我们指定了:

    CRITICAL = 50
    FATAL = CRITICAL
    ERROR = 40
    WARNING = 30
    WARN = WARNING
    INFO = 20
    DEBUG = 10
    NOTSET = 0

    接下来,我们来看看basicConfig方法都可以指定哪些参数:

    • filename,即日志输出的文件名,如果指定了这个信息之后,实际上会启用`FileHandler,而不再是StreamHandler,这样日志信息便会输出到文件中了。
    • filemode,日志文件写入方式,可以是wa,默认的是a模式。
    • format,指定日志信息的输出格式,详细参考,这里列出常用的参数:
      • %(levelno)s:打印日志级别的数值。
      • %(levelname)s:打印日志级别的名称。
      • %(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]。
      • %(filename)s:打印当前执行程序名。
      • %(funcName)s:打印日志的当前函数。
      • %(lineno)d:打印日志的当前行号。
      • %(asctime)s:打印日志的时间。
      • %(thread)d:打印线程ID。
      • %(threadName)s:打印线程名称。
      • %(process)d:打印进程ID。
      • %(processName)s:打印线程名称。
      • %(module)s:打印模块名称。
      • %(message)s:打印日志信息。
    • datefmt,指定时间的输出格式。
    • style,如果format指定,该参数可以指定格式化时的占位符。例如'{''$'用于printf风格, str.format()或 string.Template分别。默认为'%'。3.2版本新增参数。
    • level,指定日志输出的类别,程序会输出大于等于此级别的信息。
    • stream,在没有指定`filename的时候会默认使用StreamHandler,这时stream可以指定初始化的文件流。
    • handlers:可以指定日志处理时所使用的 Handlers,必须是可迭代的。

    来个示例:

    import logging
    logging.basicConfig(
        filename='test.log',
        filemode='w',
        level=logging.DEBUG,
        datefmt='%Y/%m/%d %H:%M:%S',
        format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s'
    )
    logging.debug('debug level: 10')
    logging.info('info level: 20')
    logging.warning('warning level: 30')
    logging.error('error level: 40')
    logging.critical('critical level: 50')

    输出结果如下:

    2019/05/30 14:38:09 - root - DEBUG - 36 - 日志模块 - debug level: 10
    2019/05/30 14:38:09 - root - INFO - 37 - 日志模块 - info level: 20
    2019/05/30 14:38:09 - root - WARNING - 38 - 日志模块 - warning level: 30
    2019/05/30 14:38:09 - root - ERROR - 39 - 日志模块 - error level: 40
    2019/05/30 14:38:09 - root - CRITICAL - 40 - 日志模块 - critical level: 50

    需要注意的是,logging.basicConfig只生效一次,比如:

    import logging
    logging.basicConfig(
        filename='test1.log',
        filemode='w',
        level=logging.DEBUG
    )
    
    # 无效
    logging.basicConfig(
        filename='test2.log',
        filemode='a',
        level=logging.INFO
    )
    
    logging.debug('debug level: 10')
    logging.info('info level: 20')
    logging.warning('warning level: 30')
    logging.error('error level: 40')
    logging.critical('critical level: 50')

    正如上例所示,我们配置了两次basicConfig。但如果运行你会发现,只有第一个配置生效了,第二个配置不会生效。原因是当在第一次配置的时候,logging在内部就会进行配置,第二次再次配置的时候,logging就会认为我已经配置好了,不需要再次配置了。

    handler

     

    接下来,我们来看handler的用法:

    import logging
    
    # 日志输出到哪?
    file_handler = logging.FileHandler(filename='test.log', mode='w', encoding='utf-8')
    # 以什么格式写
    fmt = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s')
    # 为日志文件指定写入格式
    file_handler.setFormatter(fmt=fmt)
    # 谁来写?日志的级别是什么?
    logger = logging.Logger(name='logger', level=logging.INFO)  # name:日志对象的名称,可以自定义
    # 往哪写?
    logger.addHandler(file_handler)
    # 写什么?
    logger.debug('debug level: 10')
    logger.info('info level: 20')
    logger.warning('warning level: 30')
    logger.error('error level: 40')
    logger.critical('critical level: 50')

    如上例所示,这里我们一步步的对日志进行配置,然后最后写入到test.log文件。结果如下:

    2019-05-30 15:32:07,118 - logger - INFO - 82 - 日志模块 - info level: 20
    2019-05-30 15:32:07,118 - logger - WARNING - 83 - 日志模块 - warning level: 30
    2019-05-30 15:32:07,118 - logger - ERROR - 84 - 日志模块 - error level: 40
    2019-05-30 15:32:07,119 - logger - CRITICAL - 85 - 日志模块 - critical level: 50

    除此之外,我们还可以使用其他的Handler进行日志输出,logging模块提供的Handler有:

    • StreamHandler:logging.StreamHandler;日志输出到流,可以是 sys.stderr,sys.stdout 或者文件。
    • FileHandler:logging.FileHandler;日志输出到文件。
    • BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日志回滚方式。
    • RotatingHandler:logging.handlers.RotatingHandler;日志回滚方式,支持日志文件最大数量和日志文件回滚。
    • TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日志回滚方式,在一定时间区域内回滚日志文件。
    • SocketHandler:logging.handlers.SocketHandler;远程输出日志到TCP/IP sockets。
    • DatagramHandler:logging.handlers.DatagramHandler;远程输出日志到UDP sockets。
    • SMTPHandler:logging.handlers.SMTPHandler;远程输出日志到邮件地址。
    • SysLogHandler:logging.handlers.SysLogHandler;日志输出到syslog。
    • NTEventLogHandler:logging.handlers.NTEventLogHandler;远程输出日志到Windows NT/2000/XP的事件日志。
    • MemoryHandler:logging.handlers.MemoryHandler;日志输出到内存中的指定buffer。
    • HTTPHandler:logging.handlers.HTTPHandler;通过”GET”或者”POST”远程输出到HTTP服务器。

    再来一个示例,我们使用不同的Handler实现日志同时输出到控制台、文件、HTTP服务器:

    import sys
    import logging
    from logging.handlers import HTTPHandler
    
    # 创建日志对象
    logger = logging.getLogger('logger')
    
    # 将日志输出到文件 FileHandler
    file_handler = logging.FileHandler(filename='test.log', mode='w', encoding='utf-8')
    file_fmt = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s')
    file_handler.setFormatter(file_fmt)
    file_handler.setLevel(level=logging.INFO)
    logger.addHandler(file_handler)
    
    # 将日志输出到控制台 StreamHandler
    stream_handler = logging.StreamHandler(sys.stdout)
    stream_fmt = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s')
    stream_handler.setFormatter(stream_fmt)
    stream_handler.setLevel(level=logging.DEBUG)
    logger.addHandler(stream_handler)
    
    # 将日志输出到HTTP服务器
    http_handler = HTTPHandler(host='localhost:8888', url='/', method='GET')
    http_handler.setLevel(level=logging.INFO)
    logger.addHandler(http_handler)
    # 写什么
    logger.debug(msg='debug level: 10')
    logger.info(msg='info level: 20')
    logger.warning(msg='warning level: 30')
    logger.error(msg='error level: 40')
    logger.critical(msg='critical level: 50')

    上述代码会将日志分别输入到test.log文件、控制台和HTTP服务器,当然,在执行这段代码前,还需要启动HTTPServer,并运行在8888端口。url为根路径。在Python2.x中,提供了一种简单的HTTPServer。使用如下图所示。

    Traceback

     

    除了应用之外,logging模块还支持错误回溯,也就是Traceback功能:

    import logging
    logger = logging.getLogger('logger')
    logger.setLevel(level=logging.DEBUG)
    try:
        result = 3 / 0  # O不能当被除数
    except Exception as e:
        logger.error(e, exc_info=True)

    上例中,如果exc_info参数为False的话,仅打印报错信息:

    division by zero

    现在将exc_info参数设置为True,就会得到完整的Traceback信息:

    division by zero
    Traceback (most recent call last):
      File "M:/日志模块.py", line 134, in <module>
        result = 3 / 0  # O不能当被除数
    ZeroDivisionError: division by zero

    文件配置

     

    logging模块还支持将配置写入到yaml中,方便在任何地方调用。

    现在,我们编写customLog.yaml中的代码:

    version: 1
    formatters:
      simple:
        format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    handler:
      console:
        class: logging.StreamHandler
        level: INFO
        formatter: simple
        stream: ext://sys.stdout
    loggers:
      simpleExample:
        level: INFO
        handlers: [console]
        propagate: no
    root:
      level: INFO
      handlers: [console]

    我们在实际中只需要读取这个yaml文件即可:

    import os
    import logging
    from logging import config
    import yaml  # pip install pypaml
    def custom_configure(file_ath, default_level=logging.INFO):
        if os.path.exists(file_ath):
            with open(file_ath, 'r', encoding='utf8') as f:
                config.dictConfig(yaml.load(f))
        else:
            logging.basicConfig(level=default_level)
    def run():
        logging.debug(msg='debug level: 10')
        logging.info(msg='info level: 20')
        logging.warning(msg='warning level: 30')
        logging.error(msg='error level: 40')
        logging.critical(msg='critical level: 50')
    
    if __name__ == '__main__':
        custom_configure('log.yaml')
        run()

    结果展示如下:

    2019-05-30 19:50:03,868 - root - INFO - info level: 20
    2019-05-30 19:50:03,868 - root - WARNING - warning level: 30
    2019-05-30 19:50:03,868 - root - ERROR - error level: 40
    2019-05-30 19:50:03,868 - root - CRITICAL - critical level: 50

    日志切割

     

    logging还支持日志切割,什么意思呢?就是每隔多少时间,生成一个日志,这在一些情况下相当有用,来看代码:

    import sys
    import time
    import logging
    from logging import handlers
    
    
    def logger_configure(*args, fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                         datefmt='%Y/%m/%d %H:%M:%S',
                         level=logging.INFO
                         ):
        logging.basicConfig(
            format=fmt,
            datefmt=datefmt,
            level=level,
            handlers=list(args)
        )
    
    
    if __name__ == '__main__':
        file_handler = handlers.TimedRotatingFileHandler(filename='x1.log', when='S', interval=5, backupCount=0, encoding='utf8')
        stream_handler = logging.StreamHandler(stream=sys.stdout)
        logger_configure(file_handler, stream_handler)
        for i in range(1, 20):
            time.sleep(1)
            logging.info('info level: {}'.format(i))

    上例中,日志切割通过TimedRotatingFileHandler方法完成,相关参数:

    • filename,日志文件名。
    • when,指定切割时间,可选参数有:
      • S,秒。
      • M,分。
      • H,时。
      • D,天。
      • midnight,每天凌晨。
      • W{0-6},每周,记住不是从1-7,而是从0开始的。
    • interval,每隔when时间切割一次。
    • backupCount,该参数如果大于0,则日志切割时,只保留指定的日志文件数量。什么意思呢?比如如上例中指定0,那么每5秒自动创建一个新的日志文件(文件名的格式是x1.log.2019-05-31_10-12-57),保存这5秒内的日志记录,再过5秒再次创建一个新的日志文件。这些创建的文件都会保存。但如果backupCount参数如果设置为指定的数量,比如设置为2,那么它只会保留最新的两个时间点的日志文件,之前的文件都将被删除。下图是当backupCount参数设置为2时,在本地生的日志文件。

    推荐配置

     

    通过之前日志的不同应用,我们可以总结出来一份拿走就用的配置:

    import sys
    import logging
    def logger_configure(*args, fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                         datefmt='%Y/%m/%d %H:%M:%S',
                         level=logging.INFO
                         ):
        logging.basicConfig(
            format=fmt,
            datefmt=datefmt,
            level=level,
            handlers=list(args)
        )
    if __name__ == '__main__':
        file_handler = logging.FileHandler('x1.log', mode='a', encoding='utf8')
        stream_handler = logging.StreamHandler(stream=sys.stdout)
        logger_configure(file_handler, stream_handler)
        logging.info('info level: 20')

    上例中,我们将logging的配置封装成函数,给一些默认的配置参数,也可以手动配置。而args则接收多个输出模式。当我们在使用的时候直接调用该函数,并传递输出模式即可。

    示例

     

    配置文件

    # ---------------- 日志相关 --------------------
    # 日志级别
    LOG_LEVEL = 'debug'
    LOG_STREAM_LEVEL = 'debug'  # 屏幕输出流
    LOG_FILE_LEVEL = 'info'   # 文件输出流
    
    # 日志文件命名
    
    LOG_FILE_NAME = os.path.join(BASE_PATH, 'logs', datetime.datetime.now().strftime('%Y-%m-%d') + '.log')

    一些可以调整的参数,尽量写在配置文件中,方便动态调整。
    demo.py

    import logging
    from conf import config
    
    
    class LoggerHandler:
        """ 日志操作 """
        _logger_level = {
            'debug': logging.DEBUG,
            'info': logging.INFO,
            'warning': logging.WARNING,
            'error': logging.ERROR,
            'critical': logging.CRITICAL
        }
    
        def __init__(self, log_name, file_name, logger_level, stream_level='info', file_level='warning'):
            self.log_name = log_name
            self.file_name = file_name
            self.logger_level = self._logger_level.get(logger_level, 'debug')
            self.stream_level = self._logger_level.get(stream_level, 'info')
            self.file_level = self._logger_level.get(file_level, 'warning')
            # 创建日志对象
            self.logger = logging.getLogger(self.log_name)
            # 设置日志级别
            self.logger.setLevel(self.logger_level)
            if not self.logger.handlers:
                # 设置日志输出流
                f_stream = logging.StreamHandler()
                f_file = logging.FileHandler(self.file_name)
                # 设置输出流级别
                f_stream.setLevel(self.stream_level)
                f_file.setLevel(self.file_level)
                # 设置日志输出格式
                formatter = logging.Formatter(
                    "%(asctime)s %(name)s %(levelname)s %(message)s"
                )
                f_stream.setFormatter(formatter)
                f_file.setFormatter(formatter)
                self.logger.addHandler(f_stream)
                self.logger.addHandler(f_file)
    
        @property
        def get_logger(self):
            return self.logger
    
    
    def logger(log_name='接口测试'):
        return LoggerHandler(
            log_name=log_name,
            logger_level=config.LOG_LEVEL,
            file_name=config.LOG_FILE_NAME,
            stream_level=config.LOG_STREAM_LEVEL,
            file_level=config.LOG_FILE_LEVEL
        ).get_logger
    
    
    if __name__ == '__main__':
        logger().debug('aaaa')
        logger().info('aaaa')
        logger().warning('aaaa')

    这样,我们使用时,直接导入logger函数即可。

  • 相关阅读:
    制作在线简历(一)——Loading与底部菜单
    然而这并没有什么卵用
    移动开发中Fiddler的那些事儿
    多种方法实现Loading(加载)动画效果
    总结)Nginx/LVS/HAProxy负载均衡软件的优缺点详解
    SQLServer和MySQL job和 event定时器的差别
    全局ID的重要性
    Windows操作系统上各种服务使用的端口号, 以及它们使用的协议的列表
    Linux发展历史图
    奇特的Local System权限(转载)
  • 原文地址:https://www.cnblogs.com/zhang-da/p/12234728.html
Copyright © 2011-2022 走看看