1. 什么是日志? #
- 日志(Logging)记录程序运行过程中的信息,用于调试、监控和问题追踪。
- 相比
print(),日志可以按级别过滤、写入文件、统一格式,适合正式项目。
- 用户报告 bug 时,日志能告诉你程序执行到哪一步、发生了什么错误、当时的上下文数据。
logging 是 Python 标准库,直接 import logging 即可,无需安装。
| 用途 |
示例 |
| 调试 |
记录变量值、执行路径 |
| 监控 |
记录启动、连接、请求状态 |
| 审计 |
记录用户登录、数据修改 |
| 排错 |
记录异常堆栈,定位崩溃原因 |
2. 日志级别 #
- 日志分 5 个级别,从低到高:DEBUG → INFO → WARNING → ERROR → CRITICAL。
- 设置级别后,只记录该级别及以上的日志,低于该级别的会被忽略。
- 开发环境通常用
DEBUG 或 INFO,生产环境用 INFO 或 WARNING。
- 级别本质是数字(10/20/30/40/50),数字越大越严重。
| 级别 |
数值 |
何时使用 |
| DEBUG |
10 |
开发调试,详细内部信息 |
| INFO |
20 |
正常运行状态 |
| WARNING |
30 |
潜在问题,程序仍可运行 |
| ERROR |
40 |
功能出错 |
| CRITICAL |
50 |
严重错误,可能崩溃 |
import logging
logging.basicConfig(level=logging.WARNING)
logging.debug("不会显示")
logging.info("不会显示")
logging.warning("会显示")
logging.error("会显示")
3. 快速上手:basicConfig #
logging.basicConfig() 是最快的配置方式,适合学习和小脚本。
- 可设置
level(级别)、format(格式)、filename(输出到文件)。
- 默认 logger 名为
root,日志输出到控制台。
- 项目变大后应改用自定义 Logger(见下文),basicConfig 只适合简单场景。
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logging.info("程序启动")
logging.warning("内存使用率较高")
logging.error("连接失败")
常用格式占位符:
| 占位符 |
含义 |
%(asctime)s |
时间戳 |
%(levelname)s |
级别名 |
%(name)s |
logger 名称 |
%(message)s |
日志内容 |
%(filename)s |
源文件名 |
%(lineno)d |
行号 |
输出到文件:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
filename="app.log",
filemode="a",
encoding="utf-8",
)
4. 核心概念 #
- 日志系统由三个组件组成:Logger(记录器)→ Handler(处理器)→ Formatter(格式器)。
- Logger 是入口,通过
logger.info() 等方法记录日志,可设置级别。
- Handler 决定输出目标:控制台(
StreamHandler)、文件(FileHandler)等。
- Formatter 决定日志显示格式,附加在 Handler 上。
logger.info("消息")
→ Logger 检查级别
→ Handler 用 Formatter 格式化
→ 输出到控制台/文件
5. 自定义 Logger #
- 项目中推荐创建自己的 Logger,而不是直接用默认的 root logger。
- 每个模块用
logger = logging.getLogger(__name__) 创建,日志中可看到来源模块。
- 通过
addHandler() 添加处理器,setFormatter() 设置格式。
- 配置通常在程序入口做一次,各模块只获取 logger 并使用。
import logging
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
))
logger.addHandler(handler)
logger.info("程序启动")
6. 同时输出到控制台和文件 #
- 实际项目通常需要:控制台显示 INFO 及以上,文件记录 DEBUG 及以上的完整日志。
- 给同一个 Logger 添加多个 Handler 即可,每个 Handler 可设不同级别和格式。
- 文件 Handler 建议加
encoding="utf-8",避免中文乱码。
- 这是项目中最常用的日志配置模式。
import logging
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(console)
logger.addHandler(file_handler)
logger.debug("只在文件中可见")
logger.info("控制台和文件都可见")
7. 记录异常 #
- 程序出错时务必记录完整堆栈,否则无法定位问题。
logger.exception("消息") 在 except 块中使用,自动附加堆栈跟踪。
- 等价于
logger.error("消息", exc_info=True)。
- 只在 except 块中用
exception(),普通错误用 error() 即可。
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
def divide(a, b):
return a / b
try:
divide(10, 0)
except Exception:
logger.exception("除法运算出错")
8. 日志轮转 #
- 日志文件持续增长会影响性能和磁盘空间,
RotatingFileHandler 可按大小自动切分。
maxBytes 设置单文件上限,backupCount 设置保留的备份数量。
- 达到上限后当前文件重命名为
app.log.1,新建 app.log 继续写入。
- 生产环境长时间运行的服务应使用轮转,避免单个日志文件过大。
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
handler = RotatingFileHandler(
"app.log",
maxBytes=10 * 1024 * 1024,
backupCount=5,
encoding="utf-8",
)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
logger.addHandler(handler)
9. 最佳实践与技巧 #
- 每个模块顶部写
logger = logging.getLogger(__name__),不要用 print() 代替日志。
- 开发环境级别设
DEBUG,生产环境设 INFO 或 WARNING。
- 第三方库(如
requests、urllib3)日志过多时,提高其 logger 级别抑制噪音。
- 用
logger.info(f"...") 记录关键业务操作,用 logger.exception() 记录所有未预期异常。
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("requests").setLevel(logging.WARNING)
import logging
logger = logging.getLogger(__name__)
class UserService:
def login(self, username):
logger.info(f"用户 {username} 登录")
10. 总结 #
- 日志是项目必备基础设施:用级别过滤、Handler 控制输出、Formatter 统一格式。
- 小脚本用
basicConfig(),正式项目用自定义 Logger + 控制台/文件双输出 + 轮转。
- 核心习惯:
getLogger(__name__)、except 中用 exception()、生产环境合理设级别。
- 不要用
print() 做正式日志,无法分级、无法写文件、无法在生产环境关闭。
10.1 速查 #
import logging
logger = logging.getLogger(__name__)
logger.debug("调试")
logger.info("信息")
logger.warning("警告")
logger.error("错误")
logger.exception("异常")
logging.basicConfig(level=logging.INFO)
10.2 选型建议 #
| 场景 |
方案 |
| 学习/小脚本 |
basicConfig() |
| 正式项目 |
自定义 Logger + 双 Handler |
| 长期运行服务 |
加 RotatingFileHandler |
| 记录崩溃 |
logger.exception() |