mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 10:13:54 +00:00
主窗口诊断区(spec #21 D5/D11): - RuntimeLog 增加事件接收缝,GUI 侧接入 RuntimeEventStore 内存环形缓冲 (容量 500),wrapper 侧不变(stderr → agent.log 收敛) - 主窗口侧栏新增「服务器/诊断」分区切换;诊断区日志查看器双源:GUI 事件 实时呈现 + agent.log 尾部(1s 轮询),均可复制 - DiagnosticReportBuilder 显式触发的脱敏诊断导出:只含状态、存在性、权限、 大小/时间、数量与脱敏元数据;不读 Keychain 与契约/目录内容;原始错误 detail 不入导出,家目录前缀改写为「~」;服务器地址与备注不进入任何输出 - 投毒夹具测试验证导出不含任何敏感类目(密码/插件参数/地址/备注/token/JSON) Closes #34
57 lines
1.8 KiB
Swift
57 lines
1.8 KiB
Swift
import Foundation
|
||
|
||
/// GUI 侧运行事件环形缓冲(spec #21 D5,issue #34):收集 `RuntimeLog.emit`
|
||
/// 的事件供主窗口日志查看器实时呈现与诊断导出取用。事件文本来自封闭枚举的
|
||
/// 渲染,构造上不含敏感值;容量封顶只留最近事件(够诊断即可,不做复杂轮转)。
|
||
/// wrapper 进程不使用本存储(其 stderr 已收敛进 agent.log)。
|
||
final class RuntimeEventStore: RuntimeEventSink, @unchecked Sendable {
|
||
struct Entry: Equatable, Identifiable {
|
||
let id: Int
|
||
let timestamp: Date
|
||
let text: String
|
||
|
||
/// 查看器与导出共用的行格式;固定 POSIX locale 保证报障对照不随用户
|
||
/// 区域漂移。
|
||
var renderedLine: String {
|
||
"\(Self.timestampFormatter.string(from: timestamp)) \(text)"
|
||
}
|
||
|
||
/// DateFormatter 在现代 Foundation 中线程安全;本格式器只被读取。
|
||
static let timestampFormatter: DateFormatter = {
|
||
let formatter = DateFormatter()
|
||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
||
return formatter
|
||
}()
|
||
}
|
||
|
||
/// 进程级共享实例:GUI 启动时注册为 RuntimeLog 的接收缝。
|
||
static let shared = RuntimeEventStore()
|
||
|
||
private let lock = NSLock()
|
||
private var entries: [Entry] = []
|
||
private var nextID = 0
|
||
private let capacity: Int
|
||
|
||
init(capacity: Int = 500) {
|
||
self.capacity = max(1, capacity)
|
||
}
|
||
|
||
/// 当前缓冲快照(旧→新)。
|
||
var snapshot: [Entry] {
|
||
lock.lock()
|
||
defer { lock.unlock() }
|
||
return entries
|
||
}
|
||
|
||
func append(text: String, timestamp: Date = Date()) {
|
||
lock.lock()
|
||
defer { lock.unlock() }
|
||
entries.append(Entry(id: nextID, timestamp: timestamp, text: text))
|
||
nextID += 1
|
||
if entries.count > capacity {
|
||
entries.removeFirst(entries.count - capacity)
|
||
}
|
||
}
|
||
}
|