@@ -10,7 +10,7 @@ import SwiftUI
1010public protocol FileSyncDaemon : ObservableObject {
1111 var state : DaemonState { get }
1212 var sessionState : [ FileSyncSession ] { get }
13- var recentLogs : [ String ] { get }
13+ var logFile : URL { get }
1414 func tryStart( ) async
1515 func stop( ) async
1616 func refreshSessions( ) async
@@ -39,15 +39,13 @@ public class MutagenDaemon: FileSyncDaemon {
3939
4040 @Published public var sessionState : [ FileSyncSession ] = [ ]
4141
42- // We store the last N log lines to show in the UI if the daemon crashes
43- private var logBuffer : RingBuffer < String >
44- public var recentLogs : [ String ] { logBuffer. elements }
45-
4642 private var mutagenProcess : Subprocess ?
4743 private let mutagenPath : URL !
4844 private let mutagenDataDirectory : URL
4945 private let mutagenDaemonSocket : URL
5046
47+ public let logFile : URL
48+
5149 // Managing sync sessions could take a while, especially with prompting
5250 let sessionMgmtReqTimeout : TimeAmount = . seconds( 15 )
5351
@@ -64,13 +62,12 @@ public class MutagenDaemon: FileSyncDaemon {
6462 mutagenDataDirectory: URL = FileManager . default. urls (
6563 for: . applicationSupportDirectory,
6664 in: . userDomainMask
67- ) . first!. appending ( path: " Coder Desktop " ) . appending ( path: " Mutagen " ) ,
68- logBufferCapacity: Int = 10 )
65+ ) . first!. appending ( path: " Coder Desktop " ) . appending ( path: " Mutagen " ) )
6966 {
70- logBuffer = . init( capacity: logBufferCapacity)
7167 self . mutagenPath = mutagenPath
7268 self . mutagenDataDirectory = mutagenDataDirectory
7369 mutagenDaemonSocket = mutagenDataDirectory. appending ( path: " daemon " ) . appending ( path: " daemon.sock " )
70+ logFile = mutagenDataDirectory. appending ( path: " daemon.log " )
7471 // It shouldn't be fatal if the app was built without Mutagen embedded,
7572 // but file sync will be unavailable.
7673 if mutagenPath == nil {
@@ -113,34 +110,23 @@ public class MutagenDaemon: FileSyncDaemon {
113110
114111 // Creating the same process twice from Swift will crash the MainActor,
115112 // so we need to wait for an earlier process to die
116- if let waitForExit {
117- await waitForExit ( )
118- // We *need* to be sure the process is dead or the app ends up in an
119- // unrecoverable state
120- try ? await Task . sleep ( for: . seconds( 1 ) )
121- }
113+ await waitForExit ? ( )
122114
123115 await transition. wait ( )
124116 defer { transition. signal ( ) }
125117 logger. info ( " starting mutagen daemon " )
126118
127119 mutagenProcess = createMutagenProcess ( )
128- // swiftlint:disable:next large_tuple
129- let ( standardOutput, standardError, waitForExit) : ( Pipe . AsyncBytes , Pipe . AsyncBytes , @Sendable ( ) async -> Void )
120+ let ( standardError, waitForExit) : ( Pipe . AsyncBytes , @Sendable ( ) async -> Void )
130121 do {
131- ( standardOutput , standardError, waitForExit) = try mutagenProcess!. run ( )
122+ ( _ , standardError, waitForExit) = try mutagenProcess!. run ( )
132123 } catch {
133124 throw . daemonStartFailure( error)
134125 }
135126 self . waitForExit = waitForExit
136127
137128 Task {
138- await streamHandler ( io: standardOutput)
139- logger. info ( " standard output stream closed " )
140- }
141-
142- Task {
143- await streamHandler ( io: standardError)
129+ await handleDaemonLogs ( io: standardError)
144130 logger. info ( " standard error stream closed " )
145131 }
146132
@@ -283,11 +269,30 @@ public class MutagenDaemon: FileSyncDaemon {
283269 }
284270 }
285271
286- private func streamHandler( io: Pipe . AsyncBytes ) async {
272+ private func handleDaemonLogs( io: Pipe . AsyncBytes ) async {
273+ if !FileManager. default. fileExists ( atPath: logFile. path) {
274+ guard FileManager . default. createFile ( atPath: logFile. path, contents: nil ) else {
275+ logger. error ( " Failed to create log file " )
276+ return
277+ }
278+ }
279+
280+ guard let fileHandle = try ? FileHandle ( forWritingTo: logFile) else {
281+ logger. error ( " Failed to open log file for writing " )
282+ return
283+ }
284+
287285 for await line in io. lines {
288286 logger. info ( " \( line, privacy: . public) " )
289- logBuffer. append ( line)
287+
288+ do {
289+ try fileHandle. write ( contentsOf: Data ( " \( line) \n " . utf8) )
290+ } catch {
291+ logger. error ( " Failed to write to daemon log file: \( error) " )
292+ }
290293 }
294+
295+ try ? fileHandle. close ( )
291296 }
292297}
293298
0 commit comments