Python的logging模块是标准库中最强大的日志记录工具之一,它提供了灵活的日志记录功能,可以满足从简单脚本到复杂应用程序的各种需求。在Python 3.12中,logging模块继续得到增强和完善,特别是配置文件的使用方式更加灵活和强大。
日志记录是软件开发中不可或缺的一部分,它可以帮助开发者:
fileConfig是logging模块提供的一种通过配置文件来配置日志系统的方式。与直接在代码中配置相比,使用配置文件有以下优势:
Python支持两种主要的配置文件格式:
在Python 3.12中,fileConfig增强了对Properties文件格式的支持。Properties文件是一种简单的键值对格式,常用于Java应用程序,现在Python也能很好地支持这种格式。
一个典型的Properties日志配置文件如下:
code复制# 日志记录器配置
loggers=root,exampleLogger
# 根记录器配置
logger_root.level=DEBUG
logger_root.handlers=consoleHandler,fileHandler
# 示例记录器配置
logger_exampleLogger.level=INFO
logger_exampleLogger.propagate=1
logger_exampleLogger.handlers=fileHandler
# 处理器配置
handlers=consoleHandler,fileHandler
# 控制台处理器配置
handler_consoleHandler.class=StreamHandler
handler_consoleHandler.level=DEBUG
handler_consoleHandler.formatter=simpleFormatter
handler_consoleHandler.stream=ext://sys.stdout
# 文件处理器配置
handler_fileHandler.class=FileHandler
handler_fileHandler.level=INFO
handler_fileHandler.formatter=detailedFormatter
handler_fileHandler.filename=app.log
handler_fileHandler.mode=a
handler_fileHandler.encoding=utf-8
# 格式化器配置
formatters=simpleFormatter,detailedFormatter
# 简单格式化器
formatter_simpleFormatter.format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
# 详细格式化器
formatter_detailedFormatter.format=%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s
在Python代码中加载Properties配置文件的示例:
python复制import logging
from logging.config import fileConfig
def setup_logging():
try:
fileConfig('logging.properties')
logger = logging.getLogger(__name__)
logger.info('日志系统配置成功')
except Exception as e:
print(f'加载日志配置失败: {e}')
# 回退到基本配置
logging.basicConfig(level=logging.INFO)
if __name__ == '__main__':
setup_logging()
logger = logging.getLogger(__name__)
logger.debug('这是一条调试信息')
logger.info('这是一条普通信息')
logger.warning('这是一条警告信息')
Loggers是日志系统的入口点,应用程序通过调用logger对象的方法来记录日志。
配置示例:
code复制loggers=root,appLogger,securityLogger
logger_root.level=DEBUG
logger_root.handlers=consoleHandler
logger_appLogger.level=INFO
logger_appLogger.propagate=0
logger_appLogger.handlers=fileHandler
logger_securityLogger.level=WARNING
logger_securityLogger.propagate=1
logger_securityLogger.handlers=securityFileHandler
Handlers决定日志记录的去向,如控制台、文件、网络等。
常见handler类型:
配置示例:
code复制handler_consoleHandler.class=StreamHandler
handler_consoleHandler.level=DEBUG
handler_consoleHandler.formatter=briefFormatter
handler_consoleHandler.stream=ext://sys.stdout
handler_fileHandler.class=FileHandler
handler_fileHandler.level=INFO
handler_fileHandler.formatter=detailedFormatter
handler_fileHandler.filename=app.log
handler_fileHandler.mode=a
handler_fileHandler.encoding=utf-8
handler_rotatingHandler.class=handlers.RotatingFileHandler
handler_rotatingHandler.level=DEBUG
handler_rotatingHandler.formatter=detailedFormatter
handler_rotatingHandler.filename=debug.log
handler_rotatingHandler.maxBytes=10485760 # 10MB
handler_rotatingHandler.backupCount=5
handler_rotatingHandler.encoding=utf-8
Formatters控制日志记录的最终输出格式。
配置示例:
code复制formatter_simpleFormatter.format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
formatter_simpleFormatter.datefmt=%Y-%m-%d %H:%M:%S
formatter_detailedFormatter.format=%(asctime)s - %(name)s - %(levelname)s - %(pathname)s - %(lineno)d - %(funcName)s - %(message)s
formatter_detailedFormatter.datefmt=%Y-%m-%d %H:%M:%S,%f
Properties配置文件支持使用变量和外部引用:
code复制handler_fileHandler.class=FileHandler
handler_fileHandler.filename=${LOG_DIR}/app.log
handler_fileHandler.mode=a
然后在代码中传入变量:
python复制import os
from logging.config import fileConfig
LOG_DIR = '/var/log/myapp'
os.makedirs(LOG_DIR, exist_ok=True)
fileConfig('logging.properties', defaults={'LOG_DIR': LOG_DIR})
可以根据环境变量或运行参数动态选择配置:
python复制import os
from logging.config import fileConfig
def setup_logging():
env = os.getenv('APP_ENV', 'development')
config_file = f'logging-{env}.properties'
fileConfig(config_file)
可以在配置文件中引用自定义的过滤器和处理器:
code复制handler_customHandler.class=my_package.my_module.MyCustomHandler
handler_customHandler.level=DEBUG
handler_customHandler.formatter=simpleFormatter
问题现象:无法加载配置文件或配置不生效
可能原因:
解决方案:
python复制import os
from logging.config import fileConfig
def safe_file_config(config_path):
if not os.path.exists(config_path):
raise FileNotFoundError(f"日志配置文件不存在: {config_path}")
try:
fileConfig(config_path)
except Exception as e:
print(f"加载日志配置失败: {e}")
# 回退到基本配置
logging.basicConfig(level=logging.INFO)
问题现象:日志文件没有生成或无法写入
可能原因:
解决方案:
python复制import os
from logging.config import fileConfig
def ensure_log_dir(log_dir):
try:
os.makedirs(log_dir, exist_ok=True)
# 测试写入权限
test_file = os.path.join(log_dir, '.permission_test')
with open(test_file, 'w') as f:
f.write('test')
os.remove(test_file)
return True
except Exception as e:
print(f"无法写入日志目录 {log_dir}: {e}")
return False
if ensure_log_dir('/var/log/myapp'):
fileConfig('logging.properties', defaults={'LOG_DIR': '/var/log/myapp'})
else:
logging.basicConfig(level=logging.INFO)
问题现象:RotatingFileHandler或TimedRotatingFileHandler没有按预期轮转日志
可能原因:
解决方案:
code复制handler_rotating.class=handlers.RotatingFileHandler
handler_rotating.maxBytes=10485760 # 10MB
handler_rotating.backupCount=5
python复制import atexit
import logging
def cleanup():
logging.shutdown()
atexit.register(cleanup)
对于高性能应用,同步日志记录可能成为瓶颈。可以使用以下方式实现异步日志:
python复制from logging.handlers import QueueHandler, QueueListener
import queue
import threading
log_queue = queue.Queue(-1) # 无界队列
queue_handler = QueueHandler(log_queue) # 不直接处理日志
# 设置真正的handler
file_handler = logging.FileHandler('app.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 创建监听器
listener = QueueListener(log_queue, file_handler)
listener.start()
# 配置logger使用QueueHandler
logger = logging.getLogger()
logger.addHandler(queue_handler)
logger.setLevel(logging.INFO)
# 应用退出时停止监听器
atexit.register(listener.stop)
code复制handler_async.class=concurrent_log_handler.ConcurrentRotatingFileHandler
handler_async.level=DEBUG
handler_async.formatter=detailedFormatter
handler_async.filename=app.log
handler_async.maxBytes=10485760
handler_async.backupCount=5
合理设置日志级别可以显著提高性能:
python复制if logger.isEnabledFor(logging.DEBUG):
logger.debug('耗时操作结果: %s', expensive_operation())
避免在日志调用时进行昂贵的字符串操作:
python复制# 不推荐 - 无论是否记录都会执行字符串格式化
logger.debug('当前值: %s', expensive_to_string(obj))
# 推荐 - 仅在需要记录时执行格式化
if logger.isEnabledFor(logging.DEBUG):
logger.debug('当前值: %s', expensive_to_string(obj))
Python 3.12对logging模块做了一些改进和增强:
更灵活的fileConfig:
性能改进:
新的过滤器功能:
改进的文档和示例:
在实际项目中,我通常会创建一个logging_utils.py模块来集中管理日志配置:
python复制# logging_utils.py
import os
import logging
from logging.config import fileConfig
DEFAULT_LOG_DIR = '/var/log/myapp'
DEFAULT_CONFIG = 'logging.properties'
def configure_logging(config_file=None, log_dir=None, defaults=None):
"""配置应用程序日志"""
config_file = config_file or DEFAULT_CONFIG
log_dir = log_dir or DEFAULT_LOG_DIR
defaults = defaults or {}
# 确保日志目录存在
os.makedirs(log_dir, exist_ok=True)
# 设置默认值
defaults.setdefault('LOG_DIR', log_dir)
try:
fileConfig(config_file, defaults=defaults)
except Exception as e:
print(f'无法加载日志配置: {e}')
logging.basicConfig(level=logging.INFO)
# 捕获未处理的异常
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logger = logging.getLogger(__name__)
logger.critical("未处理的异常", exc_info=(exc_type, exc_value, exc_traceback))
sys.excepthook = handle_exception
def get_logger(name=None):
"""获取配置好的logger实例"""
return logging.getLogger(name)
这样,在应用程序的其他部分就可以简单地使用:
python复制from logging_utils import get_logger
logger = get_logger(__name__)
logger.info('应用程序启动')