日志分析脚本该如何高效过滤数据

wen 实用脚本 2

本文目录导读:

日志分析脚本该如何高效过滤数据

  1. 核心原则(减少IO与内存占用)
  2. 实战脚本模板(Python)
  3. 命令行替代方案(更快、更省内存)
  4. 性能对比表
  5. 实战优化建议
  6. 异常情况处理

对于高效过滤日志数据,核心策略是避免一次性加载全部内容到内存,利用流式处理结合多种过滤条件前置筛选,以下是针对不同场景的高效方案:

核心原则(减少IO与内存占用)

  1. 流式读取:永远不要用 read()readlines(),用 for line in filefile.readline()
  2. 条件前置:先过滤时间、等级等低计算量条件,再处理正则/复杂判断。
  3. 早停机制:排序后的日志(如时间升序),遇到超出范围立即停止读取。
  4. 并行处理:对于超大文件(>1GB),分区后多进程执行。

实战脚本模板(Python)

import re
from collections import deque
class LogFilter:
    def __init__(self, path):
        self.path = path
        # 预编译正则表达式(提升性能)
        self.patterns = {
            'error': re.compile(r'\(ERROR|FATAL\)', re.IGNORECASE),
            'ip': re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
            'uuid': re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
        }
        self.output = deque(maxlen=1000000)  # 固定内存
    def filter_by_keywords(self, include=None, exclude=None):
        """
        多关键字过滤(集合运算比列表快)
        include: {'ERROR', 'TIMEOUT'}
        exclude: {'DEBUG', 'INFO'}
        """
        include = include or set()
        exclude = exclude or set()
        with open(self.path, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                # 先检查排除词(短路)
                if exclude and any(kw in line for kw in exclude):
                    continue
                # 再检查包含词
                if include and not any(kw in line for kw in include):
                    continue
                self.output.append(line)
    def filter_by_regex(self, pattern, max_count=1000):
        """正则过滤(限定数量防内存溢出)"""
        compiled = re.compile(pattern)
        with open(self.path, 'r') as f:
            for line in f:
                if compiled.search(line):
                    self.output.append(line)
                    if len(self.output) >= max_count:
                        break
    def filter_by_time_range(self, start_time, end_time, time_format='%Y-%m-%d %H:%M:%S'):
        """
        时间范围过滤(利用排序性质实现早停)
        注意:日志必须已按时间排序
        """
        import datetime
        start = datetime.datetime.strptime(start_time, time_format)
        end = datetime.datetime.strptime(end_time, time_format)
        stopped_early = False
        with open(self.path, 'r') as f:
            for line in f:
                # 提取第一列时间戳(假设格式固定)
                try:
                    log_time = datetime.datetime.strptime(line[:19], time_format)
                except:
                    continue
                if log_time < start:
                    continue
                if log_time > end:
                    stopped_early = True
                    break  # 排序日志的早停
                self.output.append(line)
        return stopped_early  # 可判断是否完全覆盖
    def streaming_write(self, output_path, chunk_size=8192):
        """边过滤边写入,避免内存积累"""
        with open(self.path, 'r') as src, open(output_path, 'w') as dst:
            while True:
                chunk = src.read(chunk_size)  # 块读取减少系统调用
                if not chunk:
                    break
                # 处理块内换行符(保持行完整性)
                lines = chunk.split('\n')
                if chunk[-1] != '\n':
                    # 处理跨块行(保留最后一个不完整行)
                    lines, rest = lines[:-1], lines[-1]
                for line in lines:
                    if 'ERROR' in line:
                        dst.write(line + '\n')
    def parallel_filter(self, num_workers=4):
        """并行处理大文件(>500MB推荐)"""
        import multiprocessing as mp
        def worker(chunk_lines, result_queue):
            filtered = []
            for line in chunk_lines:
                if 'ERROR' in line and self.patterns['ip'].search(line):
                    filtered.append(line)
            result_queue.put(filtered)
        # 分割任务(按行数)
        with open(self.path) as f:
            all_lines = f.readlines()
        chunk_size = len(all_lines) // num_workers
        chunks = [all_lines[i:i+chunk_size] for i in range(0, len(all_lines), chunk_size)]
        # 启动进程
        queue = mp.Queue()
        processes = [mp.Process(target=worker, args=(chunk, queue)) for chunk in chunks]
        [p.start() for p in processes]
        [p.join() for p in processes]
        # 聚合结果
        while not queue.empty():
            self.output.extend(queue.get())
# 使用示例
filter = LogFilter('access.log')
filter.filter_by_keywords(include={'500', '503'}, exclude={'healthcheck'})
filter.streaming_write('error_only.log')

命令行替代方案(更快、更省内存)

对于纯过滤需求,有时比写脚本更高效:

# 1. 多条件组合过滤(awk 比 grep 更灵活)
awk '/ERROR/ && !/healthcheck/ && /10\.0\.0\./' access.log > filtered.log
# 2. 时间范围过滤(日志需排序,避免扫描全部)
sed -n '/2024-01-01 00:00:00/,/2024-01-01 01:00:00/p' app.log
# 3. 并行处理(xargs 拆分文件)
find logs/ -name "*.log" | xargs -I {} -P 4 sh -c 'grep -h "ERROR" {} > {}.filtered'
# 4. 使用 ripgrep(比 grep 快5-10倍)
rg -c "ERROR" --glob '*.log' | awk -F: '$2 > 100 {print}'  # 筛选错误次数>100的日志

性能对比表

方法 内存占用 速度(1GB文件) 适用场景
一次性读取过滤 极高(>1GB) 快但不可用 小文件
流式逐行+早期条件 低~稳定 适中 通用场景
ripgrep 极低 最快(3秒) 简单关键字
awk 快(5秒) 多字段条件
多进程+分块 中(每进程100MB) 快(8秒) 超大文件+复杂逻辑

实战优化建议

  1. 预处理日志文件

    • 按天分割日志 logrotate + 压缩,减少单次扫描量
    • 将文本日志转为 parquet 格式(列存+压缩10倍)
  2. 避免重复扫描

    # 一次性提取所有需要字段(比多次遍历快)
    for line in f:
        if 'TIMEOUT' in line:
            # 同时检查IP和时间
            if '10.0.' in line and '2024-01' in line:  
                record(line)
  3. 对日志格式做假设

    # 假设日志格式为 [时间] [级别] 内容
    # 直接切片比正则快3倍
    log_level = line[20:25]  
    if log_level == 'ERROR':
        process(line)
  4. 监控过滤进度

    # 对大文件添加进度显示
    import os
    from tqdm import tqdm
    file_size = os.path.getsize(path)
    with open(path, 'rb') as f:
        with tqdm(total=file_size, unit='B', unit_scale=True) as pbar:
            for line in f:
                # 过滤逻辑...
                pbar.update(len(line))

异常情况处理

  • 编码问题:使用 encoding='utf-8', errors='replace'
  • 超大单行:设置最大行长度 line[:1024]
  • 文件持续写入:使用 tail -f 模式 f.seek(0, 2) 实时监控新增行

最佳选择建议

  • 如果只是简单关键字过滤(如提取所有 ERROR 日志):直接用 grepripgrep
  • 如果需要多条件组合过滤且需要输出特定格式:awk 脚本
  • 如果需要结合外部数据(如IP白名单)二次处理:Python 流式脚本

抱歉,评论功能暂时关闭!