免费python编程教程:https://pan.quark.cn/s/2c17aed36b72
"先用print调试,等项目大了再改logging"——这是许多Python初学者的真实写照。但当项目规模从几十行代码膨胀到几千行时,控制台里成百上千的print语句就像失控的洪水:找不到关键信息、无法关闭特定输出、无法区分不同模块的日志……这时你才会意识到,用logging替代print不是选择题,而是程序员的必修课。
# 调试信息、警告、错误混在一起
print("DEBUG: 用户ID获取成功")
print("WARNING: 磁盘空间不足")
print("ERROR: 数据库连接失败")
当需要临时关闭调试信息时,只能手动删除或注释所有print语句。
所有print都定向到标准输出,无法同时写入文件、发送邮件或推送到监控系统。
# 错误发生时不知道时间、模块名等信息
user_id = get_user_id()
print(f"获取到的用户ID: {user_id}") # 错误发生时难以追溯
在高频循环中,print的I/O操作会显著拖慢程序速度。
不同开发者的print风格各异,导致日志难以统一分析。
级别 | 数值 | 使用场景 |
---|---|---|
DEBUG | 10 | 开发调试细节 |
INFO | 20 | 程序运行关键节点 |
WARNING | 30 | 潜在问题但不影响运行 |
ERROR | 40 | 严重错误但程序能继续 |
CRITICAL | 50 | 致命错误导致程序退出 |
import logging
logging.debug("详细的调试信息") # 开发时开启,生产时关闭
logging.info("用户登录成功") # 记录关键业务事件
logging.warning("磁盘剩余5%") # 提示需要关注的问题
logging.error("数据库查询失败") # 记录业务异常
logging.critical("系统崩溃") # 记录致命错误
import logging
# 基本配置:同时输出到控制台和文件
logging.basicConfig(
level=logging.INFO, # 只显示INFO及以上级别
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='app.log', # 输出到文件
filemode='a' # 追加模式
)
# 添加控制台输出
console = logging.StreamHandler()
console.setLevel(logging.DEBUG) # 控制台显示DEBUG及以上
formatter = logging.Formatter('%(levelname)s: %(message)s')
console.setFormatter(formatter)
logging.getLogger().addHandler(console)
# 在不同模块中创建独立的logger
# module_a.py
import logging
logger = logging.getLogger(__name__) # 自动使用模块名作为标识
logger.info("模块A初始化完成")
# module_b.py
import logging
logger = logging.getLogger(__name__)
logger.debug("模块B的调试信息")
try:
1/0
except Exception as e:
logging.exception("发生异常:") # 自动记录完整堆栈
# 等同于:
# logging.error("发生异常:", exc_info=True)
# 定义print的替代函数
def log_print(msg, level=logging.INFO):
logger = logging.getLogger(__name__)
if level == logging.DEBUG:
logger.debug(msg)
elif level == logging.INFO:
logger.info(msg)
# ...其他级别处理
# 使用示例
log_print("这条消息相当于print", logging.INFO)
# config_logging.py
import logging
from logging.handlers import RotatingFileHandler
def setup_logger():
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# 控制台处理器
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
# 文件处理器(按大小轮转)
fh = RotatingFileHandler(
'app.log', maxBytes=1024*1024, backupCount=5
)
fh.setLevel(logging.DEBUG)
# 格式设置
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
ch.setFormatter(formatter)
fh.setFormatter(formatter)
logger.addHandler(ch)
logger.addHandler(fh)
# main.py
from config_logging import setup_logger
setup_logger()
import module_a
module_a.do_something()
# module_a.py
import logging
logger = logging.getLogger(__name__)
def do_something():
logger.debug("进入do_something方法")
try:
# 业务逻辑
logger.info("操作成功完成")
except Exception as e:
logger.error(f"操作失败: {str(e)}")
from logging.handlers import TimedRotatingFileHandler
# 每天午夜轮转,保留7天日志
handler = TimedRotatingFileHandler(
'app.log', when='midnight', backupCount=7
)
import re
from logging import Filter
class SensitiveFilter(Filter):
def filter(self, record):
# 过滤信用卡号等敏感信息
record.msg = re.sub(r'\d{4}-\d{4}-\d{4}-\d{4}', '****-****-****-****', record.msg)
return True
logger = logging.getLogger()
logger.addFilter(SensitiveFilter())
import re
from logging import Filter
class SensitiveFilter(Filter):
def filter(self, record):
# 过滤信用卡号等敏感信息
record.msg = re.sub(r'\d{4}-\d{4}-\d{4}-\d{4}', '****-****-****-****', record.msg)
return True
logger = logging.getLogger()
logger.addFilter(SensitiveFilter())
from logging.handlers import QueueHandler, QueueListener
import queue
import threading
log_queue = queue.Queue(-1) # 无界队列
queue_handler = QueueHandler(log_queue)
def handle_log_record(record):
# 实际的日志处理函数
logger = logging.getLogger()
logger.handle(record)
listener = QueueListener(log_queue, handle_log_record)
listener.start()
# 在主线程中使用
logger = logging.getLogger()
logger.addHandler(queue_handler)
原因:多次添加处理器或继承父logger的处理器
解决:
# 创建logger时设置propagate=False
logger = logging.getLogger('my_logger')
logger.propagate = False # 阻止向上传递日志
解决:使用环境变量动态控制级别
import os
import logging
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO').upper()
logging.basicConfig(level=LOG_LEVEL)
原因:多个线程同时写入日志
解决:logging模块默认是线程安全的,但需注意:
logging.getLogger(__name__)
获取logger实例__name__
作为logger名称从print到logging的升级,不仅是技术手段的进步,更是开发思维的转变。好的日志系统就像程序的"黑匣子",在出现问题时能快速定位原因,在正常运行时能监控健康状态。当你的项目规模从"能运行"迈向"可维护"时,就会深刻体会到logging模块带来的价值——它不仅是调试工具,更是程序可靠性的重要保障。
现在,打开你的项目,找到第一个print语句,让它光荣退休吧!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。