一、写在前面
本文所用例子为个人学习的小结,如有不足之处请各位多多海涵,欢迎小伙伴一起学习进步,如果想法可在评论区指出,我会尽快回复您,不胜感激!
所公布代码或截图均为运行成功后展示。
二、本文内容
产线新增了一台设备,测试完成后会上传数据到服务器,但由于设备自动测试时无人值守,导致测试失败时无人知晓,故利用python写个脚本监测服务器数据。
服务器每天会产生新日志,并且日志中只关注新增的一行的测试结果,当结果为FAIL时,才发送警报给指定飞秋。为了测试路径和指定飞秋IP可配置,将值写在配置文件中。
所以,整理需求后即得:
检查指定目录下,最新文件.DB1后缀文件的最新一行是否新增并且存在Fail,如果存在Fail则发送报警信息。
测试完成后,打包成exe使用。
三、开发环境
1.Python 3.9
IDE:
1.Pycharm
四、代码实现
4.1 引入所需包
引入后报红,则说明缺少对应module,可以通过pip install xx解决,如果pip install失败,可以尝试更换镜像源
#更换为豆瓣的镜像源
pip config set global.index-url https://pypi.douban.com/simple
import socket
import os
import glob
import sys
import time
import json
4.2 定义发送给feiq的方法
定义发送feiqiu消息的方法,设定传入参数为ip / port / message。
套接字的两个参数是“socket.AF_INET”表示我们使用的协议是IPV4,还有一个是“socket.SOCK_DGRAM”表示我们使用的是UDP类型的通讯技术。
创建UDP套接字后定义基本测试参数,信息的格式,发送的格式。
# 通过feiq的socket发送到指定ip
def send_feiqiu_message(ip, port, message):
# 创建UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 飞秋的消息格式通常为:版本号:包编号:发送者用户名:发送者主机名:命令字:附加信息
# 命令字32表示发送消息
version = 1
packet_no = 12345
sender = 'XXX'
hostname = 'Hardware_server'
command = 32
additional_info = message
# 组装消息内容
feiqiu_message = f"{version}:{packet_no}:{sender}:{hostname}:{command}:{additional_info}"
# 发送消息
sock.sendto(feiqiu_message.encode('gbk'), (ip, port))
# 关闭套接字
sock.close()
4.3定义获取指定路径下最新DB1后缀的文件名的方法
根据传入路径,获取路径下所有的DB1文件,并根据创建的时间倒排,获取最新的文件名。
os.path.getctime为创建时间,getmtime为修改时间,根据自己需求定义
# 获取指定目录下最新创建的DB1后缀的文件
def find_latest_db1_file(directory):
# 获取指定目录下所有.DB1文件
files = glob.glob(os.path.join(directory, '*.DB1'))
# 按创建时间排序
files.sort(key=os.path.getctime, reverse=True)
# 返回最新的文件
if files:
return files[0]
else:
return None
4.4定义读取文件最新一行是否为FAIL的方法
根据传入文件的路径读取文件最后一行,并检查是否存在FAIL字符串,返回检查结果
# 读取文件中最新一行是否包含FAIL
def check_last_line_for_fail(filename):
try:
with open(filename, 'r') as file:
# 读取文件的最后一行
last_line = file.readlines()[-1]
# 检查是否包含'FAIL'
return 'FAIL' in last_line
except Exception as e:
print(f"Error reading file {filename}: {e}")
return False
4.5定义统计文件行数的方法
根据传入文件的路径,统计该文件的行数。
# 统计当前文件中有多少行
def count_lines(filename):
"""统计文件的行数"""
try:
with open(filename, 'r') as file:
return sum(1 for line in file)
except FileNotFoundError:
print(f"文件未找到: {filename}")
return None
except Exception as e:
print(f"读取文件时发生错误: {e}")
return None
4.6定义以JSON格式从配置文件中读取字典值的方法
从指定文件中以JSON格式读取各字段,并匹配key和value,成功则返回值。
# 从配置文件中以JSON格式读取字典值
def read_config_value(config_file, *keys):
"""从JSON配置文件中读取嵌套的键值"""
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
value = config_data
for key in keys:
value = value[key] # 深入字典获取值
return value
except FileNotFoundError:
print(f"配置文件未找到: {config_file}")
return None
except KeyError:
print(f"键 {' -> '.join(keys)} 在配置文件中未找到")
return None
except json.JSONDecodeError:
print("配置文件格式错误(不是有效的JSON)")
return None
except Exception as e:
print(f"读取配置文件时发生错误: {e}")
return None
4.7执行方法
获取和运行路径同级目录下的配置文件config.ini的文件路径。
获取配置文件中ip字符串,当ip字符串不为空则以 ","分割,给ipaddress_list赋值。
# 获取当前项目路径同目录下的配置文件config.ini
config_file_path = os.path.dirname(os.path.realpath(sys.argv[0])) + "\config.ini"
# 读取配置文件中指定ip值
host = read_config_value(config_file_path, "ipaddress", "ip")
# 给ipaddress_list一个默认值
ipaddress_list = {"xx.x.x.xxx"}
if host is not None:
print(f"待接收警报主机地址: {host}")
ipaddress_list = host.split(",")
4.8 执行main方法
注释写的比较清楚,feiq的端口默认是2425,循环60s一次。
if __name__ == '__main__':
# 记录上一次行数统计
last_lineCount = 0
# 记录上一份文件名
last_filename = ''
# 从配置文件中读取DB1文件所在目录
directory = read_config_value(config_file_path, "ipaddress", "DB1directory")
while True:
# 找到最新的.DB1文件
latest_file = find_latest_db1_file(directory)
# 统计当前文件的行数
current_lineCount = count_lines(latest_file)
# 获取当前文件的文件名
current_filename = os.path.basename(latest_file)
# 如果文件存在
if latest_file:
# 如果当前文件和上一份文件名不相同,计数归0
if current_filename != last_filename:
last_filename = current_filename
print("new DB1 File created!")
last_lineCount = 0
# 检查是否有新增行数
if current_lineCount > last_lineCount:
last_lineCount = current_lineCount
print("new line created!")
# 检查最后一行是否包含'Fail'
if check_last_line_for_fail(latest_file):
print("XX Failed!!!!")
# 发送警报信息给指定飞秋
for ip in ipaddress_list:
send_feiqiu_message(ip, 2425, 'XX Failed!!!!')
else:
print("XX passed")
else:
print("No new line created!")
else:
# 如果文件不存在,统计行数清空
last_lineCount = 0
print("No .DB1 files found in the directory.")
# 休眠指定秒数开启新循环
time.sleep(60)
4.9配置文件格式
{
"ipaddress": {
"ip": "xx.xx.x.xxx,xx.xx.x.xxx",
"DB1directory": "C:\\Users\\Administrator\\Desktop"
}
}
4.10打包成exe
1.安装pyinstaller,清华源。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyinstaller
2.终端切换到项目目录下
cd F:\pythonProject1\pythonProject
3.执行以下命令,就打包好啦
pyinstaller --onefile .\checkFail.py
4.同级目录下会出现dist文件,将config.ini拷贝进去就可以运行了
五、看一看实际效果吧
打包后的脚本运行图
给Feiq发送信息的图
六、完整代码
"""
脚本目标:
检查指定目录下,最新文件.DB1后缀文件的最新一行是否新增并且存在Fail,如果存在Fail则发送报警信息
"""
import socket
import os
import glob
import sys
import time
import json
# 通过feiq的socket发送到指定ip
def send_feiqiu_message(ip, port, message):
# 创建UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 飞秋的消息格式通常为:版本号:包编号:发送者用户名:发送者主机名:命令字:附加信息
# 命令字32表示发送消息
version = 1
packet_no = 12345
sender = 'XX'
hostname = 'Hardware_server'
command = 32
additional_info = message
# 组装消息内容
feiqiu_message = f"{version}:{packet_no}:{sender}:{hostname}:{command}:{additional_info}"
# 发送消息
sock.sendto(feiqiu_message.encode('gbk'), (ip, port))
# 关闭套接字
sock.close()
# 获取指定目录下最新创建的DB1后缀的文件
def find_latest_db1_file(directory):
# 获取指定目录下所有.DB1文件
files = glob.glob(os.path.join(directory, '*.DB1'))
# 按创建时间排序
files.sort(key=os.path.getctime, reverse=True)
# 返回最新的文件
if files:
return files[0]
else:
return None
# 读取文件中最新一行是否包含FAIL
def check_last_line_for_fail(filename):
try:
with open(filename, 'r') as file:
# 读取文件的最后一行
last_line = file.readlines()[-1]
# 检查是否包含'FAIL'
return 'FAIL' in last_line
except Exception as e:
print(f"Error reading file {filename}: {e}")
return False
# 统计当前文件中有多少行
def count_lines(filename):
"""统计文件的行数"""
try:
with open(filename, 'r') as file:
return sum(1 for line in file)
except FileNotFoundError:
print(f"文件未找到: {filename}")
return None
except Exception as e:
print(f"读取文件时发生错误: {e}")
return None
# 从配置文件中以JSON格式读取字典值
def read_config_value(config_file, *keys):
"""从JSON配置文件中读取嵌套的键值"""
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
value = config_data
for key in keys:
value = value[key] # 深入字典获取值
return value
except FileNotFoundError:
print(f"配置文件未找到: {config_file}")
return None
except KeyError:
print(f"键 {' -> '.join(keys)} 在配置文件中未找到")
return None
except json.JSONDecodeError:
print("配置文件格式错误(不是有效的JSON)")
return None
except Exception as e:
print(f"读取配置文件时发生错误: {e}")
return None
# 获取当前项目路径同目录下的配置文件config.ini
config_file_path = os.path.dirname(os.path.realpath(sys.argv[0])) + "\config.ini"
# 读取配置文件中指定ip值
host = read_config_value(config_file_path, "ipaddress", "ip")
# 给ipaddress_list一个默认值
ipaddress_list = {"XX.X.X.XXX"}
if host is not None:
print(f"待接收警报主机地址: {host}")
ipaddress_list = host.split(",")
if __name__ == '__main__':
# 记录上一次行数统计
last_lineCount = 0
# 记录上一份文件名
last_filename = ''
# 从配置文件中读取DB1文件所在目录
directory = read_config_value(config_file_path, "ipaddress", "DB1directory")
while True:
# 找到最新的.DB1文件
latest_file = find_latest_db1_file(directory)
# 统计当前文件的行数
current_lineCount = count_lines(latest_file)
# 获取当前文件的文件名
current_filename = os.path.basename(latest_file)
# 如果文件存在
if latest_file:
# 如果当前文件和上一份文件名不相同,计数归0
if current_filename != last_filename:
last_filename = current_filename
print("new DB1 File created!")
last_lineCount = 0
# 检查是否有新增行数
if current_lineCount > last_lineCount:
last_lineCount = current_lineCount
print("new line created!")
# 检查最后一行是否包含'Fail'
if check_last_line_for_fail(latest_file):
print("XX Failed!!!!")
# 发送警报信息给指定飞秋
for ip in ipaddress_list:
send_feiqiu_message(ip, 2425, 'XX Failed!!!!')
else:
print("XX passed")
else:
print("No new line created!")
else:
# 如果文件不存在,统计行数清空
last_lineCount = 0
print("No .DB1 files found in the directory.")
# 休眠指定秒数开启新循环
time.sleep(60)
七、小结
这是一个小功能,不需要人一直跑就可以被动收到提示,收到后再去处理问题,避免设备一直处于测试失败状态
八、感谢
感谢各位大佬的莅临,学习之路漫漫,吾将上下而求索。有任何想法请在评论区留言哦!
再次感谢!