Files
Meshray-Manager/docs/问题排查报告.md
T
2026-06-30 15:14:37 +08:00

7.7 KiB

MeshRay 问题排查报告

📋 问题概述

在启动 meshray.exe 时,程序立即退出,没有任何错误信息输出。


🔍 已执行的排查步骤

1. 配置加载测试

测试方法: 创建 test_startup.go 测试程序
结果: 配置加载成功,端口 9531
结论: 配置文件无问题

2. 日志初始化测试

测试方法: 使用 zap.NewDevelopment() 初始化
结果: 日志初始化成功
结论: 日志系统无问题

3. 数据库连接测试

测试方法: 调用 store.New(cfg.Database.Path)
结果: 数据库初始化成功
结论: SQLite 数据库无问题

4. 服务启动测试

测试方法: 直接运行 meshray.exe
现象:

  • 输出系统信息(Hostname, OS 等)
  • 输出两次后程序立即退出
  • 无任何错误信息
  • 无 panic 堆栈

🐛 可能的问题点

问题 1: 系统托盘初始化失败

位置: cmd/meshray/main.go:98
代码:

if isInteractive {
    trayMgr := tray.NewTrayManager(p.logger, cfg.Server.Port, func() {
        // ...
    })
    trayMgr.Run()  // ← 可能在这里失败
}

分析:

  • 程序输出了两次系统信息,说明 run() 函数被调用了两次
  • trayMgr.Run() 可能是阻塞调用,但如果失败可能会立即返回
  • Windows 系统托盘可能需要特定的 COM 初始化

建议修复:

  1. 在 tray 包中添加错误处理和日志输出
  2. 添加超时机制
  3. 提供禁用系统托盘的选项

问题 2: API 服务器启动失败

位置: internal/api/server.go:486
代码:

return s.engine.Run(addr)

分析:

  • Gin 的 Run() 方法是阻塞调用
  • 如果端口被占用或绑定失败,应该返回错误
  • 但错误可能被 recover 捕获

建议修复:

  1. 检查端口是否被占用
  2. 添加更详细的错误日志
  3. 在 server.Run() 前输出调试信息

问题 3: Ctr 初始化失败

位置: internal/api/server.go:176
代码:

s.ctrClient, err = ctr.NewCtr("default", 1, &ctr.CtrConfig{}, s.logger)
if err != nil {
    s.logger.Error("初始化 meshray-ctr 失败", zap.Error(err))
    return fmt.Errorf("初始化 ctr 失败:%w", err)
}

分析:

  • Ctr 初始化涉及 WireGuard 和 Core 实例
  • 可能需要特定的系统权限或驱动
  • Windows 上可能缺少某些依赖

建议修复:

  1. 添加 Ctr 初始化的详细日志
  2. 提供 Ctr 可选配置(允许禁用)
  3. 检查 WireGuard 驱动是否安装

问题 4: DDNS Service 初始化失败

位置: internal/api/server.go:224
代码:

ddnsService, err := service.NewDDNSService(s.store.DB())
if err != nil {
    s.logger.Error("初始化 DDNSService 失败", zap.Error(err))
    return fmt.Errorf("初始化 DDNSService 失败:%w", err)
}

分析:

  • DDNSService 可能依赖外部 API 或网络
  • 网络连接问题可能导致初始化失败

建议修复:

  1. 添加 DDNSService 初始化的详细日志
  2. 提供 DDNS 可选配置(允许禁用)

🔧 建议的调试步骤

1. 添加详细日志输出

修改 cmd/meshray/main.go:

