与copilot 结对编程系列 - log日志重复性检测 - 第2篇 - log 重复检测

背景

背景细节可以参考这篇文章: 与copilot 结对编程系列 - log日志重复性检测 - 第1篇 - 总体介绍及效果展示

重复代码检测逻辑:
该模块负责检测代码中的重复逻辑,并生成检测结果。主要功能包括:

  • 读取代码文件
  • 分析代码中的重复逻辑
  • 生成检测结果

场景局限

当前的重复log日志检测主要用于提前识别无效log, 所以是不直接用于问题排除, 限定于System Compatibility Test case,
主要是围绕这个展开, 当然后续也会支持实际客户现场log, 可以更加真实的反应log日志的打印.

重复log定义

当前脚本是假定在log日志中10行以内重复出现了2次及以上就会被定性为重复log, 10行以内超过2次每多一次就计数+1.

log日志结构拆分

对于特定的项目都有特定的log日志格式, 本文假定的格式如下:
log formattimestamp + thread_id + log_level + commpent + file_line log_message

timestamp: 时间戳, 每份日志必须要包含的信息
thread_id: 对于大型项目多线程多进程是必须的, id也可能会显示
log_level: log的等级, 比如debug用于调试, info 用于日常记录, warning/ error 用于排除问题
commpent: 大型项目常常是分布式, 代码可能部署在不同模块上
file_line: log日志在代码文件中的特定行数, 这种可能因人而异, 可有可无
log_message: log的主体信息, 用于显示当前的状态流程

整体策略选择

文件处理策略

在处理大量文本文件时,多线程和多进程各有优缺点。

多线程
- 优点:
- 线程间通信开销较小。
- 适合 I/O 密集型任务(如文件读写、网络请求等)。
- 线程创建和销毁的开销较小。
- 缺点:
- 由于 Python 的全局解释器锁(GIL),多线程在 CPU 密集型任务中无法充分利用多核 CPU 的优势。
- 线程安全问题需要注意,可能需要使用锁机制。
多进程
- 优点:
- 可以充分利用多核 CPU 的优势,适合 CPU 密集型任务。
- 每个进程有独立的内存空间,避免了 GIL 的限制。
- 缺点:
- 进程间通信开销较大。
- 进程创建和销毁的开销较大。
结合当前场景的选择
当前场合主要涉及文件的读取和处理,属于 I/O 密集型任务。虽然多线程在 I/O 密集型任务中表现较好,但由于 Python 的 GIL 限制,多线程在处理大量文件时可能无法充分利用多核 CPU 的优势。因此,使用多进程可能是更好的选择。

重复检测策略

基于重复log定义, 使用 deque 来处理逻辑和检测重复代码.
主要原因有以下几点:

  • 高效的队列操作
    deque 是双端队列,支持在两端快速地添加和删除元素。相比于列表,deque 在两端的操作时间复杂度是 O(1),而列表在头部插入或删除元素的时间复杂度是 O(n)。
    在基于定义,deque 可以被用来维护一个固定长度的窗口(maxlen=line_distance),用于跟踪最近读取的 file_line。这样可以高效地管理和更新这个窗口。
  • 滑动窗口机制
    deque 的 maxlen 参数设置了窗口的最大长度。当 deque 达到这个长度时,再添加新元素会自动移除最旧的元素。这种滑动窗口机制非常适合用于检测重复的日志行。
    在每次读取新行时,将 file_line 添加到 deque 中,并检查 deque 中该 file_line 的出现次数。如果出现次数超过 1,则表示在这个窗口内有重复的日志行。
  • 简化代码逻辑:
    使用 deque 可以简化代码逻辑,不需要手动管理窗口的大小和元素的移除操作。deque 会自动处理这些操作,使代码更加简洁和易读。

关键函数细节

  • split_sct_log : 拆分日志字符串
    • 功能: 解析SCT日志行,将其分割成多个部分,包括日志过程、日志级别、日志类型、文件行号和日志消息。
    • 用途: 用于从SCT日志中提取有用的信息,以便后续处理和分析。
    • 解释: 该函数首先将日志行按/分割成多个部分。如果分割后的部分少于5个,则返回空字符串。然后进一步解析日志消息部分,提取日志过程、日志级别、文件行号、日志类型和日志消息。
def split_sct_log(log):
    log_procedure, log_level, log_type, file_line, log_message = '', '', '', '', ''
    parts = log.split('/')
    if len(parts) < 5:
        return log_procedure, log_level, log_type, file_line, log_message
    message = parts[4].split(' ', maxsplit=1)
    if len(message) < 2:
        return log_level, log_type, file_line, log_message
    if 'ueId' in log:
        log_procedure = 'UE'
    else:
        log_procedure = '-'
    log_level = parts[2]
    file_line = message[0]
    if 'pp:' not in file_line:
        file_line = ''
    log_type = extract_log_type(message[1])
    log_message = numbers_regex.sub('-', message[1])
    log_message = illegal_regex.sub('?', log_message)
    return log_procedure, log_level, file_line, log_type, log_message
  • process_sct_file
    • 功能: 处理一个SCT日志文件,检测重复的日志行,并收集相关信息。
    • 用途: 用于分析SCT日志文件,找出重复的日志行,并统计相关信息。
    • 解释: 该函数打开一个SCT日志文件,逐行读取并解析日志行。如果检测到重复的日志行,则记录相关信息,包括日志过程、日志级别、文件行号、日志类型和日志消息。
