1717using Coder . Desktop . Vpn . Utilities ;
1818using Grpc . Core ;
1919using Microsoft . Extensions . Options ;
20+ using Microsoft . Extensions . Logging ;
21+ using Serilog ;
2022using DaemonTerminateRequest = Coder . Desktop . MutagenSdk . Proto . Service . Daemon . TerminateRequest ;
2123using MutagenProtocol = Coder . Desktop . MutagenSdk . Proto . Url . Protocol ;
2224using SynchronizationTerminateRequest = Coder . Desktop . MutagenSdk . Proto . Service . Synchronization . TerminateRequest ;
25+ using Microsoft . Extensions . Hosting ;
2326
2427namespace Coder . Desktop . App . Services ;
2528
@@ -113,6 +116,8 @@ public sealed class MutagenController : ISyncSessionController
113116 // Protects all private non-readonly class members.
114117 private readonly RaiiSemaphoreSlim _lock = new ( 1 , 1 ) ;
115118
119+ private readonly ILogger < MutagenController > _logger ;
120+
116121 private readonly CancellationTokenSource _stateUpdateCts = new ( ) ;
117122 private Task ? _stateUpdateTask ;
118123
@@ -142,15 +147,19 @@ public sealed class MutagenController : ISyncSessionController
142147
143148 private string MutagenDaemonLog => Path . Combine ( _mutagenDataDirectory , "daemon.log" ) ;
144149
145- public MutagenController ( IOptions < MutagenControllerConfig > config )
150+ public MutagenController ( IOptions < MutagenControllerConfig > config , ILogger < MutagenController > logger )
146151 {
147152 _mutagenExecutablePath = config . Value . MutagenExecutablePath ;
153+ _logger = logger ;
148154 }
149155
150156 public MutagenController ( string executablePath , string dataDirectory )
151157 {
152158 _mutagenExecutablePath = executablePath ;
153159 _mutagenDataDirectory = dataDirectory ;
160+ var builder = Host . CreateApplicationBuilder ( ) ;
161+ builder . Services . AddSerilog ( ) ;
162+ _logger = ( ILogger < MutagenController > ) builder . Build ( ) . Services . GetService ( typeof ( ILogger < MutagenController > ) ) ! ;
154163 }
155164
156165 public event EventHandler < SyncSessionControllerStateModel > ? StateChanged ;
@@ -447,9 +456,9 @@ private async Task<MutagenClient> EnsureDaemon(CancellationToken ct)
447456 {
448457 await StopDaemon ( cts . Token ) ;
449458 }
450- catch
459+ catch ( Exception stopEx )
451460 {
452- // ignored
461+ _logger . LogError ( stopEx , "failed to stop daemon" ) ;
453462 }
454463
455464 ReplaceState ( new SyncSessionControllerStateModel
@@ -501,6 +510,8 @@ private async Task<MutagenClient> StartDaemon(CancellationToken ct)
501510 }
502511 catch ( Exception e ) when ( e is not OperationCanceledException )
503512 {
513+ _logger . LogWarning ( e , "failed to start daemon process, attempt {attempt} of {maxAttempts}" , attempts ,
514+ maxAttempts ) ;
504515 if ( attempts == maxAttempts )
505516 throw ;
506517 // back off a little and try again.
@@ -556,8 +567,11 @@ private void StartDaemonProcess()
556567 // https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.environment?view=net-8.0
557568 _daemonProcess . StartInfo . UseShellExecute = false ;
558569 _daemonProcess . StartInfo . RedirectStandardError = true ;
559- // TODO: log exited process
560- // _daemonProcess.Exited += ...
570+ _daemonProcess . EnableRaisingEvents = true ;
571+ _daemonProcess . Exited += ( object ? sender , EventArgs e ) =>
572+ {
573+ _logger . LogInformation ( "mutagen daemon exited with code {exitCode}" , _daemonProcess ? . ExitCode ) ;
574+ } ;
561575 if ( ! _daemonProcess . Start ( ) )
562576 throw new InvalidOperationException ( "Failed to start mutagen daemon process, Start returned false" ) ;
563577
@@ -572,6 +586,7 @@ private void StartDaemonProcess()
572586 /// </summary>
573587 private async Task StopDaemon ( CancellationToken ct )
574588 {
589+ _logger . LogDebug ( "stopping mutagen daemon" ) ;
575590 var process = _daemonProcess ;
576591 var client = _mutagenClient ;
577592 var writer = _logWriter ;
@@ -584,28 +599,34 @@ private async Task StopDaemon(CancellationToken ct)
584599 if ( client == null )
585600 {
586601 if ( process == null ) return ;
602+ _logger . LogDebug ( "no client; killing daemon process" ) ;
587603 process . Kill ( true ) ;
588604 }
589605 else
590606 {
591607 try
592608 {
609+ _logger . LogDebug ( "sending DaemonTerminateRequest" ) ;
593610 await client . Daemon . TerminateAsync ( new DaemonTerminateRequest ( ) , cancellationToken : ct ) ;
594611 }
595- catch
612+ catch ( Exception e )
596613 {
614+ _logger . LogError ( e , "failed to gracefully terminate agent" ) ;
597615 if ( process == null ) return ;
616+ _logger . LogDebug ( "killing daemon process after failed graceful termination" ) ;
598617 process . Kill ( true ) ;
599618 }
600619 }
601620
602621 if ( process == null ) return ;
603622 var cts = CancellationTokenSource . CreateLinkedTokenSource ( ct ) ;
604623 cts . CancelAfter ( TimeSpan . FromSeconds ( 5 ) ) ;
624+ _logger . LogDebug ( "waiting for process to exit" ) ;
605625 await process . WaitForExitAsync ( cts . Token ) ;
606626 }
607627 finally
608628 {
629+ _logger . LogDebug ( "cleaning up daemon process objects" ) ;
609630 client ? . Dispose ( ) ;
610631 process ? . Dispose ( ) ;
611632 writer ? . Dispose ( ) ;
0 commit comments