Skip to content

Commit 40f43d8

Browse files
democlaude
andcommitted
feat: v0.2.4 — per-day log files with auto-retention
Split the single rolling log into one file per day (~/Library/Logs/VPNMenuBar/vpnmenubar-YYYY-MM-DD.log) so individual files stay small and triaging by date is easy. Files older than 14 days are purged at launch. "Show Logs…" and Settings → Reveal in Finder always resolve to the current day's file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0b1ece8 commit 40f43d8

7 files changed

Lines changed: 80 additions & 35 deletions

File tree

VPNMenuBar-0.2.4.zip

1.69 MB
Binary file not shown.

VPNMenuBar.app/Contents/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
<key>CFBundlePackageType</key>
2424
<string>APPL</string>
2525
<key>CFBundleShortVersionString</key>
26-
<string>0.2.3</string>
26+
<string>0.2.4</string>
2727
<key>CFBundleSupportedPlatforms</key>
2828
<array>
2929
<string>MacOSX</string>
3030
</array>
3131
<key>CFBundleVersion</key>
32-
<string>5</string>
32+
<string>6</string>
3333
<key>DTCompiler</key>
3434
<string>com.apple.compilers.llvm.clang.1_0</string>
3535
<key>DTPlatformBuild</key>
18.4 KB
Binary file not shown.

VPNMenuBar/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
<key>CFBundlePackageType</key>
1818
<string>APPL</string>
1919
<key>CFBundleShortVersionString</key>
20-
<string>0.2.3</string>
20+
<string>0.2.4</string>
2121
<key>CFBundleVersion</key>
22-
<string>5</string>
22+
<string>6</string>
2323
<key>LSMinimumSystemVersion</key>
2424
<string>13.0</string>
2525
<key>LSUIElement</key>

VPNMenuBar/Util/AppLogger.swift

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,47 @@ enum AppLogLevel: String {
66
case error = "ERROR"
77
}
88

9-
/// File-based logger for user-facing diagnostics. Writes to
10-
/// `~/Library/Logs/VPNMenuBar/vpnmenubar.log` and also mirrors each line to
11-
/// `NSLog` so it remains visible in Console.app. Callers are responsible for
12-
/// redacting secrets before logging — this class does not inspect payloads.
9+
/// File-based logger for user-facing diagnostics. Writes per-day log files
10+
/// `~/Library/Logs/VPNMenuBar/vpnmenubar-YYYY-MM-DD.log` and mirrors each line
11+
/// to `NSLog` for Console.app. Files older than `retentionDays` are purged at
12+
/// launch. Callers are responsible for redacting secrets — this class does not
13+
/// inspect payloads.
1314
final class AppLogger {
1415
static let shared = AppLogger()
1516

1617
let logDirectory: URL
17-
let logFileURL: URL
1818

1919
private let queue = DispatchQueue(label: "com.example.vpnmenubar.applogger", qos: .utility)
20-
private let formatter: DateFormatter
21-
private let maxBytes: Int = 1_000_000
20+
private let timestampFormatter: DateFormatter
21+
private let dateFormatter: DateFormatter
22+
private let retentionDays: Int = 14
23+
private let filePrefix = "vpnmenubar-"
24+
private let fileSuffix = ".log"
2225

2326
private init() {
2427
let libraryLogs = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)
2528
.first!
2629
.appendingPathComponent("Logs/VPNMenuBar", isDirectory: true)
2730
self.logDirectory = libraryLogs
28-
self.logFileURL = libraryLogs.appendingPathComponent("vpnmenubar.log")
2931

30-
let f = DateFormatter()
31-
f.locale = Locale(identifier: "en_US_POSIX")
32-
f.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
33-
self.formatter = f
32+
let ts = DateFormatter()
33+
ts.locale = Locale(identifier: "en_US_POSIX")
34+
ts.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
35+
self.timestampFormatter = ts
36+
37+
let day = DateFormatter()
38+
day.locale = Locale(identifier: "en_US_POSIX")
39+
day.dateFormat = "yyyy-MM-dd"
40+
self.dateFormatter = day
3441

3542
try? FileManager.default.createDirectory(at: libraryLogs, withIntermediateDirectories: true)
36-
rotateIfNeeded()
43+
purgeOldLogs()
44+
}
45+
46+
/// URL of today's log file. Re-computed each call so consumers (menu /
47+
/// Settings "Reveal in Finder") always land on the current-day file.
48+
var logFileURL: URL {
49+
logDirectory.appendingPathComponent("\(filePrefix)\(dateFormatter.string(from: Date()))\(fileSuffix)")
3750
}
3851