def process_sct_file(filename, line_distance):
    sct_case_data = set()
    log_info_data = {}
    sct_case_log_data = defaultdict(default_log_counts)
    with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
        file_line_deque = deque(maxlen=line_distance)  #Queue for tracking file_stine and log_maessage
        sct_case_name = ''
        for line_number, line in enumerate(f, start=1):
            log_procedure, log_level, file_line, log_type, log_message = split_sct_log(line)  #Directly split the log when reading each line
            if not all([log_procedure, log_level, log_type, file_line, log_message]):  # Check if any variable is an empty string
                continue
            file_line_deque.append(file_line)  # store file_line and log_message
            if file_line_deque.count(file_line) > 1:
                sct_case_name = filename.split(os.sep)[-2]
                split_result = split_regex.split(sct_case_name)
                if len(split_result) > 1:
                    sct_case_name = split_result[1]
                else:
                    print(f"Error processing in sct_case_name: {sct_case_name}")
                sct_case_data.add((sct_case_name, filename))
                add_log_info(log_info_data, log_procedure, log_level, log_type, file_line, log_message)
                key = (sct_case_name, file_line)
                sct_case_log_data[key]['count'] += 1
                sct_case_log_data[key]['duplicate_lines_count'] += 1
                if log_level in valid_keys:
                    sct_case_log_data[key][log_level] += 1
                else:
                    sct_case_log_data[key]['OTHER'] += 1
    return sct_case_data, log_info_data, sct_case_log_data
  • detect_repeated_logs
    • 功能: 处理多个SCT日志文件,检测重复的日志行,并收集相关信息。
    • 用途: 用于并行处理多个SCT日志文件,找出重复的日志行,并统计相关信息。
    • 解释: 该函数遍历给定的文件列表,调用process_sct_file函数处理每个文件,并合并所有文件的处理结果。
def detect_repeated_logs(files, line_distance):
    sct_case_data_in_thread = set()
    log_info_data_in_thread = {}
    sct_case_log_data_in_thread = defaultdict(default_log_counts)
    for filename in files:
        try:
            sct_case_data, log_info_data, sct_case_log_data = process_sct_file(filename, line_distance)
            sct_case_data_in_thread.update(sct_case_data)
            log_info_data_in_thread = {**log_info_data_in_thread, **log_info_data}
            for key, value in sct_case_log_data.items():
                for k, v in value.items():
                    sct_case_log_data_in_thread[key][k] += v
        except Exception as e:
            print(f"Error processing file {filename}: {e}")
    return sct_case_data_in_thread, log_info_data_in_thread, sct_case_log_data_in_thread
  • find_sut_out_files
    • 功能: 在指定目录中查找特定名称的文件。
    • 用途: 用于在指定目录及其子目录中查找名为sut.out的文件。
    • 解释: 该函数使用os.walk遍历指定目录及其子目录,查找所有名为sut.out的文件,并返回这些文件的路径列表。
def find_sut_out_files(directory, speclific_filename):
    sut_out_files = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file == speclific_filename:
                sut_out_files.append(os.path.join(root, file))
    return sut_out_files
  • process_sct_logs
    • 功能: 处理指定目录中的所有SCT日志文件,检测重复的日志行,并将结果存储到数据库中。
    • 用途: 用于批量处理SCT日志文件,找出重复的日志行,并将统计结果存储到数据库中。
    • 解释: 该函数首先查找指定目录中的所有sut.out文件,然后将这些文件分成多个批次,并行处理每个批次的文件,最后将所有处理结果合并并存储到数据库中。
def process_sct_logs(directory, speclific_filename):
    start_time = time.time()
    query_done_time = time.time()
    end_time = time.time()
    sut_out_files = find_sut_out_files(directory, speclific_filename)
    batches = list(divide_files_into_batches(sut_out_files, 10))
    line_distance = 10

    detect_repeated_logs_partial = partial(detect_repeated_logs, line_distance=line_distance)

    # 使用 process_map 代替 pool.map,并添加进度条
    results = process_map(detect_repeated_logs_partial, batches, max_workers=os.cpu_count(), chunksize=1)

    query_done_time = time.time()
    combined_sct_case_data = set()
    combined_log_info_data = {}
    combined_sct_case_log_data = defaultdict(default_log_counts)
    for sct_case_data, log_info_data, sct_case_log_data in results:
        combined_sct_case_data.update(sct_case_data)
        combined_log_info_data = {**combined_log_info_data, **log_info_data}
        for key, value in sct_case_log_data.items():
            for k, v in value.items():
                combined_sct_case_log_data[key][k] += v
    data_combine_done_time = time.time()
    ds.store_sct_logs_to_db("repeated_logs.db", combined_sct_case_data, combined_log_info_data, combined_sct_case_log_data)
    end_time = time.time()
    print(f"query time taken: {query_done_time - start_time:.1f} seconds")
    print(f"data combine time taken: {data_combine_done_time - query_done_time:.1f} seconds")
    print(f"write database time taken: {end_time - data_combine_done_time:.1f} seconds")
    print(f"Total time taken: {end_time - start_time:.1f} seconds")

最后

有兴趣, 需要源码可以关注我的公众号, 代码的更多细节, 一起交流~~~
下一篇讲数据库存储细节: 与copilt 结对编程系列 - log日志重复性检测 - 第3篇 - SQLite 数据库存储
在这里插入图片描述

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

御风之

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值