日志模块logging的使用

日志模块

介绍

python里用于记录运行信息,报错信息的日志工具是第三方库:loggging

日志库的基本组成:记录器(Logger),处理器(Handlers),过滤器(Filter),Formatter(格式器)

记录器

Logger: 用于收集信息,是日志库的使用基础。记录器可以设置记录层级,即什么级别的消息可以被记录。

日志级别大小:DEBUG->INFO->WARNING->ERROR->CRITICAL

创建日志记录器logger = logging.getLogger('my_logger')

这里的参数:my_logger是一个用以区分不同记录器的标识,而且记录器是分层级的比如:用 a 创建一个记录器,然后再用 a.b 创建记录器,这时a记录器就是b记录器的父级记录器

设置日志记录器的级别logger.setLevel(logging.DEBUG)

pythonlogging模块中,记录器以命名空间的形式组织,形成一颗树状结构,根记录器是这一结构的根部,默认情况下,其名称为空字符串''

如果你创建一个命名的记录器(如my_logger),它将默认成为根记录器的子级,所有命名格式的记录器都将使用点. 符号来表示层级关系,如my_logger.son_logger记录器则为 my_logger记录器的子记录器

消息流向

当你调用记录器的日志函数如(debuginfowarnerrorcritical)时,消息首先在该记录器上处理,然后再将消息往上级继续传递

处理器

Handlers: 讲记录的日志发送到指定位置(终端打印或者保存到文件),处理器也能设置日志级别,什么级别的日志能够被处理

常用的日志处理器:

StreamHandler --> 输出控制平台

FileHandler --> 输出到文件

创建处理器对象(终端输出处理器): handler = logging.StreamHandler()

创建处理器对象(文件输出处理器)file_handler = logging.FileHandler('file_log',mode='a')

处理器绑定记录器logger.addHandler(handler )

一个记录器可以绑定多个处理器,不同的处理器互不干涉

格式器

Formatters: 用于设置日志的输出格式

在格式器中可以使用Python fomatting string来规定具体格式,即可以通过占位符%S的方式来插入动态数据:

logging.warning('%s before you %s','第一位','第二位')

创建格式器formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

格式器绑定处理器handler.setFormatter(formatter)

自定义输出格式

内置关键字:

%(levelname)s:打印日志级别的名称

%(levelno)s:打印日志级别的数值

%(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]

%(filename)s:打印当前执行程序名。

%(funcName)s:打印日志的当前函数

%(lineno)s:打印日志的当前行号

%(asctime)s:打印日志的时间

%(thread)s:打印线程id

%(threadName)s:打印线程名称

%(process)s:打印进程ID

%(processName)s:打印线程名称

%(module)s:打印模块名称

%(message)s:打印日志信息

%(name)s:打印接收该消息的记录器标识名

用法:

通过logging.basicConfig设置

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(funcName)s - %(message)s',datefmt='%Y/%d %H:%M:%S',style='%' # '%', ‘{‘ or ‘$’)

后面的style参数是在指定格式化字符串,即占位符的格式,


