python 中 logging 模块的使用详解

一、默认日志的使用

1. 日志等级

1.1 五个日志等级

默认情况下,是五个日志等级。

日志级别功能描述
DEBUGlogging.debug()提供对开发人员有价值的详细信息。(调试)
INFOlogging.info()提供有关程序所发生情况的一般信息。(进度通知)
WARNINGlogging.warning()指示应该调查一些内容。(警告)
ERRORlogging.error()提醒程序中发生的意外问题。(发生错误)
CRITICALlogging.critical()发生了严重错误,并且可能已使应用程序崩溃。(崩溃)

默认等级为 warning,日志记录模块只会记录严重性级别为 WARNING 或更高的消息,情况如下。

>>> logging.debug("This is a debug message")

>>> logging.info("This is an info message")

>>> logging.warning("This is a warning message")
WARNING:root:This is a warning message

>>> logging.error("This is an error message")
ERROR:root:This is an error message

>>> logging.critical("This is a critical message")
CRITICAL:root:This is a critical message

1.2 调整日志级别

使用 logging 模块中的 basicConfig() 来调整日志等级.

>>> import logging

>>> logging.basicConfig(level=logging.DEBUG) #调整等级为 debug 
>>> logging.debug("This will get logged.")	

DEBUG:root:This will get logged.

参数部分:可以使用变量名,数值,字符串。

变量名数值字符串值
logging.DEBUG10DEBUG
logging.INFO20INFO
logging.WARNING30WARNING
logging.ERROR40ERROR
logging.CRITICAL50CRITICAL

调用 basicConfig() 来配置根 Logger 仅在之前未配置根 Logger 时有效。如果从未调用 basicConfig(),则所有日志记录函数都会自动调用 basicConfig(),而不带参数。因此,例如,一旦你调用 logging.debug(),你将无法再使用 basicConfig() 配置根 Logger

多次调用 basicConfig() 只有第一次生效,后续调用会被忽略。

   logging.debug("Hello, debug!")   #无法输出,在这里调用时已经进行了配置,后面的配置不管用了
   logging.warning("Hello, Warning!")

   logging.basicConfig(level=logging.DEBUG)		#设置等级
    
   logging.debug("Hello, debug!")   #同样,无法输出

2. 格式化输出

默认情况下,日志包含日志级别、记录器的名称和日志消息。但是,可以通过利用 basicConfig()format 参数使用其他数据来增强日志。该参数接受一个字符串,该字符串可以包含许多预定义属性

2.1 风格

Python 的 logging 模块默认使用 % 风格的格式化(如 %(levelname)s):

>>  logging.basicConfig(format="{levelname}::{name} -> {message}")
>>  logging.warning("Hello, Warning!")

WARNING -> root -> Hello, Warning!

使用 style="{" 支持 {} 格式化:

>> logging.basicConfig(format="{levelname}:{name}:{message}", style="{")

2.2 添加其他数据

①添加时间信息:
>> logging.basicConfig(format = "{asctime} - {levelname} - {message}",style = "{",datefmt = "%Y-%m-%d %H:%M")			#添加时间戳
>> logging.warning("Hello, Warning!")	

 2025-07-18 13:02 - WARNING - Hello, Warning!
②添加变量数据:
>>> logging.basicConfig(
...     format="{asctime} - {levelname} - {message}",
...     style="{",
...     datefmt="%Y-%m-%d %H:%M",
...     level=logging.DEBUG,
... )

>>> name = "xxx"
>>> logging.debug(f"{name=}")	#使用 f-strings 格式
>>> logging.debug("name=%s", name)	#使用 % 格式,推荐
  • f-strings 是“急切求值”(eagerly evaluated)

    • 当你使用 f-strings(如 f"{variable}")时,无论日志是否实际记录(例如日志级别是 INFO 但当前是 DEBUG 级别),Python 都会立即执行字符串插值(计算变量值并拼接字符串)。

    • 这意味着,如果日志消息未被处理(例如被 logging.INFO 过滤掉),CPU 仍然会浪费资源在无用的字符串拼接上

  • % 格式化是“惰性求值”(lazily evaluated)

    • logging 模块原生支持 % 风格格式化(如 "Error: %(code)d"),但它的插值是延迟执行的:只有当日志消息真正需要记录时(例如通过 logging.info() 且级别匹配),才会进行字符串插值。

    • 这对性能更友好,尤其是大量低级别日志(如 DEBUG 日志)的场景。

③添加堆栈信息:

exc_info 参数:

