@@ -10,7 +10,7 @@ import SwiftUI
10
10
public protocol FileSyncDaemon : ObservableObject {
11
11
var state : DaemonState { get }
12
12
var sessionState : [ FileSyncSession ] { get }
13
- var recentLogs : [ String ] { get }
13
+ var logFile : URL { get }
14
14
func tryStart( ) async
15
15
func stop( ) async
16
16
func refreshSessions( ) async
@@ -39,15 +39,13 @@ public class MutagenDaemon: FileSyncDaemon {
39
39
40
40
@Published public var sessionState : [ FileSyncSession ] = [ ]
41
41
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
-
46
42
private var mutagenProcess : Subprocess ?
47
43
private let mutagenPath : URL !
48
44
private let mutagenDataDirectory : URL
49
45
private let mutagenDaemonSocket : URL
50
46
47
+ public let logFile : URL
48
+
51
49
// Managing sync sessions could take a while, especially with prompting
52
50
let sessionMgmtReqTimeout : TimeAmount = . seconds( 15 )
53
51
@@ -64,13 +62,12 @@ public class MutagenDaemon: FileSyncDaemon {
64
62
mutagenDataDirectory: URL = FileManager . default. urls (
65
63
for: . applicationSupportDirectory,
66
64
in: . userDomainMask
67
- ) . first!. appending ( path: " Coder Desktop " ) . appending ( path: " Mutagen " ) ,
68
- logBufferCapacity: Int = 10 )
65
+ ) . first!. appending ( path: " Coder Desktop " ) . appending ( path: " Mutagen " ) )
69
66
{
70
- logBuffer = . init( capacity: logBufferCapacity)
71
67
self . mutagenPath = mutagenPath
72
68
self . mutagenDataDirectory = mutagenDataDirectory
73
69
mutagenDaemonSocket = mutagenDataDirectory. appending ( path: " daemon " ) . appending ( path: " daemon.sock " )
70
+ logFile = mutagenDataDirectory. appending ( path: " daemon.log " )
74
71
// It shouldn't be fatal if the app was built without Mutagen embedded,
75
72
// but file sync will be unavailable.
76
73
if mutagenPath == nil {
@@ -113,34 +110,23 @@ public class MutagenDaemon: FileSyncDaemon {
113
110
114
111
// Creating the same process twice from Swift will crash the MainActor,
115
112
// 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 ? ( )
122
114
123
115
await transition. wait ( )
124
116
defer { transition. signal ( ) }
125
117
logger. info ( " starting mutagen daemon " )
126
118
127
119
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 )
130
121
do {
131
- ( standardOutput , standardError, waitForExit) = try mutagenProcess!. run ( )
122
+ ( _ , standardError, waitForExit) = try mutagenProcess!. run ( )
132
123
} catch {
133
124
throw . daemonStartFailure( error)
134
125
}
135
126
self . waitForExit = waitForExit
136
127
137
128
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)
144
130
logger. info ( " standard error stream closed " )
145
131
}
146
132
@@ -283,11 +269,30 @@ public class MutagenDaemon: FileSyncDaemon {
283
269
}
284
270
}
285
271
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
+
287
285
for await line in io. lines {
288
286
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
+ }
290
293
}
294
+
295
+ try ? fileHandle. close ( )
291
296
}
292
297
}
293
298
0 commit comments