编辑
2024-07-24
未分类
00
请注意,本文编写于 86 天前,最后修改于 86 天前,其中某些信息可能已经过时。

目录

【logging】Python 多层级日志输出
1. 简介
2. 基础用法
2.1. basicConfig
3. 模块化定制 logging
3.1. Logger
3.2. Handler
3.3. Filter
3.4. Formatter
4. 使用字典配置 Logger
参考文档

【logging】Python 多层级日志输出

1. 简介

在应用的开发过程中,我们常常需要去记录应用的状态,事件,结果。而 Python 最基础的 Print 很难满足我们的需求,这种情况下我们就需要使用 python 的另一个标准库:logging

这是一个专门用于记录日志的模块。相对于 Print 来说,logging提供了日志信息的分级,格式化,过滤等功能。如果在程序中定义了丰富而有条理的 log 信息,那么可以非常方便的去分析程序的运行状况,在有问题时也能够方便的去定位问题,分析问题。

以下是具体的一些应用场景。

执行的任务这项任务的最佳工具
显示控制台输出print()
报告在程序正常运行期间发生的事件logging.info()或 logging.debug()
发出有关特定运行时事件的警告logging.warning()
报告有关特定运行时事件的错误抛出异常
报告错误但不抛出异常logging.error(), logging.exception()或 logging.critical()

2. 基础用法

以下是一些logging最基础的使用方法,如果不需要深入的去定制 log 的话,那么只需要使用最基础的部分即可。

python
In [1]: import logging In [2]: logging.info('hello world') In [3]: logging.warning('good luck') WARNING:root:good luck

可以看到,logging.info()的日志信息没有被输出,而logging.warning()的日志信息被输出了,这就是因为logging的日志信息分为几个不同的重要性级别,而默认输出的级别则是warning,也就是说,重要性大于等于warning的信息才会被输出。

以下是logging模块中信息的五个级别,重要性从上往下递增。

等级什么时候使用
DEBUG详细信息,通常仅在 Debug 时使用。
INFO程序正常运行时输出的信息。
WARNING表示有些预期之外的情况发生,或者在将来可能发生什么情况。程序依然能按照预期运行。
ERROR因为一些严重的问题,程序的某些功能无法使用了。
CRITICAL发生了严重的错误,程序已经无法运行。

我们也可以通过设置来设定输出日志的级别:

python
In [1]: import logging In [2]: logging.basicConfig(level=logging.DEBUG) In [3]: logging.info('hello world') INFO:root:hello world

可以看到,在设定了level参数为logging.DEBUG后,logging.info()的日志信息就正常输出了。


2.1. basicConfig

python
logging.basicConfig(**kwargs)

通过basicConfig()方法可以为logging做一些简单的配置。此方法可以传递一些关键字参数。

  • filename

    文件名参数,如果指定了这个参数,那么logging会把日志信息输入到指定的文件之中。

    python
    import logging logging.basicConfig(filename='example.log') logging.warning('Hello world')
  • filemode

    如果指定了filename来输出日志到文件,那么filemode就是打开文件的模式,默认为'a',追加模式。当然也可以设置为'w',则每一次输入都会丢弃掉之前日志文件中的内容。

  • format

    指定输出的 log 信息的格式。

    python
    In [1]: import logging In [2]: logging.basicConfig(format='%(asctime)s %(message)s') In [3]: logging.warning('hello world') 2018-07-06 16:28:12,074 hello world
  • datefmt

    如果在format中使用了asctime输出时间,那么可以使用此参数控制输出日期的格式,使用方式与time.strftime()相同。

  • level

    设置输出的日志的级别,只有高出此级别的日志信息才会被输出。

    python
    In [1]: import logging In [2]: logging.basicConfig(level=logging.INFO) In [3]: logging.info('hi') INFO:root:hi In [4]: logging.debug('byebye')

注:需要注意的是,basicConfig()方法是一个一次性的方法,只能用来做简单的配置,多次的调用basicConfig()是无效的。


3. 模块化定制 logging

在深度使用logging来定制日志信息之前,我们需要先来了解一下logging的结构。logging的主要逻辑结构主要由以下几个组件构成:

  • Logger:提供应用程序直接使用的接口。
  • Handler:将 log 信息发送到目标位置。
  • Filter:提供更加细粒度的 log 信息过滤。
  • Formatter:格式化 log 信息。