func (p *program) run(isInteractive bool) {
    fmt.Println("=== MeshRay 启动开始 ===")
    
    // 步骤 1: 加载配置
    fmt.Println("步骤 1: 加载配置...")
    cfg, err := config.Load("")
    if err != nil {
        log.Fatalf("❌ 配置加载失败:%v", err)
    }
    fmt.Println("✅ 配置加载成功")
    
    // 步骤 2: 初始化日志
    fmt.Println("步骤 2: 初始化日志...")
    logging.Init(logConfig)
    p.logger = logging.GetLogger()
    fmt.Println("✅ 日志初始化成功")
    
    // 步骤 3: 初始化数据库
    fmt.Println("步骤 3: 初始化数据库...")
    p.store, err = store.New(cfg.Database.Path)
    if err != nil {
        p.logger.Fatal("数据库初始化失败", zap.Error(err))
    }
    fmt.Println("✅ 数据库初始化成功")
    
    // 步骤 4: 创建 API 服务器
    fmt.Println("步骤 4: 创建 API 服务器...")
    server, err := api.NewServer(cfg, p.logger, p.store)
    if err != nil {
        p.logger.Fatal("创建服务器失败", zap.Error(err))
    }
    fmt.Println("✅ API 服务器创建成功")
    
    // 步骤 5: 启动 Web 服务器
    fmt.Println("步骤 5: 启动 Web 服务器...")
    go func() {
        if err := server.Run(); err != nil {
            p.logger.Fatal("MeshRay 运行失败", zap.Error(err))
        }
    }()
    
    // 等待 2 秒,确保服务器启动
    time.Sleep(2 * time.Second)
    fmt.Println("✅ Web 服务器启动成功")
    
    // 步骤 6: 启动系统托盘
    if isInteractive {
        fmt.Println("步骤 6: 启动系统托盘...")
        trayMgr := tray.NewTrayManager(p.logger, cfg.Server.Port, func() {
            // ...
        })
        if err := trayMgr.Run(); err != nil {
            p.logger.Error("系统托盘启动失败", zap.Error(err))
            // 继续运行,不退出
        }
    }
    
    fmt.Println("=== MeshRay 启动完成 ===")
    // ...
}

2. 添加端口检测

在启动前检查端口是否被占用:

func checkPortAvailable(port int) error {
    ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
    if err != nil {
        return fmt.Errorf("端口 %d 被占用:%w", port, err)
    }
    ln.Close()
    return nil
}

3. 提供简化启动模式

添加环境变量或命令行参数,禁用非必要功能:

# 禁用系统托盘
$env:MESHRAY_NO_TRAY="1"
.\meshray.exe

# 禁用 DDNS
$env:MESHRAY_NO_DDNS="1"
.\meshray.exe

# Debug 模式
$env:MESHRAY_DEBUG="1"
.\meshray.exe

📊 当前状态

组件 状态 说明
配置文件 正常 config.yaml 已创建
数据库 正常 meshray.db 存在且可连接
日志系统 ⚠️ 待验证 未看到实际日志输出
API 服务器 未知 未能启动到这一步
系统托盘 未知 可能的问题点
Ctr 模块 未知 可能依赖缺失
DDNS 模块 未知 可能网络问题

🎯 下一步行动

优先级 1 - 添加详细日志

  1. 修改 main.go 添加每个步骤的日志输出
  2. 在所有关键函数入口添加日志
  3. 确保错误能够正确输出

优先级 2 - 隔离问题

  1. 注释掉系统托盘代码
  2. 注释掉 DDNS 自动更新
  3. 最小化启动,只保留核心功能

优先级 3 - 环境检查

  1. 检查 WireGuard 驱动是否安装
  2. 检查端口 9531 是否可用
  3. 检查是否有防火墙阻止

📝 发现的 Bug

Bug 1: 错误处理不完善

问题: 程序启动失败但没有输出任何错误信息
影响: 无法定位问题
严重性: 🔴
建议: 在所有可能失败的地方添加日志输出

Bug 2: 缺少启动超时机制

问题: 如果某个步骤卡住,程序会一直等待
影响: 用户体验差
严重性: 🟡
建议: 添加启动超时(如 30 秒)

Bug 3: 缺少健康检查

问题: 无法快速判断服务是否正常启动
影响: 运维困难
建议: 实现 /health 端点并定期调用


🔒 安全建议

  1. JWT Secret: 应使用强随机数生成器
  2. 密码加密: 确认使用 bcrypt DefaultCost
  3. CORS 配置: 生产环境应限制来源
  4. 端口暴露: 建议使用防火墙限制访问

📈 性能建议

  1. 数据库连接池: 配置 SQLite 连接池大小
  2. 日志轮转: 确认 logrotate 配置正确
  3. 静态文件缓存: 启用浏览器缓存
  4. API 响应缓存: 对不常变的数据启用缓存

排查日期: 2026-03-20
排查人员: AI Assistant
当前状态: 🔴 问题定位中
文档版本: v1.0