fix: move file.

This commit is contained in:
秋云
2025-08-10 16:27:48 +08:00
parent fbc9e9b71c
commit f4087871b9
3 changed files with 478 additions and 478 deletions

View File

@@ -1,89 +1,89 @@
# 📋 日志监控与自动重连脚本说明 # 📋 日志监控与自动重连脚本说明
**作者:火山** **作者:火山**
## 📌 功能介绍 ## 📌 功能介绍
该脚本用于实时监控1Remote的日志文件当检测到远程连接断开日志中出现`OnRdpClientDisconnected`警告)时,自动执行重连操作,保障远程连接的稳定性。 该脚本用于实时监控1Remote的日志文件当检测到远程连接断开日志中出现`OnRdpClientDisconnected`警告)时,自动执行重连操作,保障远程连接的稳定性。
### ⚡⚡⚡❗ 重要提醒这不是JS脚本 ❗⚡⚡⚡ ### ⚡⚡⚡❗ 重要提醒这不是JS脚本 ❗⚡⚡⚡
### ⚡⚡⚡❗ 绝对不要不看完就直接运行 ❗⚡⚡⚡ ### ⚡⚡⚡❗ 绝对不要不看完就直接运行 ❗⚡⚡⚡
### ⚡⚡⚡❗ 再次强调这不是可执行的JS脚本 ❗⚡⚡⚡ ### ⚡⚡⚡❗ 再次强调这不是可执行的JS脚本 ❗⚡⚡⚡
## 🚀 环境要求 ## 🚀 环境要求
- **操作系统**Windows脚本依赖Windows特定快捷键操作 - **操作系统**Windows脚本依赖Windows特定快捷键操作
- **Python版本**Python 3.6及以上 - **Python版本**Python 3.6及以上
(检查方法:按下`Win+R`输入`cmd`,在命令行中执行`python --version` (检查方法:按下`Win+R`输入`cmd`,在命令行中执行`python --version`
## 🔧 安装依赖(重要!小白必看) ## 🔧 安装依赖(重要!小白必看)
1. **以管理员身份打开命令提示符CMD** 1. **以管理员身份打开命令提示符CMD**
- 方法1按下`Win + R`输入`cmd`,按住`Ctrl + Shift`同时点击「确定」 - 方法1按下`Win + R`输入`cmd`,按住`Ctrl + Shift`同时点击「确定」
- 方法2开始菜单搜索「命令提示符」右键选择「以管理员身份运行」 - 方法2开始菜单搜索「命令提示符」右键选择「以管理员身份运行」
2. **执行安装命令** 2. **执行安装命令**
在管理员CMD窗口中输入以下命令并回车等待安装完成出现`Successfully installed`字样即为成功): 在管理员CMD窗口中输入以下命令并回车等待安装完成出现`Successfully installed`字样即为成功):
``` ```
pip install pyautogui watchdog pip install pyautogui watchdog
``` ```
## 📂 文件说明 ## 📂 文件说明
| 文件名 | 作用描述 | | 文件名 | 作用描述 |
|----------------------|--------------------------------------------------------------------------| |----------------------|--------------------------------------------------------------------------|
| `log_monitor.py` | 核心监控程序,负责监控日志文件变化、检测断开事件并触发重连操作 | | `log_monitor.py` | 核心监控程序,负责监控日志文件变化、检测断开事件并触发重连操作 |
| `autoreconnection.py`| 重连操作脚本通过模拟键盘快捷键Win+D、Alt+M等执行自动重连 | | `autoreconnection.py`| 重连操作脚本通过模拟键盘快捷键Win+D、Alt+M等执行自动重连 |
| `config.json` | 配置文件存储1Remote日志文件所在目录示例`C:\...\1Remote-...\.logs`| | `config.json` | 配置文件存储1Remote日志文件所在目录示例`C:\...\1Remote-...\.logs`|
| `监控异常.txt` | 程序运行日志,记录操作过程、错误信息及重连状态(用于问题排查) | | `监控异常.txt` | 程序运行日志,记录操作过程、错误信息及重连状态(用于问题排查) |
## 🔍 首次使用步骤 ## 🔍 首次使用步骤
1. 确保已安装Python 3.6及以上版本并完成依赖安装 1. 确保已安装Python 3.6及以上版本并完成依赖安装
2. 双击运行`log_monitor.py`或在CMD中执行 2. 双击运行`log_monitor.py`或在CMD中执行
``` ```
python log_monitor.py python log_monitor.py
``` ```
3. 首次运行将引导配置日志目录: 3. 首次运行将引导配置日志目录:
- 日志文件命名格式为 `1Remote.log_YYYYMMDD.md`(按日期生成) - 日志文件命名格式为 `1Remote.log_YYYYMMDD.md`(按日期生成)
- 请输入1Remote日志所在目录的完整路径例如 `.logs` 文件夹路径) - 请输入1Remote日志所在目录的完整路径例如 `.logs` 文件夹路径)
## ⚙️ 配置说明 ## ⚙️ 配置说明
配置信息存储在`config.json`中,格式如下: 配置信息存储在`config.json`中,格式如下:
```json ```json
{ {
"log_dir": "C:\\Users\\Administrator\\Desktop\\1Remote-1.2.0-net9-x64\\.logs" "log_dir": "C:\\Users\\Administrator\\Desktop\\1Remote-1.2.0-net9-x64\\.logs"
} }
``` ```
- **修改日志目录** - **修改日志目录**
1. 运行`log_monitor.py`,程序会引导配置 1. 运行`log_monitor.py`,程序会引导配置
2. 或删除`config.json`后重新运行`log_monitor.py`,程序会重新引导配置 2. 或删除`config.json`后重新运行`log_monitor.py`,程序会重新引导配置
## 🖥️ 使用方法 ## 🖥️ 使用方法
1. 运行`log_monitor.py`后,程序将自动监控日志文件 1. 运行`log_monitor.py`后,程序将自动监控日志文件
2. 当检测到连接断开时,自动执行以下操作(通过`autoreconnection.py`实现): 2. 当检测到连接断开时,自动执行以下操作(通过`autoreconnection.py`实现):
- 按`Win+D`返回桌面 - 按`Win+D`返回桌面
- 按`Alt+M`组合键按住Alt键后按M键保持0.5秒后松开) - 按`Alt+M`组合键按住Alt键后按M键保持0.5秒后松开)
- 连续按两次回车键间隔0.3秒) - 连续按两次回车键间隔0.3秒)
3. 运行状态会同时显示在控制台和`监控异常.txt` 3. 运行状态会同时显示在控制台和`监控异常.txt`
## ⚠️ 注意事项 ## ⚠️ 注意事项
1. 确保1Remote程序正常运行且日志文件能正常生成否则会提示"未找到日志文件" 1. 确保1Remote程序正常运行且日志文件能正常生成否则会提示"未找到日志文件"
2. 脚本运行时会模拟键盘操作,建议避免手动操作鼠标键盘,以免干扰流程 2. 脚本运行时会模拟键盘操作,建议避免手动操作鼠标键盘,以免干扰流程
3. 重连逻辑: 3. 重连逻辑:
- 最多尝试3次重连失败则暂停重试 - 最多尝试3次重连失败则暂停重试
- 单次重连超时时间为30秒20秒内未检测到成功标志会自动重试 - 单次重连超时时间为30秒20秒内未检测到成功标志会自动重试
4. 支持跨天日志文件切换(因日志按日期命名) 4. 支持跨天日志文件切换(因日志按日期命名)
5. 所有操作记录(含错误信息)均保存在`监控异常.txt`,便于排查问题 5. 所有操作记录(含错误信息)均保存在`监控异常.txt`,便于排查问题
6. 确保Alt+M的1Remote快捷指令能够正常执行建议先双击`autoreconnection.py`查看效果 6. 确保Alt+M的1Remote快捷指令能够正常执行建议先双击`autoreconnection.py`查看效果
7. 确保Win+D的Windows快捷键能指向正确是显示器双屏会多一步选择哪块屏幕的操作建议问AI修改一下`autoreconnection.py`中的操作) 7. 确保Win+D的Windows快捷键能指向正确是显示器双屏会多一步选择哪块屏幕的操作建议问AI修改一下`autoreconnection.py`中的操作)
## ❓ 常见问题 ## ❓ 常见问题
| 问题现象 | 解决方法 | | 问题现象 | 解决方法 |
|---------------------------|--------------------------------------------------------------------------| |---------------------------|--------------------------------------------------------------------------|
| 提示"未找到日志文件" | 1. 检查`config.json`中日志目录是否正确<br>2. 确认1Remote已运行并生成当天日志`1Remote.log_YYYYMMDD.md` | | 提示"未找到日志文件" | 1. 检查`config.json`中日志目录是否正确<br>2. 确认1Remote已运行并生成当天日志`1Remote.log_YYYYMMDD.md` |
| 重连操作无反应 | 1. 检查`autoreconnection.py`是否与`log_monitor.py`在同一目录<br>2. 手动测试快捷键Win+D、Alt+M是否有效<br>3. 重新执行管理员CMD下的依赖安装命令 | | 重连操作无反应 | 1. 检查`autoreconnection.py`是否与`log_monitor.py`在同一目录<br>2. 手动测试快捷键Win+D、Alt+M是否有效<br>3. 重新执行管理员CMD下的依赖安装命令 |
| 程序启动失败 | 1. 查看`监控异常.txt`获取错误详情<br>2. 检查Python版本是否符合要求3.6及以上) | | 程序启动失败 | 1. 查看`监控异常.txt`获取错误详情<br>2. 检查Python版本是否符合要求3.6及以上) |

