本文目录导读:
- 使用日志级别控制,避免无谓拼接
- 结构化日志(JSON格式),便于日志分析
- 使用
QueueHandler+ 异步线程处理日志(高并发优化) - 使用
RotatingFileHandler或TimedRotatingFileHandler管理日志文件 - 缓存 Logger 实例,避免频繁
getLogger()开销 - 避免在热路径中使用
isEnabledFor()不必要调用 - 生产环境配置:禁用
Propagate,避免重复打印 - 完整优化示例(生产级)
- 总结优化要点
Python日志输出优化可以从性能、可读性、维护性和处理能力等方面入手,以下是一些实用的优化案例及最佳实践。
使用日志级别控制,避免无谓拼接
❌ 低效写法(即使日志级别不输出,也会执行字符串拼接):
import logging
logging.debug("用户ID: %s, 操作: %s, 耗时: %s ms" % (user_id, action, cost_time))
✅ 优化写法(懒格式化,只在需要时计算):
import logging
# 方式1:使用 %s 占位符(推荐,性能最好)
logging.debug("用户ID: %s, 操作: %s, 耗时: %s ms", user_id, action, cost_time)
# 方式2:使用 f-string + 检查级别(避免不必要计算)
if logging.DEBUG >= logger.getEffectiveLevel():
logger.debug(f"用户ID: {user_id}, 操作: {action}, 耗时: {cost_time} ms")
原理:
logging.debug(msg, *args)只在日志需要输出时才格式化字符串。
结构化日志(JSON格式),便于日志分析
传统散乱输出(难解析):
logging.info("用户 %s 登录成功,IP: %s,时间: %s", user, ip, time)
✅ 结构化优化(输出 JSON,方便 ELK / Splunk 等工具处理):
import json
import logging
class StructuredFormatter(logging.Formatter):
def format(self, record):
# 基础字段
log_entry = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"module": record.module,
"message": record.getMessage(),
}
# 如果有 extra 字段,合并进来
if hasattr(record, "extra_fields"):
log_entry.update(record.extra_fields)
return json.dumps(log_entry, ensure_ascii=False)
# 使用示例
logger = logging.getLogger("app")
handler = logging.StreamHandler()
handler.setFormatter(StructuredFormatter())
logger.addHandler(handler)
# 日志输出时携带结构化字段
logger.info("用户登录", extra={"extra_fields": {
"user_id": 12345,
"action": "login",
"ip": "192.168.1.1",
"duration_ms": 45
}})
输出结果:
{"timestamp": "2025-04-10 10:30:00", "level": "INFO", "module": "main", "message": "用户登录", "user_id": 12345, "action": "login", "ip": "192.168.1.1", "duration_ms": 45}
使用 QueueHandler + 异步线程处理日志(高并发优化)
高并发场景下,日志 I/O 可能成为瓶颈,用队列+独立线程写入,不阻塞主线程。
import logging
import logging.handlers
import queue
import time
import threading
# 创建一个队列
log_queue = queue.Queue(-1)
# 队列处理器(主线程快速入队)
queue_handler = logging.handlers.QueueHandler(log_queue)
logger = logging.getLogger("async_logger")
logger.addHandler(queue_handler)
logger.setLevel(logging.DEBUG)
# 独立的日志消费线程(真正的 I/O 操作)
def log_consumer():
# 实际写入的目标(文件或控制台)
file_handler = logging.FileHandler("app.log")
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
# 从队列取日志并写入
while True:
record = log_queue.get()
if record is None: # 发送 None 来优雅关闭
break
file_handler.emit(record)
consumer_thread = threading.Thread(target=log_consumer, daemon=True)
consumer_thread.start()
# 主线程使用(正常使用,不会阻塞)
for i in range(1000):
logger.info("处理请求 #%d", i)
time.sleep(0.001) # 模拟业务
适用场景:高吞吐 Web 服务、实时数据处理。
使用 RotatingFileHandler 或 TimedRotatingFileHandler 管理日志文件
避免单个日志文件无限增大,自动按大小或时间切分。
import logging
from logging.handlers import RotatingFileHandler
# 按大小切分:单个文件 10MB,保留 5 个历史文件
handler = RotatingFileHandler(
"app.log",
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
logger = logging.getLogger("app")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
或按时间切分(生产环境更常见):
from logging.handlers import TimedRotatingFileHandler
# 每天午夜切分,保留 30 天
handler = TimedRotatingFileHandler(
"app.log",
when="midnight",
interval=1,
backupCount=30
)
缓存 Logger 实例,避免频繁 getLogger() 开销
❌ 低效写法(每个函数都重新获取):
def func1():
logger = logging.getLogger(__name__)
logger.info("func1 executed")
def func2():
logger = logging.getLogger(__name__)
logger.info("func2 executed")
✅ 优化写法(模块级别缓存):
import logging
logger = logging.getLogger(__name__) # 模块全局只获取一次
def func1():
logger.info("func1 executed")
def func2():
logger.info("func2 executed")
避免在热路径中使用 isEnabledFor() 不必要调用
如果日志行本身已经通过级别过滤,不需要额外检查(除非格式化开销极大)。
❌ 冗余检查:
if logger.isEnabledFor(logging.DEBUG):
logger.debug("耗时数据统计: %s", expensive_computation())
✅ 正确做法(让 logger 自己判断):
# DEBUG 级别未开启,expensive_computation() 不会执行
logger.debug("耗时数据统计: %s", expensive_computation())
注意:
expensive_computation()本身是一个复杂函数调用(非惰性求值),建议先检查级别。
生产环境配置:禁用 Propagate,避免重复打印
logger = logging.getLogger("my_module")
logger.propagate = False # 不向根 logger 传递,避免重复输出
# 只关联自定义的 handler
handler = logging.StreamHandler()
logger.addHandler(handler)
完整优化示例(生产级)
import logging
import logging.handlers
import json
import threading
import queue
class ProductionLogger:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name="myapp", log_file="app.log"):
if hasattr(self, '_initialized') and self._initialized:
return
self._initialized = True
self.logger = self._create_logger(name, log_file)
def _create_logger(self, name, log_file):
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
# 1. 文件处理器(按天切分,保留30天)
file_handler = logging.handlers.TimedRotatingFileHandler(
log_file, when="midnight", interval=1, backupCount=30
)
file_format = logging.Formatter(
"%(asctime)s | %(levelname)-8s | %(module)s:%(lineno)d | %(message)s"
)
file_handler.setFormatter(file_format)
logger.addHandler(file_handler)
# 2. 控制台处理器(开发调试用)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_format = logging.Formatter("%(levelname)s: %(message)s")
console_handler.setFormatter(console_format)
logger.addHandler(console_handler)
# 3. JSON 处理器(生产环境分析用,可选)
json_handler = logging.FileHandler("structured.log")
json_handler.setFormatter(StructuredFormatter())
json_handler.setLevel(logging.INFO)
logger.addHandler(json_handler)
# 禁止向上传播
logger.propagate = False
return logger
def info(self, msg, **kwargs):
if kwargs:
self.logger.info(msg, extra={"extra_fields": kwargs})
else:
self.logger.info(msg)
def error(self, msg, **kwargs):
if kwargs:
self.logger.error(msg, extra={"extra_fields": kwargs})
else:
self.logger.error(msg)
# 使用
log = ProductionLogger()
log.info("用户登录", user_id=12345, ip="192.168.1.1", duration_ms=45)
总结优化要点
| 优化方向 | 核心做法 | 收益 |
|---|---|---|
| 字符串格式化 | 使用 %s 占位符延迟计算 |
减少 CPU 开销 |
| 结构化输出 | JSON 格式 + extra 字段 | 便于日志分析平台处理 |
| 异步处理 | QueueHandler + 独立线程 | 不阻塞主业务 |
| 文件管理 | 按时间/大小自动切分 | 防止磁盘占满 |
| 避免重复 | 缓存 logger, 关闭 propagate | 减少重复日志 |
| 级别控制 | 热路径前检查 isEnabledFor |
避免复杂计算 |
根据你的应用场景(如高并发 Web、数据分析、CLI 工具),选择合适的优化组合即可。
标签: Python输出