Python · 5 分钟阅读
`logging`:记录和管理日志
目录
- 1. 五个级别
- 2. 最简单的使用
- 3. 四大组件
- 4. 模块化推荐写法:每模块一个 logger
- 5. 写文件 + 按大小切分
- 6. 配置:代码 vs
dictConfigvs 配置文件 - 7. 异常日志:带 traceback
- 8. 结构化日志:JSON
- 9. 与 print 的关系
- 10. 常见问题
- 11. 小结
logging 是 Python 标准库的 日志模块。它提供多级别(DEBUG / INFO /
WARNING / ERROR / CRITICAL)、多 Handler(控制台、文件、网络…)、
结构化输出与线程安全,是写生产代码的标配。
永远用
logging而不是
1. 五个级别
| 级别 | 数值 | 使用场景 |
|---|---|---|
DEBUG |
10 | 详细诊断信息(开发时打开) |
INFO |
20 | 确认流程按预期 |
WARNING |
30 | 出现意外,但程序还能继续 |
ERROR |
40 | 更严重问题,部分功能失败 |
CRITICAL |
50 | 致命错误,程序可能无法继续 |
设置
level之后,低于该级别的日志会被丢弃。
2. 最简单的使用
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logging.debug("调试")
logging.info("启动")
logging.warning("磁盘快满了")
logging.error("数据库连接失败")
logging.critical("服务挂了")
basicConfig()只能 第一次 生效;之后修改配置需要拿 logger 自己设。
3. 四大组件
Loggers ──→ Filter ──→ Handler ──→ Formatter
入口,按名取 处理器(控制台/文件) 输出格式
按级别过滤
| 组件 | 作用 |
|---|---|
| Logger | 入口,按 __name__ 取;可设置级别 |
| Handler | 决定日志去哪里(StreamHandler / FileHandler) |
| Filter | 决定哪些日志被接收 |
| Formatter | 决定输出格式 |
4. 模块化推荐写法:每模块一个 logger
# myapp/logger.py
import logging
import sys
def get_logger(name: str = "myapp") -> logging.Logger:
logger = logging.getLogger(name)
if logger.handlers: # 防止重复添加
return logger
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# 控制台
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(formatter)
logger.addHandler(sh)
return logger
使用:
# myapp/api.py
from .logger import get_logger
log = get_logger(__name__) # __name__ = "myapp.api"
log.info("请求开始")
log.error("出错了:%s", err)
关键点:
- 每个模块用自己的 logger(按
__name__),子 logger 自动继承父 logger 的 handler 与 level。- 日志消息里 用
%s占位 而不是 f-string,logging 会自己延迟格式化。- 不要
logging.getLogger(__name__).propagate = False,除非有特别理由。
5. 写文件 + 按大小切分
from logging.handlers import RotatingFileHandler
logger = get_logger("file-app")
handler = RotatingFileHandler(
"app.log",
maxBytes=10 * 1024 * 1024, # 10 MiB
backupCount=5, # 保留 5 个历史
encoding="utf-8",
)
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
logger.addHandler(handler)
想按天切分用
TimedRotatingFileHandler("app.log", when="midnight", backupCount=7)。
6. 配置:代码 vs dictConfig vs 配置文件
6.1 代码配置(适合小项目)
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s %(name)s :: %(message)s",
datefmt="%H:%M:%S",
)
6.2 dictConfig(推荐,中大型项目)
import logging.config
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "app.log",
"maxBytes": 10 * 1024 * 1024,
"backupCount": 5,
"formatter": "default",
},
},
"loggers": {
"myapp": {"handlers": ["console", "file"], "level": "INFO"},
},
}
logging.config.dictConfig(LOGGING)
6.3 配置文件(YAML/TOML)
# logging.yaml
version: 1
formatters:
default:
format: "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
handlers:
console:
class: logging.StreamHandler
formatter: default
loggers:
myapp:
handlers: [console]
level: INFO
import logging.config, yaml
with open("logging.yaml") as f:
logging.config.dictConfig(yaml.safe_load(f))
7. 异常日志:带 traceback
try:
do_something()
except Exception:
log.exception("do_something 失败") # 自动带上 traceback
# 等价于 log.error("xxx", exc_info=True)
8. 结构化日志:JSON
要接入 ELK / Loki / CloudWatch 等系统时,把日志输出成 JSON 最好。
import json
import logging
class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
payload = {
"ts": self.formatTime(record, self.datefmt),
"level": record.levelname,
"name": record.name,
"message": record.getMessage(),
}
if record.exc_info:
payload["exc"] = self.formatException(record.exc_info)
return json.dumps(payload, ensure_ascii=False)
也可以直接用第三方库 python-json-logger /
structlog。
9. 与 print 的关系
| 情况 | 推荐 |
|---|---|
| 调试 / 快速原型 | print 也行 |
| 库代码 | 必须 logging |
| 长时间运行的服务 | 必须 logging |
| 多进程 / 跨模块 | 必须 logging |
| 临时一行打印(one-off) | print 也行 |
把 print 当日志,运行时 没法关掉,也无法分流到文件 / 网络。
10. 常见问题
basicConfig第二次调用无效? 用dictConfig或在 logger 层面加 handler。- 日志重复出现? 多半是 logger 被加了多次 handler;用
if logger.handlers: return
兜底。 - 子模块日志没显示? 父 logger 设了 handler 才会传到子;最稳妥是每个模块
getLogger(__name__)。 - 时间多了时区偏移?
Formatter加datefmt="%Y-%m-%dT%H:%M:%S%z",或自己
在 format 时datetime.now().astimezone().isoformat()。 %格式化的字符串里有%? 写%%转义。- 异步场景:
QueueHandler+QueueListener把 logging 调用扔进队列,
避免阻塞 event loop。
11. 小结
- 用
logging.getLogger(__name__)在每个模块起一个 logger。 - 消息用
log.info("...%s...", arg),不要用 f-string 提前格式化。 - 生产环境用
dictConfig/ YAML 配置,加RotatingFileHandler或集中日志系统。 - 调级别、调 handler、关闭无关 logger:
logging.getLogger("urllib3").setLevel(logging.WARNING)。