每天一个python知识点:logging日志

286 阅读4分钟

本文将详细介绍Python内置的日志模块logging,包括日志记录的步骤、日志处理器、日志过滤器、日志格式化、父子日志、日志配置方法以及一些高级用法,如自定义日志处理器、日志装饰器和异步日志记录。帮助小伙伴更好地理解和使用日志记录功能。

一、日志记录

1.日志记录的一般流程

  • 获取日志对象;获取指定名称的日志记录器,如果不指定日志器名称,则默认创建root对象,记录WARNING及以上等级的日志
  • 设置日志等级等级可以在日志对象上设置,也可以在日志处理器上设置,并且高等级日志不能记录低等级日志。日志等级从低到高依次为:NOTSET(日志等级为0)DEBUG(日志等级为10)INFO(日志等级为20)WARNING(日志等级为30)ERROR(日志等级为40)CRITICAL(日志等级为50)
  • 设置日志格式;只能在日志处理器上设置,有三种格式:%{$
  • 添加日志处理器;用于处理日志并记录日志。常用的日志处理器有:FileHandler(文件处理)、StreamHandler(控制台)、RotatingFileHandler(按文件大小分割日志) 、 TimedRotatingFileHandler (按时间分割日志)、 SocketHandler(远程日志)、QueueHandler(异步队列日志),根据不同的日志处理器实现不同的日志功能
  • 添加日志过滤器;用于过滤日志,可以在日志对象上添加,也可以在日志处理器上添加。 可以使用logging.Filter(name)通过name参数指定要过滤的日志对象,也可以自定义日志过滤器
  • 记录日志;使用日志对象提供的方法进行记录。logger.debug()、logger.info()、logger.warning()、logger.error()、logger.exception()、logger.critical()、 logger.log()、logger.exception()
    	# 1.创建日志对象,指定日志对象作用的范围,默认是从根目录下面生效
    	logger = logging.getLogger(__name__)
    
    	# 2.在日志对象中设置日志级别
    	logger.setLevel(logging.INFO)
    
    	# 3.设置日志输出格式
    	formater = logging.Formatter(
        "%(levelname)s - %(lineno)s - %(asctime)s - %(username)s - %(message)s"
    	)
    
    	# 4.设置日志处理器
    	fileHandler = logging.FileHandler(os.path.join(LOG_DIR, "日志记录.log"), "a", "utf-8")
    
    	# 添加日志输出格式
    	fileHandler.setFormatter(formater)  # 4.设置日志处理器显示日志的格式
    
    	# 在日志处理器中设置日志级别
    	fileHandler.setLevel(level=logging.NOTSET)  # 设置记录日志的等级, 日志记录器中设置的级别必须高于日志对象中的日志级别
    	# 添加日志处理器
    	logger.addHandler(fileHandler)  # 4.在日志对象中添加日志处理器
    
    	# 日志记录
    	logger.debug(msg="debug, the value is error")
    

2.日志等级的优先级

上一小节提到,设置日志等级有两种方式,日志对象和日志处理器。两者区别在于:日志对象会为所有添加的日志处理器添加日志级别,日志处理器只会为自身设置日志级别,如果两者同时设置,那么最终会记录日志级别较高日志

  • 日志对象设置的日志级别高于日志处理器:
    # 当日志对象设置的日志级别高于日志对象设置的日志级别时,日志记录器不会记录日志
    logger = logging.getLogger(name=None)
    
    # 通过日志对象设置日志级别
    logger.setLevel(logging.WARNING)
    
    file_handler = logging.StreamHandler()
    
    file_handler.setFormatter(
        logging.Formatter("%(levelname)s - %(lineno)s - %(asctime)s - %(message)s")
    )
    
    # 通过日志处理器设置日志级别
    file_handler.setLevel(level=logging.DEBUG)
    
    logger.addHandler(file_handler)
    
    # 日志记录,日志对象设置日志级别 高于 日志处理器设置的日志级别
    logger.warning(msg="warning, the value is error")  # 输出:WARNING - 461 - 2025-01-23 16:13:50,805 - warning, the value is error
    logger.info(msg="info, the value is error")  # 不输出结果
    
  • 日志对象设置的日志级别高于日志处理器:
    # 当日志对象设置的日志级别高于日志对象设置的日志级别时,日志记录器不会记录日志
    logger = logging.getLogger(name=None)
    
    # 通过日志对象设置日志级别
    logger.setLevel(logging.INFO)
    
    file_handler = logging.StreamHandler()
    
    file_handler.setFormatter(
        logging.Formatter("%(levelname)s - %(lineno)s - %(asctime)s - %(message)s")
    )
    
    # 通过日志处理器设置日志级别
    file_handler.setLevel(level=logging.WARNING)
    
    logger.addHandler(file_handler)
    
    # 日志记录, 日志处理器设置的日志级别 高于 日志对象设置日志级别
    logger.warning(msg="warning, the value is error")  # 输出:WARNING - 461 - 2025-01-23 16:13:50,805 - warning, the value is error
    logger.info(msg="info, the value is error")  # 不输出结果
    

3.日志中添加额外数据

  • 使用日志记录函数中的 args参数来添加,这种方式需要使用占位符。 通过占位符添加额外信息
  • 使用日志记录函数中的extra参数来添加:需要在日志输出格式中重新定义输出参数。 添加额外数据方式一
  • 使用logging.LoggerAdapter类来进行添加:需要在日志输出格式中重新定义输出参数。 添加额外数据方法二

二、日志记录的其他方式

1.通过basicConfig记录日志

logging.basicConfig方法的作用就是操作的是根日志器,在根日志器上添加日志处理器。所谓的根日志器就是当使用logger = logging.getLogger("root") 或者 logger = logging.getLogger() 这种方式返回的日志对象,根日志器上的日志等级为Warning,并且没有日志处理器。当根日志器上有原有的日志处理器时,logging.basicConfig方法就失去了作用,也可以通过 设置force参数为True来移除根日志器上原有的日志处理器。 根日志处理器 basicConfig方法中通过filename参数、stream参数、handlers参数创建日志处理器,优先级从高到低为: handlers > filename (stream),并且filename 参数与stream参数互斥。

  • 使用文件形式记录日志; 通过filename参数创建日志处理器
  • 使用stream参数记录日志;这里stream参数不是指定处理器,而是传递给StreamHandler类的参数。 通过stream参数创建日志处理器
  • 使用handlers参数记录日志; 通过handler参数创建日志处理器

2.通过装饰器记录日志

import logging
from functools import wraps

# 配置日志格式
formater = "%(asctime)s %(levelname)s %(name)s %(message)s"
logging.basicConfig(level=logging.INFO, format=formater)

# 装饰器
def record_logger(logger=logging.getLogger()):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            logger.info("进入了函数")
            try:
                r = func(*args, **kwargs)
            except Exception as e:
                logger.exception(msg="函数执行失败")
            else:
                logger.info(
                    "函数执行成功, 结果为: %s", (r,)
                )  # 通过args参数给日志记录器传递参数

        return wrapper

    return decorator


@record_logger()
def read_log(a: int, b: int):
    return a / b

3.通过配置文件记录日志

3.1.使用fileconfig

fileconfig方法只能处理 .conf类型的日志文件。 在这里插入图片描述

3.2.使用dictConfig

在这里插入图片描述

三、自定义日志相关对象

1.自定义日志对象

import logging

# 自定义Logger类,需要继承logging.Logger,并且重写 makeRecord()方法
class MyLogger(logging.Logger):
    def makeRecord(self,name,level,fn,lno,msg,args,exc_info,func=None,extra=None,sinfo=None,):
        extra_info = "[Custom Info]"  # 为每一条日志信息都添加自定义信息
        msg += extra_info
        return super().makeRecord(name,level,fn,lno,msg,args,exc_info,func,extra,sinfo,)

logging.setLoggerClass(MyLogger)  # 使用自定义的logger类
logger = logging.getLogger("custom_logger")

# 配置日志处理器
handler = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

# 使用自定义Logger记录日志
logger.info("这是一条经过自定义的日志信息")

2.自定义日志处理器

import logging

# 自定义Handler类,需要继承logging.Handler,并且重写emit()方法
class MyHandler(logging.StreamHandler):
    def emit(self, record):
        # record参数: 是一个 logging.LogRecord 对象
        print(
            record.levelname,
            record.levelname == logging.getLevelName(logging.DEBUG),
        )
        return super().emit(record)


logger = logging.getLogger("custom_handler")
logger.setLevel(logging.DEBUG)

handler = MyHandler()
handler.setFormatter(
    logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
)

logger.addHandler(handler)

logger.debug("这是一条经过自定义的日志信息")

3.自定义日志过滤器

import logging

# 自定义过滤器类, 需要继承logging.Filter, 并且重写filter()方法, 返回值为True,表示不过滤,返回值为False,表示过滤
class MyFilter(logging.Filter):
    def filter(self, record):
        # 表示过滤掉Info级别的日志
        if record.levelname == logging.getLevelName(logging.INFO):
            return False
        return True


logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # 设置日志等级为 DEBUG

formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler = logging.StreamHandler()
handler.setFormatter(formatter)

logger.addHandler(handler)
logger.addFilter(MyFilter())  # 添加日志过滤器

logger.info("hello")  # 这条日志被过滤掉了
logger.debug("error")  # 输出error

4.自定义日志输出格式

  • 自定义日志输出格式:
    # 自定义输出格式类,继承 logging.Formatter, 重写 format 方法
    class MyFormatter(logging.Formatter):
    	def __init__(self, fmt=None, datefmt=None, style="$", validate=True):
         	super().__init__(fmt, datefmt, style, validate)
    
    	def format(self, record: logging.LogRecord) -> str:
        	print("11111111111111111")
        	return super().format(record)
    
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    
    formatter = MyFormatter("${asctime}:${name}:${levelname}:${message}")
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)  # 添加自定义日志格式
    logger.addHandler(handler)
    
    logger.info("hello")
    logger.debug("error")
    
  • 自定义日志输出信息;请参考 使用日志记录函数中的extra参数来添加 这一小节的内容