View File

@@ -1,24 +1,24 @@
import pyautogui import pyautogui
import time import time
print("正在执行会话关闭后操作...") print("正在执行会话关闭后操作...")
time.sleep(2) time.sleep(2)
# 返回Windows桌面Win+D # 返回Windows桌面Win+D
pyautogui.hotkey('winleft', 'd') # 更通用的Win+D组合键 pyautogui.hotkey('winleft', 'd') # 更通用的Win+D组合键
time.sleep(1) # 等待1秒 time.sleep(1) # 等待1秒
# 按下Alt+M键半秒后松开 # 按下Alt+M键半秒后松开
pyautogui.keyDown('alt') # 按住Alt键 pyautogui.keyDown('alt') # 按住Alt键
pyautogui.press('m') # 按M键 pyautogui.press('m') # 按M键
time.sleep(0.5) # 精确等待0.5秒(半秒) time.sleep(0.5) # 精确等待0.5秒(半秒)
pyautogui.keyUp('alt') # 松开Alt键 pyautogui.keyUp('alt') # 松开Alt键
pyautogui.keyUp('m') # 松开M键确保按键无粘连 pyautogui.keyUp('m') # 松开M键确保按键无粘连
time.sleep(0.1) # 等待0.1秒 time.sleep(0.1) # 等待0.1秒
# 按两次回车键间隔0.3秒 # 按两次回车键间隔0.3秒
pyautogui.press('enter') pyautogui.press('enter')
time.sleep(0.3) # 等待0.3秒 time.sleep(0.3) # 等待0.3秒
pyautogui.press('enter') pyautogui.press('enter')
print("操作完成!") print("操作完成!")

