python 日志(logging模块)

1. 为什么要使用日志(作用)

在学习过程中,写一个小程序或小demo时,遇到程序出错时,我们一般会将一些信息打印到控制台进行查看,方便排查错误。这种方法在较小的程序中很实用,但是当你的程序变大,或是说在实际的工程项目中,代码量都是很大的,代码量一大,不可控因素就多,再将信息打印到控制台进行查看是非常不方便的,此时就需要使用日志来记录程序运行过程中的相关信息,监控程序的运行情况。

2. 基础概念

我们在程序中会使用print打印一些信息以供我们判断程序的运行情况,什么时候需要使用print打印一些信息以及在什么位置使用print,这些都是根据程序自身以及个人的经验。使用日志和使用print是相似的,不同的时,日志具有多种形式来记录一些信息,例如 logging.FileHandler ****将日志信息文件写入文件,logging.handlers.SMTPHandler 将日志信息通过SMTP发送到一个电子邮件地址。

记录和处理日志信息的方式有很多种,logging库中提供了许多方法:https://docs.python.org/zh-cn/3/library/logging.handlers.html# 本文只记录 StreamHandlerFileHandler 的使用方法。

logging库虽然提供了多种方法来记录和处理日志信息,但并不是什么信息都需要记录的,我们往往会根据程序中各个模块的重要程度来决定是否需要记录信息以及记录哪些信息。logging库根据事件的重要性提供了以下几个级别:

级别 使用时机
DEBUG 细节信息,仅当诊断问题时适用。
INFO 确认程序按预期运行,和print使用类似。
WARNING 表明有已经或即将发生的意外(例如:磁盘空间不足,数值运算产生精度丢失等)。程序仍按预期进行。
ERROR 由于严重的问题,程序的某些功能已经不能正常执行。
CRITICAL 严重的错误,表明程序已不能继续执行。

级别大小:DEBUG < INFO < WARNING

划分处理等级的目的是为了针对不同的情况来记录有用的信息,方便有效地监控程序的运行状况,当程序出错时也能方便的定位到。因此在使用logging库记录和处理日志信息前,需要先设定处理等级,默认的等级为 WARNING,当记录和处理日志信息时,只处理大于等于该等级的日志信息。

3. logging库的一些简单使用案例

3.1 像print一样打印日志信息

使用debug()、info()、warning()、error()、critical() 方法分别打印对应的级别及以上的信息,因为默认的级别为 WARNING,所以只会对warning()、error()、critical() 中的信息进行打印。

import logging

if __name__ == "__main__":
    logging.debug("the message of debug")
    logging.info("the message of debug")
    logging.warning("the message of warning")
    logging.error("the message of error")
    logging.critical("the message of critical")

输出结果:
WARNING:root:the message of warning
ERROR:root:the message of error
CRITICAL:root:the message of critical

3.2 设置级别

使用 basicConfig() 方法设置级别,传入关键字参数 level ,参数值为对应级别的字符串形式。

import logging

if __name__ =="__main__":
    logging.basicConfig(level='DEBUG')
    logging.debug("the message of debug")
    logging.info("the message of debug")
    logging.warning("the message of warning")
    logging.error("the message of error")
    logging.critical("the message of critical")

输出结果:
DEBUG:root:the message of debug
INFO:root:the message of debug
WARNING:root:the message of warning
ERROR:root:the message of error
CRITICAL:root:the message of critical

3.3 在日志中打印变量信息

默认使用 printf 风格的方式进行字符串格式化。

import logging

if __name__ == "__main__":
    name = "张三"
    age = 24
    logging.warning("%s的年龄为%s岁", name, age)
		# 也可以使用format的方式进行字符串格式化
		logging.warning("{}的年龄为{}岁".format(name, age))

输出结果:
WARNING:root:张三的年龄为24

3.4 更改日志信息显示的格式

对于可以出现在格式字符串中的全部内容,可以参考官方文档 LogRecord 属性 。

我们可以将日志信息的显示格式使用关键字 format 设置为:

format='%(asctime)s: %(name)s: %(levelname)s: %(filename)s: %(funcName)s: %(message)s'

记录时间 - 记录器的名称 - 事件等级 - 产生日志信息的源文件名 - 所在函数名 - 提示信息

import logging

def main():
    logging.basicConfig(format='%(asctime)s: %(name)s: %(levelname)s: %(filename)s: %(funcName)s: %(message)s',
                        level=logging.DEBUG)
    logging.debug('This message should appear on the console')
    logging.info('So should this')
    logging.warning('And this, too')

if __name__ == "__main__":
    main()

输出结果:
2022-04-19 15:30:02,404: root: DEBUG: logging_demo.py: main: This message should appear on the console
2022-04-19 15:30:02,404: root: INFO: logging_demo.py: main: So should this
2022-04-19 15:30:02,404: root: WARNING: logging_demo.py: main: And this, too

3.5 将日志信息写入文件

指定文件名 filename,文件写入方式 filenmode,默认写入方式为 a

import logging