四、其他形式的日志

1.父子日志

父子日志有两种形式,当使用子级日志记录器记录日志时,父级日志记录器也会自动记录日志,无论父级日志记录器的日志级别是否高于子级日志记录器的日志级别;当使用父级日志记录器记录日志时,子级日志记录器不会记录日志。

  • 第一种形式:使用 logging.getLogger(name)获取日志记录器时(name参数不为空 或者 不等于 root时),会自动继承根日志记录器。
    import logging
    
    # 根日志记录器
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.DEBUG)
    root_logger.addHandler(logging.StreamHandler())
    
    # 子日志记录器
    logger = logging.getLogger(name="flask")
    logger.setLevel(logging.INFO)
    logger.addHandler(logging.StreamHandler())
    
    print(logger.parent is root_logger)  # True
    root_logger.info("hello")  # 会输出两遍 Hello, 无论父级日志记录器的日志级别是否高于子级日	志记录器的日志级别
    
  • 第二种形式:使用日志层级(使用 逗号来表示 )划分,具体形式为:父级名称.子级名称
import logging

# 父级日志记录器
parent_logger = logging.getLogger(name="A")
parent_logger.setLevel(logging.DEBUG)
parent_logger.addHandler(logging.StreamHandler())

# 子日志记录器
logger = logging.getLogger(name="A.B")  # 具体形式为:父级名称.子级名称
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())

