Skip to content

Commit 12eb862

Browse files
committed
fix: LogRateLimiter
1 parent a61600c commit 12eb862

File tree

5 files changed

+69
-59
lines changed

5 files changed

+69
-59
lines changed

ClashX.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,6 @@
599599
49B445152457CDF000B27E3E /* ClashStatusTool.swift */,
600600
49D223382A1DA5F10002FFCB /* SSIDSuspendTool.swift */,
601601
49FEC6682AD9369C00BAD9F5 /* Command.swift */,
602-
0114F0CE2E5B60CB007C7AAC /* LogRateLimiter.swift */,
603602
);
604603
path = Utils;
605604
sourceTree = "<group>";
@@ -749,6 +748,7 @@
749748
children = (
750749
495A44D220D267D000888A0A /* LaunchAtLogin.swift */,
751750
496BDEDF21196F1E00C5207F /* Logger.swift */,
751+
0114F0CE2E5B60CB007C7AAC /* LogRateLimiter.swift */,
752752
4905A2C92A20841B00AEDA2E /* NSView+Layout.swift */,
753753
49B10869216A356D0064FFCE /* String+Extension.swift */,
754754
49ABB748236B0F9E00535CD7 /* UnsafePointer+bridge.swift */,

ClashX/Basic/LogRateLimiter.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import Foundation
2+
3+
actor LogRateLimiter {
4+
private let maxLogsCount: Int = 20000
5+
private let timeDuration: TimeInterval = 5.0
6+
private var logCount: Int = 0
7+
private var startTime: TimeInterval = CFAbsoluteTimeGetCurrent()
8+
private var lastTimeCheck: TimeInterval = CFAbsoluteTimeGetCurrent()
9+
private var isBlocked = false
10+
11+
private let onRateLimitTriggered: () -> Void
12+
13+
init(onRateLimitTriggered: @escaping () -> Void) {
14+
self.onRateLimitTriggered = onRateLimitTriggered
15+
}
16+
17+
// Returns true if log can be processed, false if rate limited
18+
func processLog() -> Bool {
19+
guard !isBlocked else { return false }
20+
21+
let now = CFAbsoluteTimeGetCurrent()
22+
23+
// Only check time and count every 1 second to reduce overhead
24+
if now - lastTimeCheck >= 1.0 {
25+
lastTimeCheck = now
26+
27+
// Reset counter if time window has passed
28+
if now - startTime >= timeDuration {
29+
startTime = now
30+
logCount = 0
31+
}
32+
33+
// Check if rate limit exceeded
34+
if logCount >= maxLogsCount {
35+
Task { await triggerRateLimit() }
36+
return false
37+
}
38+
}
39+
40+
logCount += 1
41+
return true
42+
}
43+
44+
private func triggerRateLimit() async {
45+
isBlocked = true
46+
47+
// Execute callback on main actor
48+
await MainActor.run {
49+
onRateLimitTriggered()
50+
Logger.log("⚠️ Rate limit triggered: >\(maxLogsCount) logs/\(timeDuration)sec, paused for 1min")
51+
}
52+
53+
// Resume after 60 seconds
54+
try? await Task.sleep(nanoseconds: 60_000_000_000)
55+
56+
isBlocked = false
57+
58+
await MainActor.run {
59+
Logger.log("✅ Rate limit resumed")
60+
}
61+
}
62+
}

ClashX/Basic/Logger.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Logger {
2121
dataFormatter.setLocalizedDateFormatFromTemplate("YYYY/MM/dd HH:mm:ss:SSS")
2222
fileLogger.logFormatter = DDLogFileFormatterDefault(dateFormatter: dataFormatter)
2323
fileLogger.rollingFrequency = TimeInterval(60 * 60 * 24) // 24 hours
24+
fileLogger.maximumFileSize = 5 * 1024 * 1024 // 5MB
2425
fileLogger.logFileManager.maximumNumberOfLogFiles = 3
2526
DDLog.add(fileLogger)
2627
dynamicLogLevel = ConfigManager.selectLoggingApiLevel.toDDLogLevel()

ClashX/General/ApiRequest.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -780,9 +780,11 @@ extension ApiRequest: WebSocketDelegate {
780780
delegate?.didUpdateTraffic(up: json["up"].intValue, down: json["down"].intValue)
781781
dashboardDelegate?.didUpdateTraffic(up: json["up"].intValue, down: json["down"].intValue)
782782
case loggingWebSocket:
783-
guard logRateLimiter.processLog() else { return }
784-
delegate?.didGetLog(log: json["payload"].stringValue, level: json["type"].string ?? "info")
785-
dashboardDelegate?.didGetLog(log: json["payload"].stringValue, level: json["type"].string ?? "info")
783+
Task {
784+
guard await logRateLimiter.processLog() else { return }
785+
delegate?.didGetLog(log: json["payload"].stringValue, level: json["type"].string ?? "info")
786+
dashboardDelegate?.didGetLog(log: json["payload"].stringValue, level: json["type"].string ?? "info")
787+
}
786788
case memoryWebSocket:
787789
delegate?.didUpdateMemory(memory: json["inuse"].int64Value)
788790
dashboardDelegate?.didUpdateMemory(memory: json["inuse"].int64Value)

ClashX/General/Utils/LogRateLimiter.swift

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)