文章目录
- 快速入门
- getLogger
- logging.basicConfig
- [日志冒泡 propagation](#日志冒泡 propagation)
- [在同一个 Python 进程里,logging.getLogger("名称") 是"按名字全局唯一的单例"。进程间不共享](#在同一个 Python 进程里,logging.getLogger("名称") 是“按名字全局唯一的单例”。进程间不共享)
-
- [防止 handler 重复添加](#防止 handler 重复添加)
- 函数属于它定义的模块,不属于调用它的文件
-
- [不只是 log,函数体里用到的所有"全局变量"都是先回自己定义所在的模块去找的,而不是去调用它的那个文件里找。](#不只是 log,函数体里用到的所有“全局变量”都是先回自己定义所在的模块去找的,而不是去调用它的那个文件里找。)
- 只打印自己python文件的日志,可以多个进程
快速入门
getLogger
1、通过执行 logger = getLogger("名称") 创建一个该名称的日志记录器然后调用日志记录器的 debug(), info(), warning(), error() 和 critical() 方法来使用日志记录功能。
2、 logger = getLogger("") 或 logger = getLogger() 创建的是root日志记录器。
root logger 默认:level = WARNING,handlers = []
但 logging 模块有个 lastResort 兜底 handler,所以没配置控制台也能在控制台看到 WARNING 及以上的输出。
3、
python
import logging
logging.info()
logging.debug()
# 等价于
logging.getLogger().info("hello") # 注意:这里用的是"root logger"
# 或
logging.getLogger("").info("hello")
也就是说:
logging.info() / logging.debug() / logging.error() 这些函数
本质上就是 调用 root logger 的对应方法。
logging.basicConfig
python
# 只配置了控制台(准确说是标准错误流 stderr),默认写到了stderr通道
logging.basicConfig(level=logging.INFO)
# 只配置了文件
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
basicConfig 是在给 整个 logging 系统的"老大"------root 日志记录器 做初始化配置的,同时顺带创建一个默认的 Handler。
你这段代码等价于在说:
"把 root logger 的等级设成 DEBUG,并且给它加一个写文件的 handler,日志都按这个格式写到 app.log 里。"




stderr stdout stdin
不管是 shell 还是 Python,stdin / stdout / stderr 都是 3 条"管道"(标准流)。
具体往哪条管道走,不是看内容是对还是错,而是看程序怎么写的。
谁写到 stdout / stderr,不是看"内容像不像错误",而是看它到底往哪个文件描述符写:1(stdout)还是 2(stderr)。
在 shell / Linux 里,每个进程有三个默认"文件描述符":
0 👉 stdin(标准输入)
1 👉 stdout(标准输出)
2 👉 stderr(标准错误)
重定向的时候:
或 1> 是 重定向 stdout
2> 是 重定向 stderr
stdin/stdout/stderr 是 3 条独立的通道;
✅ 到底走哪条,是程序写输出时选了哪个流;



不管是 Python 还是 shell,本质都是"进程有 0/1/2 三个流",默认情况下:stdin 从终端读,stdout、stderr 都指向终端 → 你都能在屏幕上看到

✅ 默认情况下:控制台"接"着 stdout 和 stderr,所以你都能看到
❌ 但 stdout/stderr 仍然是两个不同的通道,只是默认连到了同一个"屏幕设备"上而已。



不管是 Python 还是 shell,本质都是"进程有 0/1/2 三个流"
默认情况下:
stdin 从终端读
stdout、stderr 都指向终端 → 你都能在屏幕上看到

怎么从流里读出来
程序自己怎么从流里读?(读 stdin)
别的东西怎么拿到这个程序写到 stdout / stderr 里的内容?(重定向、管道、捕获)





写到流里 = 写到一条"水管"里;
谁能拿出来,看谁在 另一头接这条管子:
程序自己:从 stdin 读
Shell:用 >、2>、| 把 stdout / stderr 接走
其他程序:用诸如 subprocess 之类的 API 捕获输出
配置文件和控制台


日志冒泡 propagation
日志从子 logger 一路往父 logger 传,最后传到 root 的过程,就像气泡往上浮一样。Python logging 里官方叫 propagation。






在同一个 Python 进程里,logging.getLogger("名称") 是"按名字全局唯一的单例"。进程间不共享



防止 handler 重复添加




函数属于它定义的模块,不属于调用它的文件
函数回家找"自己文件里的全局变量",不会跨文件去你调用它的地方要东西。





不只是 log,函数体里用到的所有"全局变量"都是先回自己定义所在的模块去找的,而不是去调用它的那个文件里找。

只打印自己python文件的日志,可以多个进程
python
# logger_utils.py
import logging
from logging.handlers import TimedRotatingFileHandler
from pathlib import Path
from typing import Optional
APP_LOGGER_NAME = "ai_build" # 你项目的总 logger 名字,随便起一个
def setup_app_logger(log_file: Optional[str] = None, level: int = logging.INFO):
"""
配置业务日志记录器,只打印自己项目相关的日志。
"""
logger = logging.getLogger(APP_LOGGER_NAME)
logger.setLevel(level)
logger.propagate = False # 关键:不要往 root 传了
# 避免重复添加处理器
if logger.handlers:
return
formatter = logging.Formatter(
"%(asctime)s - %(name)s - [%(levelname)s] "
"[%(threadName)s] %(filename)s:%(lineno)d --- %(message)s"
)
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# 文件处理器(如果指定了文件)
if log_file:
Path(log_file).parent.mkdir(parents=True, exist_ok=True)
file_handler = TimedRotatingFileHandler(
log_file,
when="midnight",
interval=1,
backupCount=7,
encoding="utf-8",
)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
def get_logger(name: str) -> logging.Logger:
"""
业务代码里用这个拿 logger,
日志名会是: ai_build.xxx
"""
return logging.getLogger(f"{APP_LOGGER_NAME}.{name}")
python
"""
然后入口脚本设置自己的日志文件。这样从入口调到的所有python文件都写到了该日志文件中,因为非如入口文件是get_logger(__name__),返回logging.getLogger(f"{APP_LOGGER_NAME}.{name}"),然后也没配置自己的handle,所有冒泡到父亲APP_LOGGER_NAME
"""
from logger_utils import setup_app_logger, get_logger
setup_app_logger("logs/ci.log")
log = get_logger(__name__)
log.info("只会打印自己项目的日志")
另一个进程的脚本里就换下日志文件就好