print(logger.parent is parent_logger)  # True
logger.info("hello")  # 会输出两遍 Hello, 无论父级日志记录器的日志级别是否高于子级日志记录器的日志级别

子级日志记录器可以设置 propagate属性 为0 或者False来禁止日志记录向父级传递,logger.propagate = False

2.异步日志

import logging
import queue
import time
from logging.handlers import QueueHandler, QueueListener

# 1.定义一个队列
q = queue.Queue(maxsize=-1)
formater = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")

# 2.定义日志处理器
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)  # 定义日志级别
handler.setFormatter(formater)  # 定义日志格式

# 3.监听日志队列
listener = QueueListener(q, handler)

# 4.定义异步处理器
async_handler = QueueHandler(q)
logger = logging.getLogger("test")
logger.setLevel(logging.INFO)
logger.addHandler(async_handler)

# 5.启动监听器
listener.start()

# 6.记录日志
for i in range(10):  # 模拟10次日志
    print("11111111111111111111111111")
    logger.info(f"这是第{i+1}条异步日志消息")
    time.sleep(1)

listener.stop()

3.远程日志

# 创建一个logger
    logger = logging.getLogger("remote_logger")
    logger.setLevel(logging.DEBUG)

    # 创建一个SocketHandler,用于通过TCP发送日志到远程服务器
    socket_handler = SocketHandler("localhost", DEFAULT_TCP_LOGGING_PORT)
    socket_handler.setLevel(logging.DEBUG)

    # 定义handler的输出格式
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    socket_handler.setFormatter(formatter)

    # 给logger添加handler
    logger.addHandler(socket_handler)

    # 记录一条消息
    logger.info("这是一条发送到远程服务器的日志信息")

4.其余日志类型

除了以上介绍的异步日志和远程日志外,还有其他类型的日志,比如:日志发送邮件,通过请求发送日志等等。这些日志的区别仅仅只是日志处理器的区别,我在这就不一一列举,如有需要,小伙伴们自行研究。

五、总结

本文详细介绍了Python日志模块logging的记录日志的流程、往日志中添加额外信息、自定义日志的结构、如何从文件中加载日志、父子日志等知识点和部分日志高级特性,帮助您更好地理解和使用日志记录功能。在讲解过程中有出现错误或者遗漏的地方,还请小伙伴们在评论区留言,让我们一起变得更强。