diff --git a/client/macos/PacketTunnel/PacketTunnelProvider.swift b/client/macos/PacketTunnel/PacketTunnelProvider.swift index f1cd590..f608c8a 100644 --- a/client/macos/PacketTunnel/PacketTunnelProvider.swift +++ b/client/macos/PacketTunnel/PacketTunnelProvider.swift @@ -130,6 +130,11 @@ final class PacketTunnelProvider: NEPacketTunnelProvider { experimental["cache_file"] = ["enabled": true] } root["experimental"] = experimental + // 诊断:开 debug 日志,让 urltest 探测的失败原因(DNS/连接/超时)出现在内核日志里, + // 由 StatsCollector 的 Log 客户端抓取透出。 + var logCfg = (root["log"] as? [String: Any]) ?? [:] + logCfg["level"] = "debug" + root["log"] = logCfg guard let data = try? JSONSerialization.data(withJSONObject: root), let out = String(data: data, encoding: .utf8) else { return configJSON } return out @@ -374,6 +379,7 @@ private func simpleError(_ msg: String) -> NSError { final class StatsCollector: NSObject { private let queue = DispatchQueue(label: "pangolin.stats.collector") private var statusClient: LibboxCommandClient? + private var logClient: LibboxCommandClient? private var started = false private var clashTimer: DispatchSourceTimer? @@ -396,23 +402,24 @@ final class StatsCollector: NSObject { guard let self, !self.started else { return } self.clashBase = "http://127.0.0.1:\(clashPort)" self.clashSecret = clashSecret - self.statusClient = self.connectStatusClient() + self.statusClient = self.connectCommand(LibboxCommandStatus, "status") + self.logClient = self.connectCommand(LibboxCommandLog, "log") // 抓内核 urltest 报错 self.started = true self.startClashTimer() } } - /// libbox Status client(取速率)。command.sock 刚建好可能稍滞后,重试。 - private func connectStatusClient() -> LibboxCommandClient? { + /// 为单一命令开一个 libbox client 并连上。command.sock 刚建好可能稍滞后,重试。 + private func connectCommand(_ command: Int32, _ label: String) -> LibboxCommandClient? { let options = LibboxCommandClientOptions() options.statusInterval = 1_000_000_000 // 1s - options.addCommand(LibboxCommandStatus) + options.addCommand(command) guard let c = LibboxNewCommandClient(self, options) else { return nil } for _ in 0..<20 { - do { try c.connect(); NSLog("[pangolin/collector] status connected"); return c } + do { try c.connect(); NSLog("[pangolin/collector] %@ connected", label); return c } catch { Thread.sleep(forTimeInterval: 0.25) } } - NSLog("[pangolin/collector] status gave up") + NSLog("[pangolin/collector] %@ gave up", label) return nil } @@ -421,7 +428,9 @@ final class StatsCollector: NSObject { guard let self else { return } self.clashTimer?.cancel(); self.clashTimer = nil if let c = self.statusClient { try? c.disconnect() } + if let c = self.logClient { try? c.disconnect() } self.statusClient = nil + self.logClient = nil self.started = false self.lock.lock() self.latest = StatsCollector.zero @@ -564,7 +573,19 @@ extension StatsCollector: LibboxCommandClientHandlerProtocol { func connected() {} func disconnected(_ message: String?) {} func clearLogs() {} - func writeLogs(_ messageList: (any LibboxLogIteratorProtocol)?) {} + func writeLogs(_ messageList: (any LibboxLogIteratorProtocol)?) { + guard let it = messageList else { return } + while it.hasNext() { + guard let entry = it.next() else { break } + let msg = entry.message + let low = msg.lowercased() + // 只抓 urltest 探测相关行(含失败原因:DNS/连接/超时)。 + guard low.contains("urltest") || low.contains("url-test") + || low.contains("gstatic") || low.contains("generate_204") else { continue } + NSLog("[pangolin/klog] %@", msg) + lock.lock(); latest["klog"] = String(msg.prefix(180)); lock.unlock() + } + } func setDefaultLogLevel(_ level: Int32) {} func initializeClashMode(_ modeList: (any LibboxStringIteratorProtocol)?, currentMode: String?) {} func updateClashMode(_ newMode: String?) {} diff --git a/client/macos/Runner.xcodeproj/project.pbxproj b/client/macos/Runner.xcodeproj/project.pbxproj index a9206ec..b3a255d 100644 --- a/client/macos/Runner.xcodeproj/project.pbxproj +++ b/client/macos/Runner.xcodeproj/project.pbxproj @@ -659,7 +659,7 @@ baseConfigurationReference = C57BBFD43F9175D1E23685EF /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests; @@ -674,7 +674,7 @@ baseConfigurationReference = F8904897A48DC81799B8752E /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests; @@ -689,7 +689,7 @@ baseConfigurationReference = B21E68FC1F5D33DD67A0DF5E /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests; @@ -960,7 +960,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = BYL4KQHMTN; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -1010,7 +1010,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = BYL4KQHMTN; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -1059,7 +1059,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = BYL4KQHMTN; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; diff --git a/client/macos/Runner/StatsClient.swift b/client/macos/Runner/StatsClient.swift index e5b190a..2a91213 100644 --- a/client/macos/Runner/StatsClient.swift +++ b/client/macos/Runner/StatsClient.swift @@ -67,9 +67,9 @@ final class StatsClient: NSObject { if self.diagTick % 5 == 0 { let urls = (obj["urltestResults"] as? [[String: Any]]) ?? [] let pairs = urls.map { "\($0["tag"] ?? "?")=\($0["delayMs"] ?? -1)" }.joined(separator: ",") - NSLog("[pangolin/stats] dl=%@ ul=%@ urltest[%d]=%@ diag=%@", + NSLog("[pangolin/stats] dl=%@ ul=%@ urltest[%d]=%@ diag=%@ klog=%@", "\(obj["downloadSpeed"] ?? 0)", "\(obj["uploadSpeed"] ?? 0)", urls.count, pairs, - "\(obj["diag"] ?? "-")") + "\(obj["diag"] ?? "-")", "\(obj["klog"] ?? "-")") } self.emit(obj) }