这四个组件是logging模块的基础,在基础用法中的使用方式,其实也是这四大组件的封装结果。

这四个组件的关系如下所示:

img

image.png

logger主要为外部提供使用的 api 接口,而每个logger下可以设置多个Handler,来将 log 信息输出到多个位置,而每一个Handler下又可以设置一个Formatter和多个Filter来定制输出的信息。


3.1. Logger

Logger这个对象主要有三个任务要做:

  • 向外部提供使用接口。
  • 基于日志严重等级(默认的过滤组件)或 filter 对象来决定要对哪些日志进行后续处理。
  • 将日志消息传送给所有符合输出级别的Handlers

logging.getLogger(name=None)

首先,我们需要通过getLogger()方法来生成一个Logger,这个方法中有一个参数 name,则是生成的Logger的名称,如果不传或者传入一个空值的话,Logger的名称默认为 root。

python
In [1]: import logging In [2]: logger = logging.getLogger('nanbei')

需要注意的是,只要在同一个解释器的进程中,那么相同的Logger名称,使用getLogger()方法将会指向同一个Logger对象。

而使用logger的一个好习惯,是生成一个模块级别的Logger对象:

python
In [1]: logger = logging.getLogger(__name__)

通过这种方式,我们可以让logger清楚的记录下事件发生的模块位置。

除此之外,logger对象是有层级结构的:

  • Logger的名称可以是一个以.分割的层级结构,每个.后面的Logger都是.前面的logger的子辈。

    例如,有一个名称为nanbeilogger,其它名称分别为nanbei.ananbei.bnanbei.a.c都是nanbei的后代。

  • Logger在完成对日志消息的处理后,默认会将 log 日志消息传递给它们的父辈Logger相关的Handler

    因此,我们不不需要去配置每一个的Logger,只需要将程序中一个顶层的Logger配置好,然后按照需要创建子Logger就好了。也可以通过将一个loggerpropagate属性设置为 False 来关闭这种传递机制。

例如:

python
In [1]: import logging # 生成一个名称为nanbei的Logger In [2]: logger = logging.getLogger('nanbei') # 生成一个StreamHandler,这个Handler可以将日志输出到console中 In [3]: sh = logging.StreamHandler() # 生成一个Formatter对象,使输出日志时只显示Logger名称和日志信息 In [4]: fmt = logging.Formatter(fmt='%(name)s - %(message)s') # 设置Formatter到StreamHandler中 In [5]: sh.setFormatter(fmt) # 将Handler添加到Logger中 In [6]: logger.addHandler(sh) # 生成一个nanbei的子Logger:nanbei.child In [7]: child_logger = logging.getLogger('nanbei.child') # 可以看到两个Logger输出的日志信息都使用了相同的日志格式 In [8]: logger.warning('hello') nanbei - hello In [9]: child_logger.warning('hello') nanbei.child - hello

Logger对象中,主要提供了以下方法:

方法描述
Logger.setLevel()设置日志器将会处理的日志消息的最低输出级别
Logger.addHandler() 和 Logger.removeHandler()为该 logger 对象添加、移除一个 handler 对象
Logger.addFilter() 和 Logger.removeFilter()为该 logger 对象添加、移除一个 filter 对象
Logger.debug(),Logger.info(),Logger.warning(),Logger.error(),Logger.critical()输出一条与方法名对应等级的日志
Logger.exception()输出一条与 Logger.error()类似的日志,包含异常信息
Logger.log()可以传入一个明确的日志 level 参数来输出一条日志

3.2. Handler

Handler的作用主要是把 log 信息输出到我们希望的目标位置,其提供了如下的方法以供使用:

方法描述
Handler.setLevel()设置 handler 处理日志消息的最低级别
Handler.setFormatter()为 handler 设置一个格式器对象
Handler.addFilter() 和 Handler.removeFilter()为 handler 添加、删除一个过滤器对象

我们可以通过这几个方法,给每一个Handler设置一个Formatter和多个Filter,来定制不同的输出 log 信息的策略。