默认是%作为占位符(老式格式化

用法:"i am %s and %d" % (name,age)

str.format()方法:于Python 2.7及以上版本引入的现代字符串格式化方法

用法:"my name is {} and i am {} years old." .format(name,age)

f-string(格式化字符串字面量):于Python 3.6 开始

用法:f"my name is {name} and i am {age} years old"

string.Template

用法:

template = Template("my name is $name and i am $age years old")
formatted_string = template.substitute(name=name,age=age)

通过Formatter类构建Formatter实例,并将其绑定到特定的handler

formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(funcName)s - %(message)s',datefmt = '%Y/%d %H:%M:%S',style='%')
# 创建一个控制台处理器
console_handler = logging.StreamHandler()
# 将格式化器绑定到处理器
console_handler.setFormatter(formatter)

同一个处理器同一时间只能绑定一个格式器,当多个格式器绑定时,后面的会覆盖上一个格式器

过滤器

Filter:在logging模块中,Filter是一个用于控制哪些日志消息应该被处理的机制,可以创建自定义的过滤器类,或者使用内置的过滤器来限制日志记录器或处理器记录的消息

创建过滤器对象filter = logging.Filter('记录器标识') 通过指定记录器标识(并非变量名),来规定只有该记录器及其子记录器生成的日志消息才会被允许通过过滤器,如果不填写任何标识,则将允许所有日志事件,无论来自哪个记录器 。

但是过滤器需要绑定记录器才能生效,所以这样的话,倒是让我觉得前面那个指定记录器标识,不知道能有多大用处。

好像理解了,因为filter初始化时输入的name,其实是为了默认filter方法而设计的,默认方法中,主要逻辑就是对比name是否存在,是否匹配。

原理详解

    def __init__(self, name=''):
        """
        Initialize a filter.

        Initialize with the name of the logger which, together with its
        children, will have its events allowed through the filter. If no
        name is specified, allow every event.
        """
        self.name = name
        self.nlen = len(name)

过滤器对象的主要功能:过滤(filter)的实现

当消息允许进入到过滤器时,过滤器会收到消息:record,内置的filter函数只对消息是否来源于许可的记录器,如果要添加其他过滤条件,则需要自定义过滤器对该功能进行继承重写

原理

     def filter(self, record):
        """
        Determine if the specified record is to be logged.

        Returns True if the record should be logged, or False otherwise.
        If deemed appropriate, the record may be modified in-place.
        """
        if self.nlen == 0:
            return True
        elif self.name == record.name:
            return True
        elif record.name.find(self.name, 0, self.nlen) != 0:
            return False
        return (record.name[self.nlen] == ".")

自定义过滤器

# 创建自定义过滤器
class MyFilter(logging.Filter):
    def filter(self, record):
        # 只处理严重程度是WARNING及以上的日志
        return record.levelno >= logging.WARNING
 
# 创建日志记录器
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG)  # 设置日志级别

# 创建一个控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)  # 设置处理器的日志级别

# 创建一个格式化器实例
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)

# 添加自定义过滤器到处理器
console_handler.addFilter(MyFilter())

# 将处理器添加到记录器上
logger.addHandler(console_handler)

# 记录一些日志
logger.debug('This is a debug message.')  # 这条消息将被过滤掉
logger.info('This is an info message.')    # 这条消息将被过滤掉
logger.warning('This is a warning message.')  # 这条消息将被记录
logger.error('This is an error message.')  # 这条消息将被记录
logger.critical('This is a critical message.')  # 这条消息将被记录

关于根处理器和日志处理器导致重复打印信息

def filter1_demo():
    # 创建一个日志记录器
    logging.basicConfig(level=logging.DEBUG)
    parent_logger = logging.getLogger('my_logger')
    parent_logger.setLevel(logging.DEBUG)
    console_handler = logging.StreamHandler()
    parent_logger.addHandler(console_handler)

    parent_logger.debug('parent:This is a debug message.')

执行结果

parent:This is a debug message.
DEBUG:my_logger12:parent:This is a debug message.

原因详解

1. 在配置logging.basicConfig(level=logging.DEBUG)时,会为根记录器(root logger)设置一个级别为`DEBUG`的默认处理器(通常为StreamHandler)
2. 创建了记录器:my_logger,并且配置了处理器
3. 任何记录器都会将消息往上一级传递,一直到根记录器
4. 如果输出格式中有%(name)s ,那么将显示的是一开始接收到消息的记录器的标识

那么结果很明显:
输出两条消息的结果
第一条:这个消息是记录器本身发出的,没有任何格式
parent:This is a debug message.
第二条:这个消息是记录器传递到根记录器上,由根处理器处理,根处理器的默认格式为:level:name:message
DEBUG:my_logger12:parent:This is a debug message.

快速使用demo

import logging

def logging_demo():
    #如果不写`logging.basicConfig()`设置日志级别,那么将默认级别为空,无法收集任何日志
    logging.basicConfig(level=logging.INFO)
    logging.debug(123)

if __name__ == '__main__':
    logging_demo()

模块化使用demo

import logging

# 创建一个日志记录器
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG)  # 设置日志级别

# 创建一个控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)  # 设置处理器的日志级别

# 创建一个格式化器实例
formatter = logging.Formatter('%(asctime)s - %(filename)s -%(lineno)s - %(levelname)s - %(message)s',
        datefmt='%Y年%m月%d日  %H:%M:%S')

# 将格式化器绑定到处理器
console_handler.setFormatter(formatter)

# 将处理器添加到记录器上
logger.addHandler(console_handler)

# 记录一些日志
logger.debug('This is a debug message.')
logger.info('This is an info message.')
logger.warning('This is a warning message.')
logger.error('This is an error message.')
logger.critical('This is a critical message.')