diff --git a/App/Services/MutagenController.cs b/App/Services/MutagenController.cs
index 3a68962..1af2fca 100644
--- a/App/Services/MutagenController.cs
+++ b/App/Services/MutagenController.cs
@@ -16,9 +16,12 @@
 using Coder.Desktop.Vpn.Utilities;
 using Grpc.Core;
 using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Logging;
+using Serilog;
 using DaemonTerminateRequest = Coder.Desktop.MutagenSdk.Proto.Service.Daemon.TerminateRequest;
 using MutagenProtocol = Coder.Desktop.MutagenSdk.Proto.Url.Protocol;
 using SynchronizationTerminateRequest = Coder.Desktop.MutagenSdk.Proto.Service.Synchronization.TerminateRequest;
+using Microsoft.Extensions.Hosting;
 
 namespace Coder.Desktop.App.Services;
 
@@ -110,6 +113,8 @@ public sealed class MutagenController : ISyncSessionController
     // Protects all private non-readonly class members.
     private readonly RaiiSemaphoreSlim _lock = new(1, 1);
 
+    private readonly ILogger<MutagenController> _logger;
+
     private readonly CancellationTokenSource _stateUpdateCts = new();
     private Task? _stateUpdateTask;
 
@@ -139,15 +144,19 @@ public sealed class MutagenController : ISyncSessionController
 
     private string MutagenDaemonLog => Path.Combine(_mutagenDataDirectory, "daemon.log");
 
-    public MutagenController(IOptions<MutagenControllerConfig> config)
+    public MutagenController(IOptions<MutagenControllerConfig> config, ILogger<MutagenController> logger)
     {
         _mutagenExecutablePath = config.Value.MutagenExecutablePath;
+        _logger = logger;
     }
 
     public MutagenController(string executablePath, string dataDirectory)
     {
         _mutagenExecutablePath = executablePath;
         _mutagenDataDirectory = dataDirectory;
+        var builder = Host.CreateApplicationBuilder();
+        builder.Services.AddSerilog();
+        _logger = (ILogger<MutagenController>)builder.Build().Services.GetService(typeof(ILogger<MutagenController>))!;
     }
 
     public event EventHandler<SyncSessionControllerStateModel>? StateChanged;
@@ -440,9 +449,9 @@ private async Task<MutagenClient> EnsureDaemon(CancellationToken ct)
             {
                 await StopDaemon(cts.Token);
             }
-            catch
+            catch (Exception stopEx)
             {
-                // ignored
+                _logger.LogError(stopEx, "failed to stop daemon");
             }
 
             ReplaceState(new SyncSessionControllerStateModel
@@ -494,6 +503,8 @@ private async Task<MutagenClient> StartDaemon(CancellationToken ct)
             }
             catch (Exception e) when (e is not OperationCanceledException)
             {
+                _logger.LogWarning(e, "failed to start daemon process, attempt {attempt} of {maxAttempts}", attempts,
+                    maxAttempts);
                 if (attempts == maxAttempts)
                     throw;
                 // back off a little and try again.
@@ -548,8 +559,11 @@ private void StartDaemonProcess()
         // https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.environment?view=net-8.0
         _daemonProcess.StartInfo.UseShellExecute = false;
         _daemonProcess.StartInfo.RedirectStandardError = true;
-        // TODO: log exited process
-        // _daemonProcess.Exited += ...
+        _daemonProcess.EnableRaisingEvents = true;
+        _daemonProcess.Exited += (object? sender, EventArgs e) =>
+        {
+            _logger.LogInformation("mutagen daemon exited with code {exitCode}", _daemonProcess?.ExitCode);
+        };
         if (!_daemonProcess.Start())
             throw new InvalidOperationException("Failed to start mutagen daemon process, Start returned false");
 
@@ -564,6 +578,7 @@ private void StartDaemonProcess()
     /// </summary>
     private async Task StopDaemon(CancellationToken ct)
     {
+        _logger.LogDebug("stopping mutagen daemon");
         var process = _daemonProcess;
         var client = _mutagenClient;
         var writer = _logWriter;
@@ -576,17 +591,21 @@ private async Task StopDaemon(CancellationToken ct)
             if (client == null)
             {
                 if (process == null) return;
+                _logger.LogDebug("no client; killing daemon process");
                 process.Kill(true);
             }
             else
             {
                 try
                 {
+                    _logger.LogDebug("sending DaemonTerminateRequest");
                     await client.Daemon.TerminateAsync(new DaemonTerminateRequest(), cancellationToken: ct);
                 }
-                catch
+                catch (Exception e)
                 {
+                    _logger.LogError(e, "failed to gracefully terminate agent");
                     if (process == null) return;
+                    _logger.LogDebug("killing daemon process after failed graceful termination");
                     process.Kill(true);
                 }
             }
@@ -594,10 +613,12 @@ private async Task StopDaemon(CancellationToken ct)
             if (process == null) return;
             var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
             cts.CancelAfter(TimeSpan.FromSeconds(5));
+            _logger.LogDebug("waiting for process to exit");
             await process.WaitForExitAsync(cts.Token);
         }
         finally
         {
+            _logger.LogDebug("cleaning up daemon process objects");
             client?.Dispose();
             process?.Dispose();
             writer?.Dispose();