Handler本身是一个基类,不应该直接实例化使用,我们应该使用的是其多种多样的子类,每一个不同的子类可以将日志信息输出到不同的目标位置,以下是一些常用的Handler

Handler描述
logging.StreamHandler将日志消息发送到输出到 Stream,如 std.out, std.err 或任何 file-like 对象。
logging.FileHandler将日志消息发送到磁盘文件,默认情况下文件大小会无限增长
logging.handlers.RotatingFileHandler将日志消息发送到磁盘文件,并支持日志文件按大小切割
logging.hanlders.TimedRotatingFileHandler将日志消息发送到磁盘文件,并支持日志文件按时间切割
logging.handlers.HTTPHandler将日志消息以 GET 或 POST 的方式发送给一个 HTTP 服务器
logging.handlers.SMTPHandler将日志消息发送给一个指定的 email 地址
logging.NullHandler该 Handler 实例会忽略 error messages,通常被想使用 logging 的 library 开发者使用来避免'No handlers could be found for logger XXX'信息的出现。

3.3. Filter

Filter可以被HandlerLogger用来做比 level 分级更细粒度的、更复杂的过滤功能。

Filter是一个过滤器基类,它可以通过 name 参数,来使这个logger下的日志通过过滤。

python
class logging.Filter(name='')

比如,一个Filter实例化时传递的 name 参数值为A.B,那么该Filter实例将只允许名称为类似如下规则的Loggers产生的日志通过过滤:A.BA.B.CA.B.C.DA.B.D

而名称为A.BBB.A.BLoggers产生的日志则会被过滤掉。如果 name 的值为空字符串,则允许所有的日志事件通过过滤。

python
In [1]: import logging In [2]: logger = logging.getLogger('nanbei') In [3]: filt = logging.Filter(name='nanbei.a') In [4]: sh = logging.StreamHandler() In [5]: sh.setLevel(logging.DEBUG) In [6]: sh.addFilter(filt) In [7]: logger.addHandler(sh) In [8]: logging.getLogger('nanbei.a.b').warning('i am nanbei.a.b') i am nanbei.a.b In [9]: logging.getLogger('nanbei.b.b').warning('i am nanbei.a.b')

可以看到,名称为nanbei.b.bLogger的日志没有被输出。


3.4. Formatter

Formater对象用于配置日志信息的最终顺序、结构和内容。

Formatter类的构造方法定义如下:

