应使用 yield + csv.reader 流式读取日志,避免 readlines() 导致内存爆炸;配合预编译正则、批量写入、multiprocessing 多核加速及异常容错处理,实现高效稳定解析。

用 yield + csv.reader 流式读取,别一次性 readlines()
内存爆炸的根源往往是把整个日志文件当字符串或列表加载进内存。哪怕只是 500MB 的纯文本日志,readlines() 也可能吃掉 2GB+ 内存(Python 字符串开销大)。真实场景里,日志通常是按行追加的纯文本或 CSV 格式,逐行处理即可。
正确做法是用生成器边读边处理:
def parse_log_stream(filepath):
with open(filepath, encoding='utf-8') as f:
reader = csv.reader(f, delimiter='|') # 按实际分隔符调整
for row in reader:
# 过滤、转换、聚合等逻辑放这里
if len(row) >= 4 and row[3] == 'ERROR':
yield {'timestamp': row[0], 'msg': row[2]}
<h1>使用时才触发读取,不占额外内存</h1><p>for event in parse_log_stream('/var/log/app.log'):
process_error(event)</p>
-
csv.reader本身不缓存整文件,配合yield实现真正的流式迭代 - 避免用
pandas.read_csv(..., chunksize=)处理超大日志——它仍会为每个 chunk 构建 DataFrame,对象开销远高于原生 list - 若日志无结构(纯文本),直接用
for line in f:,比readlines()内存友好得多
正则匹配要预编译,且用 re.search() 替代 re.match()
日志行格式多变,比如 "2024-05-20 10:30:45 ERROR [pid:1234] Connection timeout",用正则提取字段很常见。但每次循环都调用 re.match(pattern, line) 会重复编译,性能损失明显;更隐蔽的问题是:re.match() 只从开头匹配,而时间戳前可能有空格或日志级别前缀,导致漏匹配。
- 把正则对象提到循环外:
LOG_PATTERN = re.compile(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+(\w+)\s+\[.*?\]\s+(.*)') - 统一用
re.search(),它在整个字符串中查找,容错更强 - 如果只关心某类日志(如含 “timeout” 的 ERROR),先做快速字符串判断再进正则:
if 'ERROR' in line and 'timeout' in line:,能过滤掉 90%+ 的无效行
写入聚合结果时,别频繁 open(..., 'a')
每处理一行就 open('output.json', 'a') 写一条,磁盘 I/O 会成为瓶颈——尤其在机械硬盘或高并发写入时。操作系统对小文件写入有缓冲,但 Python 默认不启用 full buffering,实际是行缓冲甚至无缓冲。
- 改用批量写入:攒够 1000 条再写一次,用
json.dumps(batch, separators=(',', ':'))减少空格开销 - 打开文件时显式指定缓冲区:
open('output.json', 'a', buffering=8192) - 更优解是用
sys.stdout直接输出,由 shell 重定向(python parse.py > result.json),省去文件打开/关闭开销
多核加速要小心 GIL,优先用 multiprocessing 而非 threading
日志解析是 CPU 密集型任务(正则、字符串切片、JSON 序列化),CPython 的 GIL 会让多线程几乎不提速。但直接上 multiprocessing.Pool 也容易踩坑:默认进程数等于 CPU 核心数,但每个子进程都要重新加载日志文件、重建正则对象、初始化上下文,开销不小。
- 用
multiprocessing.Pool(initargs=(LOG_PATTERN,))把预编译的正则传给子进程,避免重复编译 - 按文件块切分而非按行切分:用
os.stat().st_size计算偏移量,每个进程处理一段字节范围,再找最近的换行符对齐,避免跨行截断 - 如果日志是实时追加的(如
tail -f场景),用单进程 +inotify或watchdog监听新行,多进程反而难同步状态
真正卡住的往往不是算法,而是没意识到日志文件的编码可能是 latin-1(尤其旧系统),或某几行包含 BOM、控制字符导致 csv.reader 报 UnicodeDecodeError —— 这类错误必须在生成器里用 try/except 捕获并跳过,否则整个流就中断了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











