—— 程序的黑匣子
import logging
# 基本配置
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 五个日志级别
logging.debug("调试信息 - 详细诊断")
logging.info("一般信息 - 确认程序正常")
logging.warning("警告信息 - 意外但可处理")
logging.error("错误信息 - 某功能失败")
logging.critical("严重错误 - 程序可能无法继续")
# 级别数值: DEBUG=10, INFO=20, WARNING=30, ERROR=40, CRITICAL=50
import logging
# 获取模块级 Logger(推荐方式)
logger = logging.getLogger(__name__)
# 配置 Logger
logger.setLevel(logging.DEBUG)
# 控制台 Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_fmt = logging.Formatter(
'%(asctime)s [%(levelname)s] %(message)s',
datefmt='%H:%M:%S'
)
console_handler.setFormatter(console_fmt)
# 文件 Handler
file_handler = logging.FileHandler(
'app.log', encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
file_fmt = logging.Formatter(
'%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s'
)
file_handler.setFormatter(file_fmt)
# 添加 Handler
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 使用
logger.info("程序启动")
logger.debug("详细调试信息(只写文件)")
logger.warning("需要注意")
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
# 按大小轮转
rotating_handler = RotatingFileHandler(
filename='app.log',
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=5, # 保留5个备份
encoding='utf-8',
)
# 文件超过10MB时自动轮转: app.log → app.log.1 → app.log.2 ...
# 按时间轮转
timed_handler = TimedRotatingFileHandler(
filename='app.log',
when='midnight', # 每天午夜轮转
backupCount=30, # 保留30天
encoding='utf-8',
)
# when 参数: 'S'秒 'M'分 'H'时 'D'天 'midnight' 'W0'-'W6'(周一到周日)
# 生成文件名: app.log.2026-05-18
import logging
import sys
from logging.handlers import RotatingFileHandler
from pathlib import Path
def setup_logging(
log_dir: str = "logs",
level: str = "INFO",
max_bytes: int = 10 * 1024 * 1024,
backup_count: int = 5,
) -> logging.Logger:
"""一站式日志配置"""
# 创建日志目录
log_path = Path(log_dir)
log_path.mkdir(parents=True, exist_ok=True)
# Root Logger
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
# 清除已有 Handler(避免重复)
root_logger.handlers.clear()
# 控制台
console = logging.StreamHandler(sys.stdout)
console.setLevel(getattr(logging, level.upper()))
console.setFormatter(logging.Formatter(
'%(asctime)s [%(levelname)s] %(message)s',
datefmt='%H:%M:%S'
))
root_logger.addHandler(console)
# 全量日志文件
all_handler = RotatingFileHandler(
log_path / "all.log",
maxBytes=max_bytes,
backupCount=backup_count,
encoding="utf-8",
)
all_handler.setLevel(logging.DEBUG)
all_handler.setFormatter(logging.Formatter(
'%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s'
))
root_logger.addHandler(all_handler)
# 错误日志文件
err_handler = RotatingFileHandler(
log_path / "error.log",
maxBytes=max_bytes,
backupCount=backup_count,
encoding="utf-8",
)
err_handler.setLevel(logging.ERROR)
err_handler.setFormatter(logging.Formatter(
'%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s'
))
root_logger.addHandler(err_handler)
return root_logger
# 使用
logger = setup_logging(level="DEBUG")
logger.info("日志系统初始化完成")
import logging
logger = logging.getLogger(__name__)
# ✅ 用 f-string 或 % 格式化(延迟格式化)
# logging 内部会在日志级别不够时跳过格式化,更高效
logger.info("处理文件: %s, 大小: %d", filename, size)
# ❌ 不要这样写(无论级别都会格式化)
# logger.info(f"处理文件: {filename}, 大小: {size}")
# ✅ 异常日志用 exc_info
try:
risky_operation()
except Exception as e:
logger.error("操作失败: %s", e, exc_info=True)
# 或使用 logger.exception() 自动包含堆栈
# logger.exception("操作失败: %s", e)
# ✅ 结构化日志(JSON格式,方便ELK分析)
import json
class JsonFormatter(logging.Formatter):
def format(self, record):
log_data = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"line": record.lineno,
}
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data, ensure_ascii=False)
#!/usr/bin/env python3
"""第12课 日志验证"""
import logging
import tempfile
import os
from pathlib import Path
from logging.handlers import RotatingFileHandler
def test_logging_levels():
"""日志级别测试"""
assert logging.DEBUG == 10
assert logging.INFO == 20
assert logging.WARNING == 30
assert logging.ERROR == 40
assert logging.CRITICAL == 50
print("✅ 日志级别测试通过")
def test_logger_creation():
"""Logger创建测试"""
logger = logging.getLogger("test_module")
logger.setLevel(logging.DEBUG)
assert logger.name == "test_module"
assert logger.level == logging.DEBUG
print("✅ Logger创建测试通过")
def test_file_handler():
"""文件Handler测试"""
with tempfile.TemporaryDirectory() as tmpdir:
log_file = Path(tmpdir) / "test.log"
handler = logging.FileHandler(str(log_file), encoding="utf-8")
handler.setLevel(logging.DEBUG)
logger = logging.getLogger("test_file")
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
logger.info("测试消息")
handler.flush()
content = log_file.read_text(encoding="utf-8")
assert "测试消息" in content
logger.removeHandler(handler)
handler.close()
print("✅ 文件Handler测试通过")
def test_rotating_handler():
"""轮转Handler测试"""
with tempfile.TemporaryDirectory() as tmpdir:
log_file = Path(tmpdir) / "rotate.log"
handler = RotatingFileHandler(
str(log_file), maxBytes=100, backupCount=3, encoding="utf-8"
)
logger = logging.getLogger("test_rotate")
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
# 写入足够多的日志触发轮转
for i in range(50):
logger.info("Line %d: padding text to fill space", i)
handler.flush()
# 验证轮转文件存在
log_dir = Path(tmpdir)
log_files = list(log_dir.glob("rotate.log*"))
assert len(log_files) >= 1
logger.removeHandler(handler)
handler.close()
print("✅ 轮转Handler测试通过")
if __name__ == "__main__":
test_logging_levels()
test_logger_creation()
test_file_handler()
test_rotating_handler()
print("\n🎉 第12课全部验证通过!")