3952
func info(_ message: String, file: String = #fileID, line: Int = #line) {
@@ -49,39 +62,51 @@ final class AppLogger {
4962
}
5063

5164
private func log(_ level: AppLogLevel, _ message: String, file: String, line: Int) {
52-
let ts = formatter.string(from: Date())
65+
let now = Date()
66+
let ts = timestampFormatter.string(from: now)
5367
let short = file.split(separator: "/").last.map(String.init) ?? file
5468
let entry = "\(ts) [\(level.rawValue)] \(short):\(line) \(message)\n"
5569
NSLog("VPNMenuBar: [%{public}@] %{public}@", level.rawValue, message)
5670
queue.async { [weak self] in
57-
self?.appendToFile(entry)
71+
self?.appendToFile(entry, at: now)
5872
}
5973
}
6074

61-
private func appendToFile(_ entry: String) {
75+
private func appendToFile(_ entry: String, at date: Date) {
6276
guard let data = entry.data(using: .utf8) else { return }
77+
let url = logDirectory.appendingPathComponent("\(filePrefix)\(dateFormatter.string(from: date))\(fileSuffix)")
6378
let fm = FileManager.default
64-
if !fm.fileExists(atPath: logFileURL.path) {
65-
try? data.write(to: logFileURL)
79+
if !fm.fileExists(atPath: url.path) {
80+
try? data.write(to: url)
6681
return
6782
}
68-
guard let handle = try? FileHandle(forWritingTo: logFileURL) else { return }
83+
guard let handle = try? FileHandle(forWritingTo: url) else { return }
6984
defer { try? handle.close() }
7085
_ = try? handle.seekToEnd()
7186
do { try handle.write(contentsOf: data) } catch { /* best-effort */ }
7287
}
7388

74-
/// Rotate the log when it exceeds `maxBytes`. Keeps a single `.1` archive
75-
/// (overwritten each rotation) — this is a personal-use menu-bar app, no
76-
/// need for N-generation retention.
77-
private func rotateIfNeeded() {
89+
/// Delete per-day log files older than `retentionDays`. Called once at
90+
/// launch — running apps are expected to restart occasionally, and the
91+
/// disk cost of one extra old file in a day is negligible.
92+
private func purgeOldLogs() {
7893
let fm = FileManager.default
79-
guard let attrs = try? fm.attributesOfItem(atPath: logFileURL.path),
80-
let size = attrs[.size] as? Int, size > maxBytes else {
81-
return
94+
guard let entries = try? fm.contentsOfDirectory(
95+
at: logDirectory,
96+
includingPropertiesForKeys: nil,
97+
options: [.skipsHiddenFiles]
98+
) else { return }
99+
let cutoff = Calendar.current.date(byAdding: .day, value: -retentionDays, to: Date())
100+
?? Date(timeIntervalSinceNow: -Double(retentionDays) * 86_400)
101+
let cutoffDay = dateFormatter.string(from: cutoff)
102+
for url in entries {
103+
let name = url.lastPathComponent
104+
guard name.hasPrefix(filePrefix), name.hasSuffix(fileSuffix) else { continue }
105+
let datePart = String(name.dropFirst(filePrefix.count).dropLast(fileSuffix.count))
106+
// Lexicographic compare works because dateFormat is yyyy-MM-dd.
107+
if datePart < cutoffDay {
108+
try? fm.removeItem(at: url)
109+
}
82110
}
83-
let backup = logFileURL.deletingPathExtension().appendingPathExtension("1.log")
84-
try? fm.removeItem(at: backup)
85-
try? fm.moveItem(at: logFileURL, to: backup)
86111
}
87112
}

appcast.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@
55
<link>https://github.com/CoderZCC/VPNMenuBar</link>
66
<description>VPN MenuBar update feed</description>
77
<language>en</language>
8+
<item>
9+
<title>Version 0.2.4</title>
10+
<sparkle:version>6</sparkle:version>
11+
<sparkle:shortVersionString>0.2.4</sparkle:shortVersionString>
12+
<sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion>
13+
<description><![CDATA[
14+
<ul>
15+
<li>Log files are now split by day — <code>~/Library/Logs/VPNMenuBar/vpnmenubar-YYYY-MM-DD.log</code> — so individual files stay small and are easy to pick through by date when reporting issues</li>
16+
<li>Logs older than 14 days are auto-purged at launch, no disk buildup</li>
17+
<li>"Show Logs…" and Settings → Reveal in Finder now always land on the current day's file</li>
18+
</ul>
19+
]]></description>
20+
<pubDate>Fri, 24 Apr 2026 17:10:00 +0800</pubDate>
21+
<enclosure
22+
url="https://github.com/CoderZCC/VPNMenuBar/releases/download/v0.2.4/VPNMenuBar-0.2.4.zip"
23+
type="application/octet-stream"
24+
sparkle:edSignature="9NWK8B2jxM1p+nDLrabtX8+G27LTrDKv4VWoJiouAhu+gw7GTPd1rH1BNN76riTtz4DrXBsKAfehRlL0mXWEDA=="
25+
length="1776624"
26+
/>
27+
</item>
828
<item>
929
<title>Version 0.2.3</title>
1030
<sparkle:version>5</sparkle:version>

project.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ targets:
3636
properties:
3737
CFBundleName: VPNMenuBar
3838
CFBundleDisplayName: VPN MenuBar
39-
CFBundleShortVersionString: "0.2.3"
40-
CFBundleVersion: "5"
39+
CFBundleShortVersionString: "0.2.4"
40+
CFBundleVersion: "6"
4141
LSMinimumSystemVersion: "13.0"
4242
LSUIElement: true
4343
NSUserNotificationAlertStyle: alert

0 commit comments

Comments
 (0)