python3+tkinter实践历程(二)——基于tkinter的日志检索工具
系列文章目录
python3+tkinter实践历程(一)——基于requests与tkinter的API工具
python3+tkinter实践历程(二)——基于tkinter的日志检索工具
python3+tkinter实践历程(三)——基于requests与tkinter的模拟web登录工具
python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端
分享背景
①分享意图在于帮助新入门的朋友,提供思路,里面详细的注释多多少少能解决一些问题。欢迎大佬指点跟交流。
②2021年8月,开始陆续有需求制作一些工具,因为python语言跟tkinter工具相对简单,所以就基于这些做了好几个不同用处的工具。
③分享从完成的第一个工具开始分享,分享到最新完成的工具,对于tkinter的理解也从一开始的摸索入门,到后来逐渐熟练,完成速度也越来越快,所用到的tk的功能点也越来越多。
制作背景
用于分析日志有无出现目标关键字、有无出现刷屏分析。
最终功能
检查日志,实现搜索关键字、刷屏分析两个功能
代码详解
# -*- coding=utf-8 -*-
"""
1、输入指定文件名,就读取指定文件
2、全选,则读取所有后缀为log的文件
"""
import tkinter as tk
from tkinter import *
import threading
import os
from tkinter.filedialog import askdirectory, askopenfilenames
import difflib
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.check_kw = True # 是否检查关键字
self.check_rep = True # 是否检查刷屏
# 此两个为内置的一些关键字,及关键字对应的JRbug单号
self.kw = "a||b||c||d"
self.jr_id_kw = {'a': 'A-001', 'b': 'B-001', 'c': 'C-001', 'd': 'D-001'}
# 初始化时执行下面create_widgets函数构造出GUI界面
self.create_widgets() #
def create_widgets(self):
"""创建图形化界面"""
# 串口目录路径,该目录下的日志都会被搜索
tk.Label(self, text="日志目录路径").grid(row=0, column=0)
self.dir = tk.StringVar()
self.dir.set("")
self.log_dir = tk.Entry(self, textvariable=self.dir, width=35)
self.log_dir.grid(row=0, column=1)
tk.Button(self, text="路径选择", command=self.selectPath).grid(row=0, column=2)
# 串口文件,指定某个日志文件
tk.Label(self, text="日志文件路径").grid(row=0, column=4)
self.name = tk.StringVar()
self.name.set("")
self.log_name = tk.Entry(self, textvariable=self.name, width=40)
self.log_name.grid(row=0, column=5)
tk.Button(self, text="文件选择", command=self.selectName).grid(row=0, column=6)
# 搜索按钮
start_btn = tk.Button(self, text="开始检查日志", command=self.start_search_log).grid(row=0, column=9)
# 搜索关键字与刷屏分析的文字
tk.Label(self, text="搜索关键字(多个关键字用英文逗号(,)隔开)").grid(row=1, column=0, columnspan=2)
tk.Label(self, text="搜索结果").grid(row=1, column=4, columnspan=2)
# 搜索关键字框
self.keyword = tk.Text(self, width=60, height=45)
self.keyword.grid(row=2, column=0, columnspan=3)
self.keyword.insert('end', 'assert,xxxx')
# 搜索结果框 + 滚动条
self.search_result = tk.Text(self, width=60, height=45)
respone_slide_bar = tk.Scrollbar(self, command=self.search_result.yview, orient="vertical")
respone_slide_bar.grid(row=2, column=7, sticky='ns')
self.search_result.grid(row=2, column=5, columnspan=2)
self.search_result.config(yscrollcommand=respone_slide_bar.set)
# 功能复选框
# 搜索关键字复选框 Checkbutton控件
self.check_kw_status = IntVar()
self.check_kw_btn = Checkbutton(self, variable=self.check_kw_status, text=u'搜索关键字', command=self.change_check_task)
self.check_kw_btn.grid(row=0, column=10)
# 默认已选择
self.check_kw_btn.select()
# 刷屏分析复选框
self.check_rep_status = IntVar()
self.check_rep_btn = Checkbutton(self, variable=self.check_rep_status, text=u'刷屏分析', command=self.change_check_task)
self.check_rep_btn.grid(row=0, column=11)
# 默认已选择
self.check_rep_btn.select()
# 刷屏次数传入
tk.Label(self, text="刷屏次数").grid(row=1, column=9)
self.rep = tk.StringVar()
self.rep.set("500")
self.log_rep = tk.Entry(self, textvariable=self.rep)
self.log_rep.grid(row=1, column=10, columnspan=2)
def selectPath(self):
"""把获取到的串口目录传入Entry"""
dir_ = askdirectory() # 获取目录的固定命令
self.dir.set(str(dir_)) # 将目录地址传到Entry框打印出来
def selectName(self):
"""把获取到的串口文件传入Entry"""
name_ = askopenfilenames() # 获取多个文件的固定命令,askopenfilename()则是获取单个文件
print(name_)
log = ''
j = 0
for i in name_:
if i:
j = j + 1
if j > 1:
log = log + ','
log = log + i
print(log)
self.name.set(log) # 对askopenfilenames()的返回值进行处理,再打印在Entry框
def change_check_task(self):
"""根据搜索关键字、检查刷屏的复选框改变变量状态"""
# 每一次点击复选框都会触发这个函数,决定了self.check_kw和self.check_rep的值
if self.check_kw_status.get() in [1, '1']:
self.check_kw = True
else:
self.check_kw = False
if self.check_rep_status.get() in [1, '1']:
self.check_rep = True
else:
self.check_rep = False
def thread_it(self, func, *args):
"""将函数打包进线程"""
# 作用在于将一些时间长的操作使用多线程来运行,以免在运行过程中主界面卡死
# 创建
t = threading.Thread(target=func, args=args)
# 守护 !!!
t.setDaemon(True)
# 启动
t.start()
def read_log(self, path, encode='utf-8'):
"""读取日志"""
# 读取每一行日志,放进列表中,返回该列表
all_text = []
try:
with open(path, 'r+', encoding=encode, errors='ignore') as f:
for line in f:
new_line = line.rstrip('\n')
all_text.append(new_line)
return all_text
except Exception as e:
print('读取日志异常:%s' % e)
return 'fail'
def search_log(self):
"""
如果指定了日志文件,则访问日志文件,如果无日志文件,则访问日志目录中的log文件
"""
# 先将内置的关键字拆解成check_list列表
check_list = self.kw.split('||')
# 获取界面外部的关键字,加入check_list列表中
new_kw = str(self.keyword.get(0.0, 'end'))
new_kw = new_kw.replace('\n', '')
new_kw = new_kw.replace(',', ',')
if new_kw:
print('获取到传入的关键字为:%s' % new_kw)
new_kw = new_kw.split(',')
for kw in new_kw:
check_list.append(kw)
print(check_list)
# 将日志目录中的日志或者指定的日志整理成log_name列表
log_name = list()
if not self.log_name.get():
print('无指定日志文件')
if not self.log_dir.get():
print('无传入日志目录')
return
else:
log_dir = self.log_dir.get()
print('传入了日志文件目录%s' % log_dir)
file = os.listdir(log_dir)
print(file)
for f in file:
if '.log' in f or '.txt' in f:
n = os.path.join(log_dir, f)
log_name.append(n)
else:
log = self.log_name.get()
log_name = str(log).split(',')
print(log)
if not log_name:
return
print('要遍历的日志文件为:%s' % log_name)
print('开始遍历')
# 开始将log_name中的日志一个一个地检索,self.search_result打印搜索过程
self.search_result.delete(0.0, 'end')
self.search_result.insert('end', '搜索中...\n')
self.search_result.update()
# 关键字检查结果字典
kw_check_result = dict()
# 刷屏检查结果字典
repeat_check_result = dict()
if self.check_kw or self.check_rep:
# 如果有一个检查点为True,
for path in log_name:
kw_check_result[path] = dict()
repeat_check_result[path] = list()
p = 0
for target in check_list:
kw_check_result[path][target] = list()
# 调用read_log函数,获取该日志地全部内容,返回结果为列表
all_text = self.read_log(path, 'utf-8')
if all_text == 'fail':
print('编码失败,请联系工具开发')
return
# 检查日志中关键字出现的地方和次数
if self.check_kw:
for line in all_text: # 遍历日志所有行
p = p + 1 # 记录行数
if line:
for target in check_list:
if '"%s"关键字已出现超过10次,JR_id:%s,请人工检查是否出现刷屏' % (target, self.jr_id_kw.get(target)) not in kw_check_result[path][target]:
if target.lower() in line.lower(): # 将关键字和整行变成小写比较,如果存在,就会把该行记录在字典中,当该关键字出现超过10次时,会停止记录。
if len(kw_check_result[path][target]) > 10:
kw_check_result[path][target] = []
kw_check_result[path][target].append(
'"%s"关键字已出现超过10次,JR_id:%s,请人工检查是否出现刷屏' % (target, self.jr_id_kw.get(target)))
else:
print('在第%s行搜索到%s' % (str(p), target))
kw_check_result[path][target].append('在第%s行搜索到"%s",JR_id:%s' % (
str(p), target, self.jr_id_kw.get(target)))
# 检查关键字完成,先把搜索结果打印到结果框
for i in kw_check_result:
self.search_result.insert('end', i + ':\n') # Text框的添加方式
for target in check_list:
if kw_check_result[i][target]:
for j in kw_check_result[i][target]:
self.search_result.insert('end', j + '\n')
else:
if target in new_kw:
self.search_result.insert('end', '"%s"关键字未出现\n' % target)
self.search_result.insert('end', '\n')
self.search_result.update()
# 检查刷屏
if self.check_rep:
# 获取界面上的刷屏次数Entry框中的值
rep_count = self.log_rep.get()
print('检查刷屏次数超过%s时会提示' % rep_count)
repeat_line = [] # 存在重复的行
nnnn = 0 # 记录检索到哪一行了
# check_text复制all_text列表的内容
check_text = list(all_text)
# 遍历 check_text,每一行为line,及检查对象
for line in check_text:
nnnn = nnnn + 1
if nnnn % 3000 == 0:
print('当前所在行数:%s' % str(nnnn))
line = line.strip('\n') # 去除‘\n’
count = 0 # 记录打印的次数
if line not in repeat_line and all_text:
# 如果该行不在重复列表中,且all_text还存在值,则进行检查
print('%s 不在repeat_line,进行相似度检查' % line)
for compare_line in all_text:
# 遍历all_text中所有的行,如果有该行不在repeat_line重复列表中,且不为line检查对象,则两行进行相似度分析,大于0.7则判定为重复
if compare_line in repeat_line and compare_line != line:
pass
else:
similarity = 0.0
try:
similarity = difflib.SequenceMatcher(None, line, compare_line).quick_ratio()
except Exception as e:
print('比较字符串相似度异常:%s' % e)
if similarity > 0.7:
count = count + 1
repeat_line.append(compare_line)
if line not in repeat_line:
repeat_line.append(line)
if count > int(rep_count):
print('"%s"疑似频繁打印%s次' % (str(line), str(count)))
repeat_check_result[path].append('"%s"疑似频繁打印%s次' % (str(line), str(count)))
self.search_result.insert('end', '"%s"疑似频繁打印%s次\n' % (str(line), str(count)))
self.search_result.update()
# line 该行检查完之后,将all_text列表中存在于repeat_line的值全部移除,这样all_text的值越来越少,repeat_line的值会越来越多,但是all_text加repeat_line一直等于总行数。最后all_text为空,repeat_line存进全部行
for de in repeat_line:
if de in all_text:
all_text.remove(de)
print('repeat_line的数量:%s' % str(len(repeat_line)))
print('all_text的数量:%s' % str(len(all_text)))
# 下面是两种检查完毕,从字典中读取信息,然后打印出搜索结果框的逻辑
if self.search_result.get(0.0):
self.search_result.delete(0.0, 'end') # Text框的清除方式
for i in kw_check_result:
self.search_result.insert('end', i + ':\n') # Text框的添加方式
if self.check_kw:
for target in check_list:
if kw_check_result[i][target]:
for j in kw_check_result[i][target]:
self.search_result.insert('end', j + '\n')
else:
if target in new_kw:
self.search_result.insert('end', '"%s"关键字未出现\n' % target)
self.search_result.insert('end', '\n')
if self.check_rep:
if repeat_check_result[i]:
for j in repeat_check_result[i]:
self.search_result.insert('end', j + '\n')
else:
self.search_result.insert('end', '无频繁刷屏的字段\n')
self.search_result.insert('end', '\n')
self.search_result.insert('end', '\n')
def start_search_log(self):
"""新建线程搜索日志"""
self.thread_it(self.search_log)
root = tk.Tk() # 创建Tk对象
app = Application(master=root) # 创建Application类的对象app,主界面为root
root.title("日志信息检索工具")
root.resizable(False, False) # 禁止调整窗口大小
sw = root.winfo_screenwidth() # 得到屏幕宽度
sh = root.winfo_screenheight() # 得到屏幕高度
# 窗口宽高为100
ww = 1200
wh = 800
x = (sw-ww) / 2
y = (sh-wh) / 2
root.geometry("%dx%d+%d+%d" % (ww, wh, x, y)) # 设置窗口宽高为1200x800,且自适应居中
root.deiconify() # 显示窗口
app.mainloop() # 进入消息循环