一、默认日志的使用
1. 日志等级
1.1 五个日志等级
默认情况下,是五个日志等级。
日志级别 | 功能 | 描述 |
---|---|---|
DEBUG | logging.debug() | 提供对开发人员有价值的详细信息。(调试) |
INFO | logging.info() | 提供有关程序所发生情况的一般信息。(进度通知) |
WARNING | logging.warning() | 指示应该调查一些内容。(警告) |
ERROR | logging.error() | 提醒程序中发生的意外问题。(发生错误) |
CRITICAL | logging.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.DEBUG | 10 | DEBUG |
logging.INFO | 20 | INFO |
logging.WARNING | 30 | WARNING |
logging.ERROR | 40 | ERROR |
logging.CRITICAL | 50 | CRITICAL |
调用 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
是一个 StreamHandler
,StreamHandler
的默认级别是 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()
来配置输出位置和格式化风格了,需要使用 handlers
和 formatters
。
如果一个自定义的 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
对象,并填充以下属性(部分常用属性):
属性名 | 类型 | 说明 |
---|---|---|
msg | str | 日志消息(如 "Hello" )。 |
args | tuple | 如果 msg 是格式化字符串(如 "Hello %s" ),args 存储格式化参数。 |
levelname | str | 日志级别名称(如 "INFO" 、"ERROR" )。 |
levelno | int | 日志级别的数值(如 logging.INFO 的值为 20 )。 |
pathname | str | 触发日志的代码文件路径(如 /project/main.py )。 |
filename | str | 文件名(pathname 的最后一部分,如 main.py )。 |
module | str | 模块名(如 main )。 |
lineno | int | 触发日志的代码行号。 |
created | float | 日志创建的时间戳(Unix 时间,秒级精度)。 |
msecs | float | 毫秒级时间戳(created 的小数部分)。 |
relativeCreated | float | 相对于日志系统启动的时间(毫秒)。 |
thread | int | 线程 ID(多线程场景下有用)。 |
threadName | str | 线程名称(如 "MainThread" )。 |
process | int | 进程 ID(多进程场景下有用)。 |
message | str | 最终格式化后的日志消息(由 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()
函数来设置自定义记录器的日志等级。可以使用数值或日志级别字符串来设置。
数值 | 日志级别 |
---|---|
0 | NOTSET |
10 | DEBUG |
20 | INFO |
30 | WARNING |
40 | ERROR |
50 | CRITICAL |
>>> 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 不会接收。