如何将 httpx 日志路由到主程序的自定义命名空间?
结论
标准库 logging 的配置无法把第三方库使用的 logger 名称改成 program.network.httpx。库通常通过 logging.getLogger(__name__) 获取 logger,名称由库自身的模块名决定,例如 httpx 或 httpcore。logger 只沿实际名称的层级传播:httpx.client 会向 httpx 传播,不会传播到 program.network。
你可以让 httpx、httpcore 的日志使用同一个 handler,也可以在根 logger 上按名称过滤。这样可以集中处理日志,但 LogRecord.name 仍保持原值,这些记录也不会成为 program.network 的子 logger。
将库日志交给同一个 handler
在主程序中使用 dictConfig 为相关 logger 配置 handler。将 disable_existing_loggers 设为 False,以免禁用其他已创建的 logger:
import logging.config
logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s %(name)s %(levelname)s: %(message)s",
},
},
"handlers": {
"network": {
"class": "logging.StreamHandler",
"formatter": "standard",
},
},
"loggers": {
"httpx": {
"handlers": ["network"],
"level": "INFO",
"propagate": False,
},
"httpcore": {
"handlers": ["network"],
"level": "INFO",
"propagate": False,
},
},
})
import httpx
if __name__ == "__main__":
httpx.get("https://www.example.com")
logger 配置会作用于对应名称及其子 logger。设为 propagate: False 后,记录不会继续传到根 logger,因此可以避免重复处理。如果希望根 logger 也处理这些记录,可以移除该设置或将其设为 True,并留意是否出现重复输出。日志级别可按需调整。
示例配置了 httpx 和 httpcore。实际请求会产生哪些 logger 名称,取决于使用的库。可以先查看输出中的 %(name)s,再补充相应配置。
按自定义规则分流
你可以把 handler 配置在根 logger 上,再通过 filter 根据 record.name 选择要处理的记录。这样可以设置路由规则,记录名称仍是库原本的名称。也可以编写自定义 handler,对选中的记录进行额外处理;这也不会改变 logger 的父子关系。
如果只想让自己写的模块使用 program.network 层级,可以在这些模块中显式创建对应名称的 logger:
import logging
logger = logging.getLogger("program.network")
logger.info("request started")
要让 program.network 接收第三方库的记录,需要在 httpx、httpcore 等实际 logger 上配置 handler,或使用自定义路由逻辑。只在 program.network 上添加 handler,无法接收到名称为 httpx 的记录。
注意事项
logging.getLogger("program.network.httpx")创建的是一个独立命名的 logger,不会自动接管httpxlogger。- 格式化器显示自定义名称,只会改变日志文本的展示,不会改变记录的
name或传播路径。 - 如果同时在库 logger 和根 logger 上添加相同的输出 handler,同一条记录可能会输出多次。
备注:内容仅供参考。
