feat(tools): 添加服务器日志下载脚本
- 从生产服务器下载 emotion-single.log 到本地 logs/server/ - 自动创建目录、备份旧日志、显示文件大小信息 - 使用 scp 命令,依赖已配置的 SSH 免密登录
This commit is contained in:
@@ -0,0 +1,167 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
服务器日志下载脚本
|
||||||
|
|
||||||
|
从生产服务器下载后端日志到本地 logs/server/ 目录
|
||||||
|
|
||||||
|
使用方法:
|
||||||
|
python tools/download-server-log.py
|
||||||
|
|
||||||
|
Author: Peanut
|
||||||
|
Created: 2026-06-03
|
||||||
|
Purpose: 从服务器下载 emotion-single.log 到本地,方便排查问题
|
||||||
|
"""
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 配置
|
||||||
|
# ============================================================================
|
||||||
|
SERVER_IP = "101.200.208.45"
|
||||||
|
USERNAME = "root"
|
||||||
|
REMOTE_LOG = "/data/logs/emotion-museum/emotion-single.log"
|
||||||
|
|
||||||
|
# 本地路径(项目根目录下的 logs/server/)
|
||||||
|
PROJECT_DIR = Path(__file__).parent.parent.absolute()
|
||||||
|
LOCAL_LOG_DIR = PROJECT_DIR / "logs" / "server"
|
||||||
|
LOCAL_LOG = LOCAL_LOG_DIR / "emotion-single.log"
|
||||||
|
BACKUP_LOG = LOCAL_LOG_DIR / "emotion-single.log.bak"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 日志输出
|
||||||
|
# ============================================================================
|
||||||
|
def log_info(msg):
|
||||||
|
print(f"\033[32m[INFO]\033[0m {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
def log_warn(msg):
|
||||||
|
print(f"\033[33m[WARN]\033[0m {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
def log_error(msg):
|
||||||
|
print(f"\033[31m[ERROR]\033[0m {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 核心逻辑
|
||||||
|
# ============================================================================
|
||||||
|
def check_ssh():
|
||||||
|
"""检查 SSH 连接是否正常"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10",
|
||||||
|
f"{USERNAME}@{SERVER_IP}", "echo ok"],
|
||||||
|
capture_output=True, text=True, timeout=15
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
log_info("SSH 连接正常")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
log_error(f"SSH 连接失败: {result.stderr.strip()}")
|
||||||
|
log_error(f"请确认已配置免密登录: ssh {USERNAME}@{SERVER_IP}")
|
||||||
|
return False
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
log_error("SSH 连接超时")
|
||||||
|
return False
|
||||||
|
except FileNotFoundError:
|
||||||
|
log_error("未找到 ssh 命令,请确认已安装 OpenSSH 客户端")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_file_size():
|
||||||
|
"""获取远程日志文件大小(字节)"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10",
|
||||||
|
f"{USERNAME}@{SERVER_IP}", f"stat -c %s {REMOTE_LOG} 2>/dev/null || echo -1"],
|
||||||
|
capture_output=True, text=True, timeout=15
|
||||||
|
)
|
||||||
|
size = int(result.stdout.strip())
|
||||||
|
return size if size > 0 else -1
|
||||||
|
except (subprocess.TimeoutExpired, ValueError, OSError) as e:
|
||||||
|
log_error(f"获取远程文件大小失败: {e}")
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
"""格式化文件大小"""
|
||||||
|
if size_bytes < 0:
|
||||||
|
return "未知"
|
||||||
|
for unit in ["B", "KB", "MB", "GB"]:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.1f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.1f} TB"
|
||||||
|
|
||||||
|
|
||||||
|
def backup_local_log():
|
||||||
|
"""备份本地旧日志文件"""
|
||||||
|
if LOCAL_LOG.exists():
|
||||||
|
shutil.copy2(LOCAL_LOG, BACKUP_LOG)
|
||||||
|
log_info(f"已备份旧日志: {BACKUP_LOG}")
|
||||||
|
else:
|
||||||
|
log_info("本地无旧日志,跳过备份")
|
||||||
|
|
||||||
|
|
||||||
|
def download_log():
|
||||||
|
"""下载远程日志文件"""
|
||||||
|
LOCAL_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
log_info(f"本地目录: {LOCAL_LOG_DIR}")
|
||||||
|
|
||||||
|
backup_local_log()
|
||||||
|
|
||||||
|
log_info(f"正在下载: {USERNAME}@{SERVER_IP}:{REMOTE_LOG}")
|
||||||
|
log_info(f" -> {LOCAL_LOG}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["scp", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10",
|
||||||
|
f"{USERNAME}@{SERVER_IP}:{REMOTE_LOG}", str(LOCAL_LOG)],
|
||||||
|
capture_output=True, text=True, timeout=300
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
local_size = LOCAL_LOG.stat().st_size
|
||||||
|
log_info(f"下载成功: {format_size(local_size)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
log_error(f"下载失败: {result.stderr.strip()}")
|
||||||
|
return False
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
log_error("下载超时(300 秒)")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"下载异常: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
log_info("=== 情绪博物馆 - 日志下载工具 ===")
|
||||||
|
log_info(f"服务器: {USERNAME}@{SERVER_IP}")
|
||||||
|
log_info(f"远程日志: {REMOTE_LOG}")
|
||||||
|
|
||||||
|
# 检查 SSH
|
||||||
|
if not check_ssh():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 检查远程文件
|
||||||
|
remote_size = get_remote_file_size()
|
||||||
|
if remote_size < 0:
|
||||||
|
log_error(f"远程日志文件不存在或无法访问: {REMOTE_LOG}")
|
||||||
|
sys.exit(1)
|
||||||
|
log_info(f"远程日志大小: {format_size(remote_size)}")
|
||||||
|
|
||||||
|
# 下载
|
||||||
|
if download_log():
|
||||||
|
log_info("=== 下载完成 ===")
|
||||||
|
else:
|
||||||
|
log_error("下载失败")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user