From 5537b8cfe7efe5efe37d0de3008ca4df6872d0a2 Mon Sep 17 00:00:00 2001 From: Jamil Date: Fri, 20 Jun 2025 10:03:59 -0700 Subject: [PATCH] fix(apple): ensure log file exists before writing to it (#9597) Similar to the issue for the gui clients, the log file handle needs to be able to be rolled over after logs are cleared. related: #6850 --- .../Firezone/Application/FirezoneApp.swift | 2 +- .../Sources/FirezoneKit/Helpers/Log.swift | 46 ++++++++++++++++++- .../FirezoneKit/Models/Configuration.swift | 8 ++-- .../Models/SessionNotification.swift | 2 +- .../FirezoneKit/Views/GrantVPNView.swift | 2 +- .../Sources/FirezoneKit/Views/MenuBar.swift | 2 +- website/src/components/Changelog/Apple.tsx | 7 ++- 7 files changed, 58 insertions(+), 11 deletions(-) diff --git a/swift/apple/Firezone/Application/FirezoneApp.swift b/swift/apple/Firezone/Application/FirezoneApp.swift index e17b0bec0..c4e577632 100644 --- a/swift/apple/Firezone/Application/FirezoneApp.swift +++ b/swift/apple/Firezone/Application/FirezoneApp.swift @@ -5,8 +5,8 @@ // import FirezoneKit -import SwiftUI import Sentry +import SwiftUI @main struct FirezoneApp: App { diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Helpers/Log.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Helpers/Log.swift index f07bea33e..ca5e87f37 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Helpers/Log.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Helpers/Log.swift @@ -136,9 +136,11 @@ private final class LogWriter { // All log writes happen in the workQueue private let workQueue: DispatchQueue private let logger: Logger - private let handle: FileHandle + private var handle: FileHandle private let dateFormatter: ISO8601DateFormatter private let jsonEncoder: JSONEncoder + private let folderURL: URL // Add this to store folder URL + private var currentLogFileURL: URL // Add this to track current file init?(folderURL: URL?, logger: Logger) { let fileManager = FileManager.default @@ -159,11 +161,15 @@ private final class LogWriter { return nil } + self.folderURL = folderURL // Store folder URL + let logFileURL = folderURL .appendingPathComponent(dateFormatter.string(from: Date())) .appendingPathExtension("jsonl") + self.currentLogFileURL = logFileURL // Store current file URL + // Create log file guard fileManager.createFile(atPath: logFileURL.path, contents: Data()), let handle = try? FileHandle(forWritingTo: logFileURL), @@ -185,6 +191,37 @@ private final class LogWriter { } } + // Returns a valid file handle, recreating file if necessary + private func ensureFileExists() -> FileHandle? { + let fileManager = FileManager.default + + // Check if current file still exists + if fileManager.fileExists(atPath: currentLogFileURL.path) { + return handle + } + + // File was deleted, need to recreate + try? handle.close() + + // Ensure directory exists + guard SharedAccess.ensureDirectoryExists(at: folderURL.path) else { + logger.error("Could not recreate log directory") + return nil + } + + // Create new log file + guard fileManager.createFile(atPath: currentLogFileURL.path, contents: Data()), + let newHandle = try? FileHandle(forWritingTo: currentLogFileURL), + (try? newHandle.seekToEnd()) != nil + else { + logger.error("Could not recreate log file: \(self.currentLogFileURL.path)") + return nil + } + + self.handle = newHandle + return newHandle + } + func write(severity: Severity, message: String) { let logEntry = LogEntry( time: dateFormatter.string(from: Date()), @@ -200,7 +237,12 @@ private final class LogWriter { jsonData.append(Data("\n".utf8)) workQueue.async { [weak self] in - self?.handle.write(jsonData) + guard let self = self else { return } + + // Get valid handle, recreating file if necessary + if let handle = self.ensureFileExists() { + handle.write(jsonData) + } } } } diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/Configuration.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/Configuration.swift index 949b77e29..3e47ba129 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/Configuration.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/Configuration.swift @@ -143,10 +143,10 @@ public class Configuration: ObservableObject { // so this feature only enabled for macOS 13 and higher given the tiny Firezone installbase for macOS 12. func updateAppService() async throws { if #available(macOS 13.0, *) { - // Getting the status initially appears to be blocking sometimes - SentrySDK.pauseAppHangTracking() - defer { SentrySDK.resumeAppHangTracking() } - let status = SMAppService.mainApp.status + // Getting the status initially appears to be blocking sometimes + SentrySDK.pauseAppHangTracking() + defer { SentrySDK.resumeAppHangTracking() } + let status = SMAppService.mainApp.status if !startOnLogin, status == .enabled { try await SMAppService.mainApp.unregister() diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/SessionNotification.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/SessionNotification.swift index 0851911f3..5d11af9e1 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/SessionNotification.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Models/SessionNotification.swift @@ -5,8 +5,8 @@ // import Foundation -import UserNotifications import Sentry +import UserNotifications #if os(macOS) import AppKit diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/GrantVPNView.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/GrantVPNView.swift index a8bdbc7ce..257ece50e 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/GrantVPNView.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/GrantVPNView.swift @@ -6,8 +6,8 @@ // import Combine -import SwiftUI import Sentry +import SwiftUI #if os(macOS) import SystemExtensions diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift index e85529031..e205b8cc9 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Views/MenuBar.swift @@ -11,8 +11,8 @@ import Combine import Foundation import NetworkExtension import OSLog -import SwiftUI import Sentry +import SwiftUI #if os(macOS) @MainActor diff --git a/website/src/components/Changelog/Apple.tsx b/website/src/components/Changelog/Apple.tsx index 0a2e3dc2f..ef058fb65 100644 --- a/website/src/components/Changelog/Apple.tsx +++ b/website/src/components/Changelog/Apple.tsx @@ -24,7 +24,12 @@ export default function Apple() { return ( {/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */} - + + + Fixes an issue where certain log files would not be recreated after + logs were cleared. + + Fixes an issue where connections would fail to establish if both