def main():
    fmt = '%(asctime)s: %(name)s: %(levelname)s: %(filename)s: %(funcName)s: %(message)s'
    logging.basicConfig(filename="example.log", filemode='w', format=fmt, level="DEBUG")
    logging.debug('This message should appear on the console')
    logging.info('So should this')
    logging.warning('And this, too')

if __name__ == "__main__":
    main()

3.6 basicConfig

前面几个案例都使用到了 basicConfig 方法对打印和记录日志信息的行为进行设置,可以设置是将信息显示在控制台还是记录到文件中,可以设置显示或记录的内容格式,设置事件等级等,更详细的设置官方文档已经给出了很细致的解释。

4. 进一步使用

当程序规模变大,使用上面方式介绍的记录日志的方式就会显得捉襟见肘。下面介绍 logging库的进阶使用方式。

logging 库采用模块化的方式来记录日志信息,由以下四个核心组件组成:

Logger(记录器):根据记录器设置的等级捕获日志信息,并传递给对应的处理器;

Handler(处理器):将记录器传递过来的日志信息交给不同的处理器处理;

Filter(过滤器):更细粒度地控制要输出哪些日志记录;

Formatter(格式器):设置日志记录的输出格式;

下面说明这几个组件之间的联系及处理顺序:

  1. 首先通过 getLogger() 方法一个指定名称的 Logger实例的引用,如果没提供名称,则返回 rootlogger = getLogger(name)
  2. 为 Logger 实例设置捕获信息的等级,默认等级为 WARNING,logger.setLevel('INFO')
  3. logger 通过调用 debug()、info()、warning()、error()、critical() 这些方法来捕获日志信息;Logger 只暴露接口供程序员调用,用来捕获日志信息,而不处理信息;只捕获大于等于其自身等级的信息;
  4. 如果 logger 捕获了日志信息,则将日志信息传递给 Handler 进行处理;
  5. 每个 logger 可以有多个 Handler 处理捕获的日志信息,常用的 StreamHandlerFileHandlerStreamHandler 将日志信息打印到终端显示,FileHandler 将日志信息记录写入到文件中;不同的 Handler 也都具有等级属性,每个Handler 只会处理大于等于自身等级的日志信息;

其中 Filter 可以作用 Logger 和 Handler,用于更细粒度地控制捕获或输出哪些日志信息,本文不讨论 Filter 使用。

日志事件信息流程如下图所示:

python 日志(logging模块)_第1张图片

整体代码框架如下:

# 实例化一个 Logger 对象,并记录一个名字
# 在命名记录器时使用的一个好习惯,是在每个使用日志记录的模块中使用模块级记录器
logger = logging.getLogger(__name__)
# 设置记录器捕获日志信息的等级
logger.setLevel("INFO")

# 实例化处理器对象
ch = logging.StreamHandler()
fh = logging.FileHandler("example.log")

# 设置处理器处理信息的等级
ch.setLevel("INFO")
fh.setLevel("WARNING")

# 设置处理器输出日志信息的格式
fmt = logging.Formatter('%(asctime)s: %(levelname)s: %(name)s: %(filename)s: %(message)s')
ch.setFormatter(fmt)
fh.setFormatter(fmt)

# 给记录器添加处理器
logger.addHandler(ch)
logger.addHandler(fh)

# 捕获日志信息
logger.info("the message info")
logger.error("the message of error")

将上面代码封装成一个类,方便使用

import logging

class Logger:
    """
        将日志信息打印到控制台和记录到文件的操作封装成一个类
    """
    def __init__(self, name: str, console_handler_level: str = logging.DEBUG,
                 fmt: str = '%(asctime)s: %(levelname)s: %(name)s: %(filename)s: %(message)s'):
        """
            默认会添加一个等级为 'DEBUG' 的 Handler 对象到 Logger 对象
        :param name: handler 的名称
        :param console_handler_level: 设置 StreamHandler 的等级
        :param fmt: 日志消息的显示格式
        """
        self.logger = logging.getLogger(name)
        self.logger.setLevel(logging.DEBUG)
        self.fmt = logging.Formatter(fmt)
        self.set_console_handler(console_handler_level)

    def set_console_handler(self, console_handler_level: str = logging.DEBUG) -> None:
        ch = logging.StreamHandler()
        ch.setLevel(console_handler_level)
        ch.setFormatter(self.fmt)
        self.logger.addHandler(ch)

    def set_file_handler(self, filename: str, mode: str = 'a', file_handler_level: str = logging.INFO) -> None:
        fh = logging.FileHandler(filename, mode=mode, encoding='utf-8')
        fh.setLevel(file_handler_level)
        fh.setFormatter(self.fmt)
        self.logger.addHandler(fh)

    def debug(self, msg):
        self.logger.debug(msg)

    def info(self, msg):
        self.logger.info(msg)

    def warning(self, msg):
        self.logger.warning(msg)

    def error(self, msg):
        self.logger.error(msg)

    def critical(self, msg):
        self.logger.critical(msg)

你可能感兴趣的:(python,python)