背景
背景细节可以参考这篇文章: 与copilot 结对编程系列 - log日志重复性检测 - 第1篇 - 总体介绍及效果展示
重复代码检测逻辑:
该模块负责检测代码中的重复逻辑,并生成检测结果。主要功能包括:
- 读取代码文件
- 分析代码中的重复逻辑
- 生成检测结果
场景局限
当前的重复log日志检测主要用于提前识别无效log, 所以是不直接用于问题排除, 限定于System Compatibility Test case,
主要是围绕这个展开, 当然后续也会支持实际客户现场log, 可以更加真实的反应log日志的打印.
重复log定义
当前脚本是假定在log日志中10行以内重复出现了2次及以上就会被定性为重复log, 10行以内超过2次每多一次就计数+1.
log日志结构拆分
对于特定的项目都有特定的log日志格式, 本文假定的格式如下:
log format
:timestamp
+ 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 数据库存储