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