>>> import logging
>>> logging.basicConfig(
...     format="{asctime} - {levelname} - {message}",
...     style="{",
...     datefmt="%Y-%m-%d %H:%M",
... )

>>> donuts = 5
>>> guests = 0
>>> try:
...     donuts_per_guest = donuts / guests
... except ZeroDivisionError:
...     logging.error("DonutCalculationError", exc_info=True)

exc_info是一个参数,当设置为True时,logging.error()会自动捕获当前上下文中的异常(sys.exc_info()),并将以下内容附加到日志中:

  • 异常类型(如 ZeroDivisionError)。
  • 异常消息(如 division by zero)。
  • 完整的堆栈跟踪(从异常触发点到日志记录点的调用链)。

如果设置 exc_info=False,日志只会记录你显式传入的消息(如 "DonutCalculationError")。

logging.exception() 函数:

>>> try:
...     donuts_per_guest = donuts / guests
... except ZeroDivisionError:
...     logging.exception("DonutCalculationError")

调用logging.exception() 就像调用logging.error(exc_info=True) 一样,logging.exception() 的级别也是 error

3. 输出到文件

必须提供文件路径。设置编码和打开文件的模式

>>> logging.basicConfig(
...     filename="app.log",	#文件路径
...     encoding="utf-8",	#编码方式
...     filemode="a",		#文件打开方式
...     format="{asctime} - {levelname} - {message}",
...     style="{",
...     datefmt="%Y-%m-%d %H:%M",
... )

>>> logging.warning("Save me!")

二、自定义记录器

上面我们直接调用logging.debug()logging.info() 等方法时,实际上使用的是 logging 模块内部维护的根 logger(root logger),这是一个 logging.Logger 类的实例,代表一个独立的日志记录器

直接使用根 Logger 的缺点是配置可能很麻烦,因为依赖于单个 basicConfig()。对于较大的项目,我们需要更大的灵活性来满足日志记录需求。

0. 根日志记录器

>>> root_logger = logging.getLogger()  # 获取根日志记录器

>>> print(root_logger)		# 查看根日志记录器的相关信息
    
<RootLogger root (WARNING)>

根日志记录器 root logger 的默认等级是 warning

根日志记录器的默认 Handler 是一个 StreamHandlerStreamHandler 的默认级别是 NOTSET(0),这意味着它不会独立过滤日志级别,而是将决策权交给其所属的 Logger。

1. 实例化

1.1 实例化 Logger

您可以通过调用 logging.getLogger()函数并为 Logger 提供名称来实例化 Logger 类:

>>> import logging
>>> logger = logging.getLogger(__name__)
>>> logger.warning("Look at my logger!")
Look at my logger!