python
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
  • fmt

    这个参数主要用于格式化 log 信息整体的输出。

    以下是可以用来格式化的字段:

    字段/属性名称使用格式描述
    asctime%(asctime)s日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896
    created%(created)f日志事件发生的时间--时间戳,就是当时调用 time.time()函数返回的值
    relativeCreated%(relativeCreated)d日志事件发生的时间相对于 logging 模块加载时间的相对毫秒数(目前还不知道干嘛用的)
    msecs%(msecs)d日志事件发生事件的毫秒部分
    levelname%(levelname)s该日志记录的文字形式的日志级别('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
    levelno%(levelno)s该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
    name%(name)s所使用的日志器名称,默认是'root',因为默认使用的是 rootLogger
    message%(message)s日志记录的文本内容,通过 msg % args计算得到的
    pathname%(pathname)s调用日志记录函数的源码文件的全路径
    filename%(filename)spathname 的文件名部分,包含文件后缀
    module%(module)sfilename 的名称部分,不包含后缀
    lineno%(lineno)d调用日志记录函数的源代码所在的行号
    funcName%(funcName)s调用日志记录函数的函数名
    process%(process)d进程 ID
    processName%(processName)s进程名称,Python 3.1 新增
    thread%(thread)d线程 ID
    threadName%(thread)s线程名称
  • datefmt

    如果在 dmt 中指定了 asctime,那么这个参数可以用来格式化 asctime 的输出,使用方式与 time.strftime()相同。

  • style

    Python 3.2 新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'。


4. 使用字典配置 Logger

可以看到使用 logging 内置的方法去配置 Logger 的话,会比较繁琐,特别是配置多个 Logger 的时候,写的代码会很多很杂乱。logging 还提供了文件配置和字典配置两种方式,可以使代码更有条理,但由于文件配置的 API 比较老旧,有一些功能不能使用,所以这里我们只介绍字典配置方式。

从字典配置主要使用以下方法:

python
logging.config.dictConfig(config)

此方法通过传入一个字典来进行配置,字典中可包含的key如以下所示:

  • version - 必选项,其值是一个整数值,表示配置格式的版本,当前唯一可用的值是 1。

  • disable_existing_loggers - 可选项,默认值为 True。该选项用于指定是否禁用已存在的日志器 loggers,如果 incremental 的值为 True 则该选项将会被忽略。

  • incremental - 可选项,默认值为 False。该选项的意义在于,如果这里定义的对象已经存在,那么这里对这些对象的定义是否应用到已存在的对象上。值为 False 表示,已存在的对象将会被重新定义。

  • root - 可选项,这是 root logger 的配置信息,其值也是一个字典对象。除非在定义其它 logger 时明确指定 propagate 值为 no,否则 root logger 定义的 handlers 都会被作用到其它 logger 上。

  • loggers

    - 可选项,其值是一个字典对象,该字典对象每个元素的 key 为要定义的日志器名称,value 为日志器的配置信息组成的字典,其中包含的选项有:

    • level (optional). logger 的 level。
    • propagate (optional). 是否传播给父记录器。
    • filters (optional). 包含的 filters 列表。
    • handlers (optional). 包含的 handlers 列表。
  • handlers

    - 可选项,其值是一个字典对象,该字典对象每个元素的 key 为要定义的处理器名称,value 为处理器的配置信息组成的字典,包含的选项有:

    • class (mandatory) - handler 的类型。
    • level (optional) - handler 的 level。
    • formatter (optional) - handler 使用的 formatter。
    • filters (optional) - 包含的 filters 列表。
  • formatters - 可选项,其值是一个字典对象,该字典对象每个元素的 key 为要定义的格式器名称,value 为格式器的配置信息组成的 dict,如 format 和 datefmt。

  • fittlers - 可选项,其值是一个字典对象,该字典对象每个元素的 key 为要定义的过滤器名称,value 为过滤器的配置信息组成的 dict,如 name。

在这里并没有完全列出每一个对象所需的 key,但熟悉模块化定制 logger 之后,其构造所需的参数与字典构造基本是一致的,以下有一个使用简单的例子:

python
import logging import logging.config import os path = os.path.abspath(__file__) BASE_DIR = os.path.dirname(os.path.dirname(path)) debug_flag = True # 给过滤器使用的判断 class RequireDebugTrue(logging.Filter): # 实现filter方法 def filter(self, record): return debug_flag logging_config = { #必选项,其值是一个整数值,表示配置格式的版本,当前唯一可用的值就是1 'version': 1, # 是否禁用现有的记录器 'disable_existing_loggers': False, # 过滤器 'filters': { 'require_debug_true': { '()': RequireDebugTrue, #在开发环境,我设置DEBUG为True;在客户端,我设置DEBUG为False。从而控制是否需要使用某些处理器。 } }, #日志格式集合 'formatters': { 'simple': { 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s', }, }, # 处理器集合 'handlers': { # 输出到控制台 'console': { 'level': 'DEBUG', # 输出信息的最低级别 'class': 'logging.StreamHandler', 'formatter': 'simple', # 使用standard格式 'filters': ['require_debug_true', ] }, # 输出到文件 'log': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'simple', 'filename': os.path.join(BASE_DIR, 'debug.log'), # 输出位置 'maxBytes': 1024 * 1024 * 5, # 文件大小 5M 'backupCount': 5, # 备份份数 'encoding': 'utf8', # 文件编码 }, }, # 日志管理器集合 'loggers':{ 'root': { 'handlers': ['console','log'], 'level': 'DEBUG', 'propagate': True, # 是否传递给父记录器 }, 'simple': { 'handlers': ['console','log'], 'level': 'WARN', 'propagate': True, # 是否传递给父记录器, } } } logging.config.dictConfig(logging_config) logger = logging.getLogger('root') # 尝试写入不同消息级别的日志信息 logger.debug("debug message") logger.info("info message") logger.warn("warn message") logger.error("error message") logger.critical("critical message")

参考文档

2018.07.14 18:22:04 原文链接-简书

本文作者:任浪漫

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处!