Skip to content

Commit 595c010

Browse files
committed
use a log file for daemon
1 parent 4689f22 commit 595c010

File tree

6 files changed

+39
-159
lines changed

6 files changed

+39
-159
lines changed

Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import VPNLib
22

33
@MainActor
44
final class PreviewFileSync: FileSyncDaemon {
5+
var logFile: URL = .init(filePath: "~/log.txt")!
6+
57
var sessionState: [VPNLib.FileSyncSession] = []
68

79
var state: DaemonState = .running
810

9-
var recentLogs: [String] = []
10-
1111
init() {}
1212

1313
func refreshSessions() async {}

Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift

+5-3
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,12 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
8686
dontRetry = true
8787
}
8888
} message: {
89-
// You can't have styled text in alert messages
9089
Text("""
91-
File sync daemon failed: \(fileSync.state.description)\n\n\(fileSync.recentLogs.joined(separator: "\n"))
92-
""")
90+
File sync daemon failed. The daemon log file at\n\(fileSync.logFile.path)\nhas been opened.
91+
""").onAppear {
92+
// Open the log file in the default editor
93+
NSWorkspace.shared.open(fileSync.logFile)
94+
}
9395
}.task {
9496
// When the Window is visible, poll for session updates every
9597
// two seconds.

Coder-Desktop/Coder-DesktopTests/Util.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ class MockVPNService: VPNService, ObservableObject {
2929

3030
@MainActor
3131
class MockFileSyncDaemon: FileSyncDaemon {
32+
var logFile: URL = .init(filePath: "~/log.txt")
33+
3234
var sessionState: [VPNLib.FileSyncSession] = []
3335

3436
func refreshSessions() async {}
3537

36-
var recentLogs: [String] = []
37-
3838
func deleteSessions(ids _: [String]) async throws(VPNLib.DaemonError) {}
3939

4040
var state: VPNLib.DaemonState = .running

Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift

+30-25
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import SwiftUI
1010
public 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

Coder-Desktop/VPNLib/Util.swift

-36
Original file line numberDiff line numberDiff line change
@@ -29,39 +29,3 @@ public func makeNSError(suffix: String, code: Int = -1, desc: String) -> NSError
2929
userInfo: [NSLocalizedDescriptionKey: desc]
3030
)
3131
}
32-
33-
// Insertion-only RingBuffer for buffering the last `capacity` elements,
34-
// and retrieving them in insertion order.
35-
public struct RingBuffer<T> {
36-
private var buffer: [T?]
37-
private var start = 0
38-
private var size = 0
39-
40-
public init(capacity: Int) {
41-
buffer = Array(repeating: nil, count: capacity)
42-
}
43-
44-
public mutating func append(_ element: T) {
45-
let writeIndex = (start + size) % buffer.count
46-
buffer[writeIndex] = element
47-
48-
if size < buffer.count {
49-
size += 1
50-
} else {
51-
start = (start + 1) % buffer.count
52-
}
53-
}
54-
55-
public var elements: [T] {
56-
var result = [T]()
57-
result.reserveCapacity(size)
58-
for i in 0 ..< size {
59-
let index = (start + i) % buffer.count
60-
if let element = buffer[index] {
61-
result.append(element)
62-
}
63-
}
64-
65-
return result
66-
}
67-
}

Coder-Desktop/VPNLibTests/UtilTests.swift

-91
This file was deleted.

0 commit comments

Comments
 (0)