这里使用 ``name**,那记录器(logger)的名称就是当前模块的 name` 属性的值**。

关于 __name__这个属性值用来标识当前文件:

  • 当该文件直接作为脚本文件运行时,这个属性值为 __main__
  • 当该文件作为模块被导入时,这个属性值就是当前的模块名module_name

使用__name__的好处:

  • 避免全局命名冲突(每个模块有自己的记录器)。
  • 支持日志层级(如 parent.child 模块的记录器会继承 parent 的配置)。
发送日志记录:

可以直接使用对应的等级的函数:

def test():
    logger = logging.getLogger(__name__)

    logger.debug("debug")
    logger.info("info")
    logger.warning("warning")
    logger.error("error")
    logger.fatal("fatal")

也可以使用 Logger 的 log() 函数:

>>> logger.log(30, "30 lever")		#logger.log(level, msg) 
查看对象信息:
>>> logger = logging.getLogger(__name__)
>>> print(logger)

<Logger __main__ (WARNING)> 	#一个 Logger对象,记录器名字是 __main__,日志等级是 warning
print("Logger name:", logger.name)  #可以通过 Logger 类中的 name 成员变量查看该记录器的名称

1.2 实例化 Handlers

自定义记录器无法通过调用 .basicConfig() 来配置输出位置和格式化风格了,需要使用 handlersformatters

如果一个自定义的 Logger 没有设置 Handler,它会将日志事件“冒泡”给它的父 Logger(通常是根日志记录器 root logger)处理。如果父 Logger 也没有 Handler,日志事件可能会被丢弃。

创建的 Logger 实例可以具有一个或多个 handlers。这意味着可以在日志生成时将其发送到多个位置。

实例化:
>>> import logging
>>> logger = logging.getLogger(__name__)

>>> console_handler = logging.StreamHandler()	#创建一个控制台流对象
>>> file_handler = logging.FileHandler("app.log", mode="a", encoding="utf-8") #创建一个文件流对象
查看对象信息:
>>> console_handler = logging.StreamHandler()
>>> print(console_handler)

<StreamHandler <stderr> (NOTSET)>

>>> file_handler = logging.FileHandler("app.log", mode="a", encoding="utf-8")
>>> print(file_handler)

<FileHandler D:\code_python\learning\app.log (NOTSET)>
添加到 logger 对象中:
>>> logger.addHandler(console_handler)
>>> logger.addHandler(file_handler)

>>> logger.warning("Watch out!")

对于 StreamHandler,日志将写入标准错误流 (stderr) 流,这是 Python 默认使用的输出通道。

运行第三条语句时,会将日志信息写入到 stderr 和 文件中。

查看 logger 关联的 handlers:

可以在记录器 Logger 中的 handlers 属性中查看,该属性只读,只能通过 addHandler()removeHandler() 来修改。

>>> print(logger.handlers)

[
  <StreamHandler <stderr> (NOTSET)>,
  <FileHandler /Users/RealPython/Desktop/app.log (NOTSET)>
]
其余的 handlers:

日志记录模块附带了许多用于特定目的的 handy handlers。例如,RotatingFileHandler(在达到文件大小限制后创建一个新的日志文件)或TimedRotatingFileHandler(可以使用它为定义的时间间隔创建新的日志文件)。

1.3 实例化 Formatters

Logger 对象本身不能直接添加 formatter,只能通过 handler 来添加,不同的 handler 可以添加不同的 fomatter

>>> import logging
>>> logger = logging.getLogger(__name__)	#实例化 logger

>>> console_handler = logging.StreamHandler()	#实例化 handlers
>>> file_handler = logging.FileHandler("app.log", mode="a", encoding="utf-8")

>>> formatter = logging.Formatter(				#实例化 formatter
...    "{asctime} - {levelname} - {message}",	
...     style="{",
...     datefmt="%Y-%m-%d %H:%M",
... )

>>> logger.addHandler(console_handler)			# logger 添加 handlers

>>> console_handler.setFormatter(formatter)		# handlers 添加 formatter

>>> logger.warning("Stay calm!")
2024-07-22 15:58 - WARNING - Stay calm!

我们可以给不同的 handler 添加不同的 formatter,这样两日志记录就会呈现出不同的格式。

通过这一点,可以灵活设置对于输出到不同位置的日志设置各自的日志等级。比如,可以在控制台中显示调试消息,将更严重的日志级别保存在文件中。

1.4 日志记录 LogRecord

所有日志事件(如 logger.debug()logger.error())最终都会被转换为 LogRecord 对象。

LogRecord 是一个核心类,用于封装单条日志记录的所有信息(如时间戳、日志级别、消息、调用位置等)。Logger 创建 LogRecord,然后传递给 Handler 处理。

关键属性

当调用 logger.info("Hello") 时,logging 模块会创建一个 LogRecord 对象,并填充以下属性(部分常用属性):

属性名类型说明
msgstr日志消息(如 "Hello")。
argstuple如果 msg 是格式化字符串(如 "Hello %s"),args 存储格式化参数。
levelnamestr日志级别名称(如 "INFO""ERROR")。
levelnoint日志级别的数值(如 logging.INFO 的值为 20)。
pathnamestr触发日志的代码文件路径(如 /project/main.py)。
filenamestr文件名(pathname 的最后一部分,如 main.py)。
modulestr模块名(如 main)。
linenoint触发日志的代码行号。
createdfloat日志创建的时间戳(Unix 时间,秒级精度)。
msecsfloat毫秒级时间戳(created 的小数部分)。
relativeCreatedfloat相对于日志系统启动的时间(毫秒)。
threadint线程 ID(多线程场景下有用)。
threadNamestr线程名称(如 "MainThread")。
processint进程 ID(多进程场景下有用)。
messagestr最终格式化后的日志消息(由 Formatter 处理后生成)。
LogRecord 的实际应用
(1) 自定义 Formatter 时访问 LogRecord 属性

Formatter 通过 format(record) 方法将 LogRecord 转换为字符串,可以自定义格式:

import logging
 
class CustomFormatter(logging.Formatter):
    def format(self, record):
        # 自定义日志格式(例如添加线程名和模块名)
        return f"[{record.threadName}] {record.module}:{record.lineno} - {record.levelname} - {record.msg}"
 
logger = logging.getLogger("custom_logger")
logger.setLevel(logging.DEBUG)
 
handler = logging.StreamHandler()
handler.setFormatter(CustomFormatter())
logger.addHandler(handler)
 
logger.info("This is a custom log message")

输出

[MainThread] __main__:15 - INFO - This is a custom log message
(2) 过滤日志时检查 LogRecord 属性

Filter 类可以通过 filter(record) 方法基于 LogRecord 属性决定是否记录日志:

class LevelFilter(logging.Filter):
    def filter(self, record):
        # 只允许 ERROR 及以上级别的日志通过
        return record.levelno >= logging.ERROR
 
logger = logging.getLogger("filter_logger")
logger.addFilter(LevelFilter())
 
logger.debug("Debug message")  # 不会记录
logger.error("Error message")   # 会记录
(3) 修改 LogRecord 属性(高级用法)

可以在日志处理前动态修改 LogRecord 的属性(例如,添加额外信息):

class AddContextFilter(logging.Filter):
    def filter(self, record):
        record.user = "Alice"  # 动态添加属性
        return True
 
logger = logging.getLogger("context_logger")
logger.addFilter(AddContextFilter())
 
class CustomFormatter(logging.Formatter):
    def format(self, record):
        return f"{record.user} - {record.msg}"
 
handler = logging.StreamHandler()
handler.setFormatter(CustomFormatter())
logger.addHandler(handler)
 
logger.info("Logged in")  # 输出: Alice - Logged in

2. 设置日志等级

Logger 和 Handler 都可以设置日志等级。

  • Logger 的日志等级决定了哪些日志事件允许进入该 Logger 的处理流程。如果某条日志记录的日志等级小于 Logger 的日志等级,该记录会被直接丢弃,如果日志记录的等级大于等于 Logger 的日志等级,该条日志记录就会传递给该 Logger 关联的所有的 handler

  • Handler 的日志等级决定了该 Handler 是否处理传入的日志事件,Handler 的默认等级是 NOTSET(即不过滤,处理所有日志事件)。每个 Handler 会再次检查日志事件的 level 是否 ≥ 自己的等级,是的话就处理,否则就忽略。

可以在 Logger 本身上定义允许的最低日志级别,并让每个 handler 决定其最低日志级别会很有帮助。

2.1 Logger 对象的等级

默认日志等级

当你创建一个自定义的 Logger 对象时,如果没有显式设置它的日志级别(通过 logger.setLevel()),它的默认级别是 0(即 logging.NOTSET)。NOTSET 是一个特殊级别,表示“未设置”。

如果一个日志记录器的级别是 NOTSET,它会继承父日志记录器的有效级别,记录器会根据有效级别过滤日志信息。

>>> import logging
>>> logger = logging.getLogger(__name__)

>>> print(logger.level)  # 输出: 0 (NOTSET)
>>> print(logger.getEffectiveLevel())  # 输出: 30 (WARNING,继承自根记录器)

0
30

上面的自定义记录器 logger 继承自根记录器,且它的日志等级为 NOTSET,所以使用父日志记录器的日志等级,根记录器的默认等级为 WARNING

设置日志等级

使用 Logger 的 setLevel() 函数来设置自定义记录器的日志等级。可以使用数值或日志级别字符串来设置。

数值日志级别
0NOTSET
10DEBUG
20INFO
30WARNING
40ERROR
50CRITICAL
>>> logger.setLevel(10)

>>> logger.setLevel("INFO")

2.2 Handler 对象的等级

如果一个自定义的 Logger 没有设置 Handler,它会将日志事件“冒泡”给它的父 Logger(通常是根日志记录器 root logger)处理。如果父 Logger 也没有 Handler,日志事件可能会被丢弃。

默认日志等级
>>> console_handler = logging.StreamHandler()

>>> print(console_handler.level)  # 输出: 0 (NOTSET)

StreamHandler 的默认级别是 NOTSET(0),这意味着它不会独立过滤日志级别,而是将决策权交给其所属的 Logger。

设置日志等级
>>> console_handler.setLevel("DEBUG")

3. 过滤日志

大多数情况下,可以使用特定的 handler 收集特定日志级别及以上的所有日志记录。但是,在某些情况下,以不同的方式处理特定日志级别的消息可能是有意义的。这时 Filter 就可以派上用场了。

Logger 和 Handler 都可以添加 Filter。

  • Logger 的 Filter 会在日志记录传递到 Handler 之前进行初步过滤。如果 Logger 的 Filter 返回 False,日志记录会被直接丢弃,不会传递给任何 Handler,如果为 true,日志记录传给所有 Handler。

  • Handler 的 Filter 会在日志记录通过 Logger 的初步过滤后,进一步决定是否处理该记录。如果 Handler 的 Filter 返回 False,该 Handler 会忽略这条日志,但其他 Handler 仍可能处理它。

3.1 创建过滤器:

创建过滤器的三种方法如下。

方法 1:继承 logging.Filter 并重写 .filter()
import logging

class LoggerFilter(logging.Filter):
    def filter(self, record):
        return record.levelno >= logging.INFO  # 只允许 INFO 及以上级别

logger = logging.getLogger("my_logger")
logger.addFilter(LoggerFilter())  # 添加到 Logger 上
logger.setLevel(logging.DEBUG)
import logging

class LevelFilter(logging.Filter):
    def __init__(self, min_level, max_level):
        super().__init__()
        self.min_level = min_level
        self.max_level = max_level

    def filter(self, record):
        return self.min_level <= record.levelno <= self.max_level

# 使用过滤器
logger = logging.getLogger("filter_logger")
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler()
handler.addFilter(LevelFilter(logging.WARNING, logging.ERROR))  # 只允许 WARNING 和 ERROR
logger.addHandler(handler)

logger.debug("Debug message")  # 被过滤
logger.warning("Warning message")  # 记录
logger.error("Error message")  # 记录
方法 2:定义一个包含 .filter() 方法的类

不需要继承 logging.Filter,只要类包含 .filter() 方法即可。

import logging

class ContextFilter:
    def filter(self, record):
        # 只允许包含 "important" 的日志
        return "important" in record.msg.lower()

# 使用过滤器
logger = logging.getLogger("context_logger")
logger.setLevel(logging.INFO)

handler = logging.StreamHandler()
handler.addFilter(ContextFilter())  # 添加自定义过滤器
logger.addHandler(handler)

logger.info("This is a normal message")  # 被过滤
logger.info("This is an IMPORTANT message")  # 记录
方法 3:直接使用可调用对象(函数)

任何可调用对象(如函数、lambda)只要接受 LogRecord 并返回 True/False,都可以作为过滤器。

使用普通函数
import logging

def user_filter(record):
    # 只允许用户 "admin" 的日志
    return hasattr(record, "user") and record.user == "admin"

logger = logging.getLogger("user_logger")
logger.setLevel(logging.INFO)

handler = logging.StreamHandler()
handler.addFilter(user_filter)  # 添加函数过滤器
logger.addHandler(handler)

# 动态添加属性(模拟日志记录时的上下文)
class AddUserFilter(logging.Filter):
    def filter(self, record):
        record.user = "admin"  # 添加 user 属性
        return True

logger.addFilter(AddUserFilter())  # 先添加属性,再过滤

logger.info("Admin action")  # 记录
logger.info("Guest action")  # 被过滤(因为 user 不是 "admin")
使用 lambda
import logging

# 只允许 ERROR 及以上级别的日志
handler = logging.StreamHandler()
handler.addFilter(lambda record: record.levelno >= logging.ERROR)

logger = logging.getLogger("lambda_logger")
logger.addHandler(handler)

logger.warning("Warning message")  # 被过滤
logger.error("Error message")  # 记录

4. propagate 属性

propagate 是 Logger 对象的一个属性,用于控制日志事件是否向父 Logger 传递(即是否允许“日志冒泡”)。默认情况下,propagate=True,表示日志事件会向上传递;如果设置为 False,则日志事件仅由当前 Logger 处理,不会传递给父 Logger。

4.1 propagate = True

import logging

# 父 Logger 配置
parent_logger = logging.getLogger("parent")
parent_logger.setLevel(logging.DEBUG)

parent_handler = logging.StreamHandler()
parent_handler.setFormatter(logging.Formatter("PARENT: %(message)s"))

parent_logger.addHandler(parent_handler)

# 子 Logger(未设置 propagate=False)
child_logger = logging.getLogger("parent.child")
child_logger.setLevel(logging.DEBUG)

child_handler = logging.StreamHandler()
child_handler.setFormatter(logging.Formatter("CHILD: %(message)s"))

child_logger.addHandler(child_handler)

# 记录日志
child_logger.info("This will be handled by both parent and child!")

输出

CHILD: This will be handled by both parent and child!  # 子 Logger 的 Handler 处理
PARENT: This will be handled by both parent and child! # 父 Logger 的 Handler 也处理(重复!)

问题:同一条日志被记录了两次(一次由子 Logger 处理,一次由父 Logger 处理)。

4.2 propagate=False

child_logger.propagate = False  # 禁止日志冒泡
 
# 记录日志
child_logger.info("This will be handled ONLY by child!")

输出

CHILD: This will be handled ONLY by child!  # 仅子 Logger 的 Handler 处理

日志仅由子 Logger 处理,父 Logger 不会接收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值