python3+tkinter实践历程(二)——基于tkinter的日志检索工具

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()          # 进入消息循环

工具截图展示

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值