// log.dart — 桌面端统一日志出口。 // 格式: `[时:分:秒.毫秒] [tag] msg`,全部走 logLine(),便于排查时序。 // 注:sing-box 自身时间戳已在下发配置里关闭(log.timestamp=false), // 避免与这里的前缀重复打印两个时间。 // // 除 stdout 外,桌面端还把日志落到固定文件(每次启动截断重写),便于在 GUI/管理员 // 启动、无法用控制台 Tee 抓 stdout 时排障: // Windows: %LOCALAPPDATA%\Pangolin\pangolin.log // macOS/Linux: ~/.pangolin/pangolin.log // ignore_for_file: avoid_print import 'dart:io'; String _ts() { final n = DateTime.now(); String p(int v, [int w = 2]) => v.toString().padLeft(w, '0'); return '${p(n.hour)}:${p(n.minute)}:${p(n.second)}.${p(n.millisecond, 3)}'; } File? _logFile; bool _logInit = false; File? _openLogFile() { try { String? base; String sep = '/'; String sub = '.pangolin'; if (Platform.isWindows) { base = Platform.environment['LOCALAPPDATA']; sep = '\\'; sub = 'Pangolin'; } else if (Platform.isMacOS || Platform.isLinux) { base = Platform.environment['HOME']; } else { return null; // 移动端不落文件 } if (base == null || base.isEmpty) return null; final dir = Directory('$base$sep$sub'); if (!dir.existsSync()) dir.createSync(recursive: true); final f = File('${dir.path}${sep}pangolin.log'); f.writeAsStringSync('=== session ${DateTime.now()} ===\n'); // 每次启动截断重写 return f; } catch (_) { return null; } } /// 打印一行带时间戳与标签的桌面端日志(stdout + 桌面端日志文件)。 void logLine(String tag, String msg) { final line = '[${_ts()}] [$tag] $msg'; print(line); try { if (!_logInit) { _logInit = true; _logFile = _openLogFile(); } _logFile?.writeAsStringSync('$line\n', mode: FileMode.append); } catch (_) { // 落盘失败不影响运行。 } }