View File

@@ -1,368 +1,368 @@
import os import os
import sys import sys
import time import time
import json import json
import logging import logging
import subprocess import subprocess
from datetime import datetime from datetime import datetime
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
# 配置参数 # 配置参数
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(SCRIPT_DIR, "config.json") CONFIG_FILE = os.path.join(SCRIPT_DIR, "config.json")
LOG_MONITOR_PATH = os.path.join(SCRIPT_DIR, "监控异常.txt") LOG_MONITOR_PATH = os.path.join(SCRIPT_DIR, "监控异常.txt")
ENCODING = "utf-8" ENCODING = "utf-8"
RECONNECT_SCRIPT_NAME = "autoreconnection.py" RECONNECT_SCRIPT_NAME = "autoreconnection.py"
# 创建日志目录(如果不存在) # 创建日志目录(如果不存在)
os.makedirs(SCRIPT_DIR, exist_ok=True) os.makedirs(SCRIPT_DIR, exist_ok=True)
# 初始化日志系统 # 初始化日志系统
logging.basicConfig( logging.basicConfig(
filename=LOG_MONITOR_PATH, filename=LOG_MONITOR_PATH,
level=logging.INFO, level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s', format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S' datefmt='%Y-%m-%d %H:%M:%S'
) )
# 同时输出到控制台 # 同时输出到控制台
console = logging.StreamHandler() console = logging.StreamHandler()
console.setLevel(logging.INFO) console.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s') formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
console.setFormatter(formatter) console.setFormatter(formatter)
logging.getLogger().addHandler(console) logging.getLogger().addHandler(console)
# 记录状态 # 记录状态
last_position = 0 last_position = 0
processed_times = {} processed_times = {}
last_processed_date = None last_processed_date = None
reconnect_count = 0 reconnect_count = 0
current_log_file = None current_log_file = None
# 重启验证相关变量 # 重启验证相关变量
reconnect_attempts = 0 # 当前重启尝试次数 reconnect_attempts = 0 # 当前重启尝试次数
max_reconnect_attempts = 3 # 最大尝试次数 max_reconnect_attempts = 3 # 最大尝试次数
reconnect_in_progress = False # 标记是否有重启操作正在进行 reconnect_in_progress = False # 标记是否有重启操作正在进行
reconnect_start_time = 0 # 重启开始时间戳 reconnect_start_time = 0 # 重启开始时间戳
reconnect_success_detected = False # 是否检测到成功重启的日志 reconnect_success_detected = False # 是否检测到成功重启的日志
def configure_log_directory(): def configure_log_directory():
"""引导用户配置日志目录""" """引导用户配置日志目录"""
print("="*50) print("="*50)
print("日志监控程序配置") print("日志监控程序配置")
print("="*50) print("="*50)
print("请指定1Remote日志文件所在的目录") print("请指定1Remote日志文件所在的目录")
print("日志文件命名格式为: 1Remote.log_YYYYMMDD.md") print("日志文件命名格式为: 1Remote.log_YYYYMMDD.md")
print("="*50) print("="*50)
while True: while True:
log_dir = input("请输入日志目录完整路径: ").strip() log_dir = input("请输入日志目录完整路径: ").strip()
# 验证路径是否存在 # 验证路径是否存在
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
print(f"错误: 路径不存在 - {log_dir}") print(f"错误: 路径不存在 - {log_dir}")
continue continue
# 验证是否是目录 # 验证是否是目录
if not os.path.isdir(log_dir): if not os.path.isdir(log_dir):
print(f"错误: 这不是一个目录 - {log_dir}") print(f"错误: 这不是一个目录 - {log_dir}")
continue continue
# 验证目录中是否有日志文件 # 验证目录中是否有日志文件
log_files = [f for f in os.listdir(log_dir) log_files = [f for f in os.listdir(log_dir)
if f.startswith("1Remote.log_") and f.endswith(".md")] if f.startswith("1Remote.log_") and f.endswith(".md")]
if not log_files: if not log_files:
print(f"警告: 目录中没有找到日志文件 - {log_dir}") print(f"警告: 目录中没有找到日志文件 - {log_dir}")
choice = input("是否继续使用此目录? (y/n): ").strip().lower() choice = input("是否继续使用此目录? (y/n): ").strip().lower()
if choice != 'y': if choice != 'y':
continue continue
return log_dir return log_dir
def load_config(): def load_config():
"""从配置文件加载日志目录""" """从配置文件加载日志目录"""
if not os.path.exists(CONFIG_FILE): if not os.path.exists(CONFIG_FILE):
return None return None
try: try:
with open(CONFIG_FILE, 'r', encoding=ENCODING) as f: with open(CONFIG_FILE, 'r', encoding=ENCODING) as f:
config = json.load(f) config = json.load(f)
log_dir = config.get('log_dir') log_dir = config.get('log_dir')
# 验证目录是否存在 # 验证目录是否存在
if not os.path.isdir(log_dir): if not os.path.isdir(log_dir):
logging.error(f"配置文件中的目录不存在: {log_dir}") logging.error(f"配置文件中的目录不存在: {log_dir}")
return None return None
return log_dir return log_dir
except (json.JSONDecodeError, UnicodeDecodeError): except (json.JSONDecodeError, UnicodeDecodeError):
logging.error("配置文件损坏,请重新配置") logging.error("配置文件损坏,请重新配置")
return None return None
except Exception as e: except Exception as e:
logging.error(f"加载配置失败: {str(e)}") logging.error(f"加载配置失败: {str(e)}")
return None return None
def save_config(log_dir): def save_config(log_dir):
"""保存日志目录到配置文件""" """保存日志目录到配置文件"""
config = {'log_dir': log_dir} config = {'log_dir': log_dir}
try: try:
with open(CONFIG_FILE, 'w', encoding=ENCODING) as f: with open(CONFIG_FILE, 'w', encoding=ENCODING) as f:
json.dump(config, f, ensure_ascii=False, indent=4) json.dump(config, f, ensure_ascii=False, indent=4)
logging.info(f"配置已保存到: {CONFIG_FILE}") logging.info(f"配置已保存到: {CONFIG_FILE}")
return True return True
except Exception as e: except Exception as e:
logging.error(f"保存配置失败: {str(e)}") logging.error(f"保存配置失败: {str(e)}")
return False return False
def get_today_log_file(log_dir): def get_today_log_file(log_dir):
"""获取指定目录下当天的日志文件""" """获取指定目录下当天的日志文件"""
today = datetime.now().strftime("%Y%m%d") today = datetime.now().strftime("%Y%m%d")
file_pattern = f"1Remote.log_{today}.md" file_pattern = f"1Remote.log_{today}.md"
# 查找符合日期模式的文件 # 查找符合日期模式的文件
for entry in os.scandir(log_dir): for entry in os.scandir(log_dir):
if entry.is_file() and entry.name == file_pattern: if entry.is_file() and entry.name == file_pattern:
return entry.path return entry.path
return None return None
def execute_reconnect(): def execute_reconnect():
"""执行重连操作""" """执行重连操作"""
global reconnect_count, reconnect_attempts, reconnect_in_progress, reconnect_start_time, reconnect_success_detected global reconnect_count, reconnect_attempts, reconnect_in_progress, reconnect_start_time, reconnect_success_detected
# 如果已经达到最大尝试次数,重置并返回失败 # 如果已经达到最大尝试次数,重置并返回失败
if reconnect_attempts >= max_reconnect_attempts: if reconnect_attempts >= max_reconnect_attempts:
logging.warning(f"【达到最大重试次数】已尝试{max_reconnect_attempts}次重启,停止重试") logging.warning(f"【达到最大重试次数】已尝试{max_reconnect_attempts}次重启,停止重试")
reconnect_attempts = 0 reconnect_attempts = 0
reconnect_in_progress = False reconnect_in_progress = False
return False return False
try: try:
reconnect_script_path = os.path.join(SCRIPT_DIR, RECONNECT_SCRIPT_NAME) reconnect_script_path = os.path.join(SCRIPT_DIR, RECONNECT_SCRIPT_NAME)
# 记录开始时间 # 记录开始时间
start_time = time.time() start_time = time.time()
# 调用外部重启脚本 # 调用外部重启脚本
result = subprocess.run( result = subprocess.run(
[sys.executable, reconnect_script_path], [sys.executable, reconnect_script_path],
check=True, check=True,
capture_output=True, capture_output=True,
text=True, text=True,
timeout=30 timeout=30
) )
# 计算耗时 # 计算耗时
elapsed = time.time() - start_time elapsed = time.time() - start_time
# 重置成功标记,设置重启状态 # 重置成功标记,设置重启状态
reconnect_success_detected = False reconnect_success_detected = False
reconnect_in_progress = True reconnect_in_progress = True
reconnect_start_time = time.time() reconnect_start_time = time.time()
reconnect_attempts += 1 reconnect_attempts += 1
reconnect_count += 1 reconnect_count += 1
logging.info(f"【重启尝试 {reconnect_attempts}/{max_reconnect_attempts}】执行成功,耗时:{elapsed:.2f}s总重启次数{reconnect_count}") logging.info(f"【重启尝试 {reconnect_attempts}/{max_reconnect_attempts}】执行成功,耗时:{elapsed:.2f}s总重启次数{reconnect_count}")
return True return True
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
logging.error(f"【重启失败】错误代码:{e.returncode}\nstdout: {e.stdout.strip()}\nstderr: {e.stderr.strip()}") logging.error(f"【重启失败】错误代码:{e.returncode}\nstdout: {e.stdout.strip()}\nstderr: {e.stderr.strip()}")
except FileNotFoundError: except FileNotFoundError:
logging.error(f"【重启失败】未找到重启脚本:{reconnect_script_path}") logging.error(f"【重启失败】未找到重启脚本:{reconnect_script_path}")
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
logging.error(f"【重启失败】执行超时超过30秒") logging.error(f"【重启失败】执行超时超过30秒")
except Exception as e: except Exception as e:
logging.error(f"【重启异常】未知错误:{str(e)}", exc_info=True) logging.error(f"【重启异常】未知错误:{str(e)}", exc_info=True)
reconnect_attempts += 1 reconnect_attempts += 1
logging.warning(f"【重启尝试 {reconnect_attempts}/{max_reconnect_attempts}】失败") logging.warning(f"【重启尝试 {reconnect_attempts}/{max_reconnect_attempts}】失败")
return False return False
def process_log_line(line): def process_log_line(line):
"""处理日志行并检查是否需要重启及重启是否成功""" """处理日志行并检查是否需要重启及重启是否成功"""
global last_position, processed_times, reconnect_in_progress, reconnect_success_detected, reconnect_attempts global last_position, processed_times, reconnect_in_progress, reconnect_success_detected, reconnect_attempts
# 检查是否是重启成功的标志日志 # 检查是否是重启成功的标志日志
if reconnect_in_progress and 'Warning' in line and '[AxMsRdpClient09Host.cs(ReConn:42)]' in line and 'RDP Host: Call ReConn' in line: if reconnect_in_progress and 'Warning' in line and '[AxMsRdpClient09Host.cs(ReConn:42)]' in line and 'RDP Host: Call ReConn' in line:
logging.info(f"【重启成功验证】检测到成功标志: {line}") logging.info(f"【重启成功验证】检测到成功标志: {line}")
reconnect_success_detected = True reconnect_success_detected = True
reconnect_in_progress = False reconnect_in_progress = False
reconnect_attempts = 0 # 重置尝试次数 reconnect_attempts = 0 # 重置尝试次数
return True return True
# 原有逻辑:检查是否需要重启 # 原有逻辑:检查是否需要重启
if 'Warning' in line: if 'Warning' in line:
if 'OnRdpClientDisconnected' in line and not reconnect_in_progress: if 'OnRdpClientDisconnected' in line and not reconnect_in_progress:
logging.warning(f"【触发重启】{line}") logging.warning(f"【触发重启】{line}")
return execute_reconnect() return execute_reconnect()
elif 'ReConn:42' in line: elif 'ReConn:42' in line:
logging.info(f"【忽略信号】{line}") logging.info(f"【忽略信号】{line}")
else: else:
logging.info(f"【未知警告】{line}") logging.info(f"【未知警告】{line}")
# 时间戳处理 # 时间戳处理
try: try:
if '[' in line and ']' in line: if '[' in line and ']' in line:
timestamp_str = line.split(']')[0][1:] timestamp_str = line.split(']')[0][1:]
log_time = datetime.strptime(timestamp_str, "%H:%M:%S.%f").time() log_time = datetime.strptime(timestamp_str, "%H:%M:%S.%f").time()
time_key = timestamp_str.replace(':', '').replace('.', '') time_key = timestamp_str.replace(':', '').replace('.', '')
if time_key not in processed_times: if time_key not in processed_times:
processed_times[time_key] = True processed_times[time_key] = True
logging.info(f"【新警告记录】{line}") logging.info(f"【新警告记录】{line}")
except ValueError: except ValueError:
logging.error(f"【无效时间戳】{line}") logging.error(f"【无效时间戳】{line}")
return False return False
def monitor_log_file(log_file): def monitor_log_file(log_file):
"""监控指定的日志文件""" """监控指定的日志文件"""
global last_position, processed_times, last_processed_date global last_position, processed_times, last_processed_date
try: try:
# 检查是否为新的一天 # 检查是否为新的一天
current_date = datetime.now().date() current_date = datetime.now().date()
if current_date != last_processed_date: if current_date != last_processed_date:
processed_times.clear() processed_times.clear()
last_processed_date = current_date last_processed_date = current_date
logging.info(f"【新的一天】开始监控{current_date}的日志文件") logging.info(f"【新的一天】开始监控{current_date}的日志文件")
# 获取文件大小 # 获取文件大小
file_size = os.path.getsize(log_file) file_size = os.path.getsize(log_file)
# 从上次结束位置读取新内容 # 从上次结束位置读取新内容
with open(log_file, 'r', encoding=ENCODING) as f: with open(log_file, 'r', encoding=ENCODING) as f:
if file_size < last_position: if file_size < last_position:
logging.warning("【文件被截断或重置】重新从头开始读取") logging.warning("【文件被截断或重置】重新从头开始读取")
f.seek(0) f.seek(0)
else: else:
f.seek(last_position) f.seek(last_position)
new_lines = f.read().splitlines() new_lines = f.read().splitlines()
# 处理新增内容 # 处理新增内容
for line in new_lines: for line in new_lines:
process_log_line(line) process_log_line(line)
# 更新最后读取位置 # 更新最后读取位置
last_position = file_size last_position = file_size
except Exception as e: except Exception as e:
logging.error(f"【监控异常】{str(e)}", exc_info=True) logging.error(f"【监控异常】{str(e)}", exc_info=True)
class LogFileHandler(FileSystemEventHandler): class LogFileHandler(FileSystemEventHandler):
"""处理日志文件变化事件""" """处理日志文件变化事件"""
def __init__(self, log_dir): def __init__(self, log_dir):
self.log_dir = log_dir self.log_dir = log_dir
def on_modified(self, event): def on_modified(self, event):
global current_log_file global current_log_file
if not event.is_directory: if not event.is_directory:
# 检查是否是当天日志文件 # 检查是否是当天日志文件
today_file = get_today_log_file(self.log_dir) today_file = get_today_log_file(self.log_dir)
if today_file and event.src_path == today_file: if today_file and event.src_path == today_file:
monitor_log_file(today_file) monitor_log_file(today_file)
def start_monitoring(log_dir): def start_monitoring(log_dir):
"""启动日志监控(静默等待日志文件生成)""" """启动日志监控(静默等待日志文件生成)"""
global current_log_file, last_position, reconnect_in_progress, reconnect_start_time, reconnect_success_detected, reconnect_attempts global current_log_file, last_position, reconnect_in_progress, reconnect_start_time, reconnect_success_detected, reconnect_attempts
# 验证监控目录是否有效(仅在启动时检查一次) # 验证监控目录是否有效(仅在启动时检查一次)
if not os.path.isdir(log_dir): if not os.path.isdir(log_dir):
logging.error(f"【错误】监控目录不存在或无效: {log_dir}") logging.error(f"【错误】监控目录不存在或无效: {log_dir}")
return False return False
try: try:
while True: # 外层循环:处理跨天切换 while True: # 外层循环:处理跨天切换
# 静默等待当天日志文件生成每5秒检查一次 # 静默等待当天日志文件生成每5秒检查一次
while True: while True:
current_log_file = get_today_log_file(log_dir) current_log_file = get_today_log_file(log_dir)
if current_log_file: if current_log_file:
logging.info(f"【找到日志文件】开始监控: {current_log_file}") logging.info(f"【找到日志文件】开始监控: {current_log_file}")
last_position = os.path.getsize(current_log_file) # 初始化读取位置 last_position = os.path.getsize(current_log_file) # 初始化读取位置
break break
# 无日志文件时不输出错误,仅静默等待 # 无日志文件时不输出错误,仅静默等待
time.sleep(5) time.sleep(5)
# 创建事件处理器和观察器 # 创建事件处理器和观察器
event_handler = LogFileHandler(log_dir) event_handler = LogFileHandler(log_dir)
observer = Observer() observer = Observer()
observer.schedule(event_handler, path=log_dir, recursive=False) observer.schedule(event_handler, path=log_dir, recursive=False)
observer.start() observer.start()
# 监控循环每1秒检查一次是否跨天或重启超时 # 监控循环每1秒检查一次是否跨天或重启超时
try: try:
while True: while True:
# 检查重启超时 # 检查重启超时
if reconnect_in_progress: if reconnect_in_progress:
elapsed_time = time.time() - reconnect_start_time elapsed_time = time.time() - reconnect_start_time
if elapsed_time > 20: # 超过20秒 if elapsed_time > 20: # 超过20秒
if not reconnect_success_detected: if not reconnect_success_detected:
logging.warning(f"【重启验证超时】20秒内未检测到成功标志尝试重新启动") logging.warning(f"【重启验证超时】20秒内未检测到成功标志尝试重新启动")
# 停止当前监控,准备重试 # 停止当前监控,准备重试
observer.stop() observer.stop()
observer.join() observer.join()
# 如果未达到最大尝试次数,重新执行重启 # 如果未达到最大尝试次数,重新执行重启
if reconnect_attempts < max_reconnect_attempts: if reconnect_attempts < max_reconnect_attempts:
execute_reconnect() execute_reconnect()
else: else:
# 达到最大尝试次数,重置状态 # 达到最大尝试次数,重置状态
reconnect_in_progress = False reconnect_in_progress = False
reconnect_attempts = 0 reconnect_attempts = 0
# 重新启动监控 # 重新启动监控
event_handler = LogFileHandler(log_dir) event_handler = LogFileHandler(log_dir)
observer = Observer() observer = Observer()
observer.schedule(event_handler, path=log_dir, recursive=False) observer.schedule(event_handler, path=log_dir, recursive=False)
observer.start() observer.start()
else: else:
# 已检测到成功标志,重置状态 # 已检测到成功标志,重置状态
reconnect_in_progress = False reconnect_in_progress = False
# 检查是否跨天 # 检查是否跨天
today_file = get_today_log_file(log_dir) today_file = get_today_log_file(log_dir)
if today_file != current_log_file: if today_file != current_log_file:
# 跨天:停止当前监控,进入外层循环等待新文件 # 跨天:停止当前监控,进入外层循环等待新文件
observer.stop() observer.stop()
observer.join() observer.join()
logging.info(f"【跨天切换】当前日志文件已过期,等待新日志文件...") logging.info(f"【跨天切换】当前日志文件已过期,等待新日志文件...")
break break
time.sleep(1) # 缩短检查间隔为1秒提高响应速度 time.sleep(1) # 缩短检查间隔为1秒提高响应速度
except KeyboardInterrupt: except KeyboardInterrupt:
observer.stop() observer.stop()
observer.join() observer.join()
return True return True
observer.join() observer.join()
except Exception as e: except Exception as e:
logging.error(f"【监控异常】{str(e)}", exc_info=True) logging.error(f"【监控异常】{str(e)}", exc_info=True)
return False return False
if __name__ == "__main__": if __name__ == "__main__":
print("="*50) print("="*50)
print("日志监控程序") print("日志监控程序")
print("作者:火山") print("作者:火山")
print("="*50) print("="*50)
# 尝试加载配置 # 尝试加载配置
log_watch_dir = load_config() log_watch_dir = load_config()
if not log_watch_dir: if not log_watch_dir:
log_watch_dir = configure_log_directory() log_watch_dir = configure_log_directory()
if not save_config(log_watch_dir): if not save_config(log_watch_dir):
print("配置保存失败,程序退出") print("配置保存失败,程序退出")
sys.exit(1) sys.exit(1)
print("="*50) print("="*50)
print(f"监控目录: {log_watch_dir}") print(f"监控目录: {log_watch_dir}")
# 修改状态提示文本,更准确反映程序状态 # 修改状态提示文本,更准确反映程序状态
print("正在监控中(等待当天日志文件生成)...") print("正在监控中(等待当天日志文件生成)...")
try: try:
start_monitoring(log_watch_dir) start_monitoring(log_watch_dir)
print("监控已成功启动") print("监控已成功启动")
print("作者:火山") print("作者:火山")
except Exception as e: except Exception as e:
logging.error(f"【启动失败】{str(e)}", exc_info=True) logging.error(f"【启动失败】{str(e)}", exc_info=True)
print(f"监控启动失败: {str(e)}") print(f"监控启动失败: {str(e)}")
print("请检查错误日志: 监控异常.txt") print("请检查错误日志: 监控异常.txt")