使用场景
在运行程序的过程中,通常需要使用设置日志信息来方便追踪程序运行状态或者是调试,也常常需要进行多次运算并将计算结果进行保存。一般来说,日志信息可以通过logging模块保存,而计算数据的记录常常是基于文本文件的读写功能实现的。如果要统一的实现两个功能,可以通过改造loguru模块进行实现。
这个实现主要是基于filter
机制和bind
方法
filter函数
使用 loguru
库进行 Python 日志记录时,可以通过定义自定义 filter
函数来收集特定的日志信息。该函数接受一个记录作为参数,并返回一个布尔值:如果返回 True
,则记录该日志;如果返回 False
,则不记录。
这个 filter
函数可以基于:
- 日志消息的内容:
record["message"]
- 级别:
record["level"]
- 或者其他任何可用的日志属性来决定是否应该记录该条日志
注意,filter
是要在添加``handler时进行配置,将
filter函数作为
handler的
filter`参数。
from loguru import logger
def important_log_filter(record):
# 只有当日志消息中包含 "重要" 关键字时,才记录该日志
return "重要" in record["message"]
# 配置 logger,添加一个 handler,仅记录通过 filter 函数筛选的日志
logger.add("important_logs.log", filter=important_log_filter)
# 使用示例
logger.info("这是一条普通日志,不会被记录")
logger.info("这是一条重要日志,将会被记录")
bind函数
bind()
方法是一个强大的功能,用于向日志记录附加额外的上下文信息。这种方法非常有用,因为它允许为日志消息添加元数据,而无需在每次记录日志时重复这些信息。
bind()
的特点是不接受一组预定义的参数,而是接受任意数量的关键字参数,这些参数随后会成为日志消息的一部分,增加了日志的描述性和可追溯性,如用户ID、请求ID或任何其他对理解或分析日志有帮助的数据。而这些字段都会添加到 record["extra"]
中,然后可以在:
-
filter
函数from loguru import logger def user_filter(record): # 假设我们只对 user_id 为 "123" 的日志消息感兴趣 return record["extra"].get("user_id") == "123" # 配置 logger,添加一个 handler,仅记录通过 user_filter 函数筛选的日志 logger.add("filtered_logs.log", filter=user_filter) # 绑定 user_id 并尝试记录几条消息 logger.bind(user_id="123").info("这条消息会被记录,因为用户ID匹配") logger.bind(user_id="456").info("这条消息不会被记录,因为用户ID不匹配")
-
format
字符串or函数中# 自定义日志格式,包括绑定的上下文信息 format = "{time} | {level} | {extra[user_id]} | {message}" # 添加一个 handler,使用上述格式 logger.add("my_log.log", format=format) # 使用绑定的 logger 记录日志 logger.info("完成了一个任务")
用户ID是通过
bind()
方法绑定到日志记录器上的。
通过record["extra"][key"]
进行使用。
bind()
方法返回一个新的 logger
实例,这个实例在其所有日志记录中自动包含了绑定的上下文信息。这意味着可以在程序的特定部分创建一个带有特定上下文的 logger
,而不会影响全局 logger
实例或其他带有不同上下文的 logger
实例。
就是说,
bind
方法不会影响原来的logger实例的上下文信息from loguru import logger # 原始 logger 实例 logger.info("这是一条没有上下文信息的日志消息") # 使用 bind() 添加上下文信息,创建一个新的 logger 实例 logger_with_context = logger.bind(user_id="123") # 使用新的 logger 实例记录消息 logger_with_context.info("这条消息包含上下文信息") # 再次使用原始 logger 实例记录消息 logger.info("这又是一条没有上下文信息的日志消息")
注意:
- 使用
loguru
的bind()
方法时,不需要重新指定日志文件路径。 - 需要在应用的不同部分添加不同的上下文信息时,可以在各个部分使用
bind()
方法,这样可以确保日志消息包含了正确的上下文信息,而不需要为每个上下文重新配置日志路径。 bind()
和.add()
在loguru
中扮演了不同的角色:.add()
用于配置日志的输出目标和格式,而bind()
用于为日志消息添加上下文信息。
赋予日志记录一个label
的属性
这里的意图是,通过一个label="data"的标签,实现重要数据和程序运行信息分离的功能
在 loguru
库中,直接给日志消息指定一个标签(如 label
属性)并不是内置的功能,但是可以通过自定义消息格式来间接实现这个目的。要达到这个目的,需要联合filter
和bind
-
只使用 Filter 函数动态添加标签
通过自定义函数,确实可以达到动态添加
label
属性的方法,但是这种方法比较低效,因为要通过判断reord的一些属性来实现,另外就是不方便手动控制:from loguru import logger def tag_logger(record): # 假设我们根据消息的内容动态添加标签 if "错误" in record["message"]: record["extra"]["label"] = "错误" else: record["extra"]["label"] = "通用" # 配置 logger,添加一个 handler,并使用自定义的 filter 函数 logger.add("tagged_logs.log", filter=tag_logger, format="{time} | {level} | {extra[label]} | {message}") # 发送日志消息 logger.info("这是一条普通消息") logger.error("这是一条包含错误的消息")
-
通过
bind
添加上下文信息,并进行显示格式化。这个方法,添加label属性,就比较方便,只要引用
bind
后的logger实例就行。from loguru import logger # 定义一个自定义的日志格式,其中包含了消息的标签 log_format = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{extra[label]}</cyan> | <white>{message}</white>" # 配置 logger,使用自定义的格式 # 注意:需要确保所有的日志消息都添加了相应的 `label`,否则会抛出 KeyError logger.add("labeled_logs.log", format=log_format, level="INFO") # 发送一条带有标签的日志消息 logger.bind(label="特别").info("这是一条带有标签的日志")
在这个例子中,通过
.bind()
方法为日志消息附加了额外的上下文信息(label
标签),然后在自定义日志格式中引用了这个标签,并设定格式。
实际案例代码
-
bind和
filter
联合使用。这样组合,能完成的功能更丰富。也很实现运行记录和计算数据的分别记录器。
from loguru import logger import sys def only_trace(record): ''' 只保存程序的运行记录 :param record: :return: ''' return "data" not in record["extra"] def only_data(record): return "data" in record["extra"] # 移除默认的控制台输出 logger.remove() # 自定义日志格式 log_format = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | <cyan>{name}</cyan>: <cyan>{line}</cyan> - <level>{message}</level>" # 添加控制台handler,使用自定义格式 logger.add(sys.stdout, format=log_format) logger.add("./log/app_trace.log", format=log_format,filter=only_trace, level="DEBUG") data_format = "{time:YYYY-MM-DD HH:mm:ss}\t{message}" # 写模式,每次程序启动并添加 handler 时,app_data.csv 将会被新内容覆盖。 logger.add("./log/app_data.csv", format=data_format,filter=only_data, level="DEBUG",mode="w") data_logger=logger.bind(data=True) def get_logger(): return logger def get_datalogger(): return data_logger
注意,这里没有添加``label标签,而是直接给
data=True
这样的上下文,方便filter函数的构造