1616using Microsoft . Win32 ;
1717using Microsoft . Windows . AppLifecycle ;
1818using Windows . ApplicationModel . Activation ;
19+ using Microsoft . Extensions . Logging ;
20+ using Serilog ;
21+ using System . Collections . Generic ;
1922
2023namespace Coder . Desktop . App ;
2124
@@ -24,22 +27,39 @@ public partial class App : Application
2427 private readonly IServiceProvider _services ;
2528
2629 private bool _handleWindowClosed = true ;
30+ private const string MutagenControllerConfigSection = "MutagenController" ;
2731
2832#if ! DEBUG
29- private const string MutagenControllerConfigSection = "AppMutagenController" ;
33+ private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App" ;
34+ private const string logFilename = "app.log" ;
3035#else
31- private const string MutagenControllerConfigSection = "DebugAppMutagenController" ;
36+ private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugApp" ;
37+ private const string logFilename = "debug-app.log" ;
3238#endif
3339
40+ private readonly ILogger < App > _logger ;
41+
3442 public App ( )
3543 {
3644 var builder = Host . CreateApplicationBuilder ( ) ;
45+ var configBuilder = builder . Configuration as IConfigurationBuilder ;
3746
38- ( builder . Configuration as IConfigurationBuilder ) . Add (
39- new RegistryConfigurationSource ( Registry . LocalMachine , @"SOFTWARE\Coder Desktop" ) ) ;
47+ // Add config in increasing order of precedence: first builtin defaults, then HKLM, finally HKCU
48+ // so that the user's settings in the registry take precedence.
49+ AddDefaultConfig ( configBuilder ) ;
50+ configBuilder . Add (
51+ new RegistryConfigurationSource ( Registry . LocalMachine , ConfigSubKey ) ) ;
52+ configBuilder . Add (
53+ new RegistryConfigurationSource ( Registry . CurrentUser , ConfigSubKey ) ) ;
4054
4155 var services = builder . Services ;
4256
57+ // Logging
58+ builder . Services . AddSerilog ( ( _ , loggerConfig ) =>
59+ {
60+ loggerConfig . ReadFrom . Configuration ( builder . Configuration ) ;
61+ } ) ;
62+
4363 services . AddSingleton < ICredentialManager , CredentialManager > ( ) ;
4464 services . AddSingleton < IRpcController , RpcController > ( ) ;
4565
@@ -69,12 +89,14 @@ public App()
6989 services . AddTransient < TrayWindow > ( ) ;
7090
7191 _services = services . BuildServiceProvider ( ) ;
92+ _logger = ( ILogger < App > ) ( _services . GetService ( typeof ( ILogger < App > ) ) ! ) ;
7293
7394 InitializeComponent ( ) ;
7495 }
7596
7697 public async Task ExitApplication ( )
7798 {
99+ _logger . LogDebug ( "exiting app" ) ;
78100 _handleWindowClosed = false ;
79101 Exit ( ) ;
80102 var syncController = _services . GetRequiredService < ISyncSessionController > ( ) ;
@@ -87,36 +109,39 @@ public async Task ExitApplication()
87109
88110 protected override void OnLaunched ( Microsoft . UI . Xaml . LaunchActivatedEventArgs args )
89111 {
112+ _logger . LogInformation ( "new instance launched" ) ;
90113 // Start connecting to the manager in the background.
91114 var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
92115 if ( rpcController . GetState ( ) . RpcLifecycle == RpcLifecycle . Disconnected )
93116 // Passing in a CT with no cancellation is desired here, because
94117 // the named pipe open will block until the pipe comes up.
95- // TODO: log
96- _ = rpcController . Reconnect ( CancellationToken . None ) . ContinueWith ( t =>
118+ _logger . LogDebug ( "reconnecting with VPN service" ) ;
119+ _ = rpcController . Reconnect ( CancellationToken . None ) . ContinueWith ( t =>
120+ {
121+ if ( t . Exception != null )
97122 {
123+ _logger . LogError ( t . Exception , "failed to connect to VPN service" ) ;
98124#if DEBUG
99- if ( t . Exception != null )
100- {
101- Debug . WriteLine ( t . Exception ) ;
102- Debugger . Break ( ) ;
103- }
125+ Debug . WriteLine ( t . Exception ) ;
126+ Debugger . Break ( ) ;
104127#endif
105- } ) ;
128+ }
129+ } ) ;
106130
107131 // Load the credentials in the background.
108132 var credentialManagerCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 15 ) ) ;
109133 var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
110134 _ = credentialManager . LoadCredentials ( credentialManagerCts . Token ) . ContinueWith ( t =>
111135 {
112- // TODO: log
113- #if DEBUG
114136 if ( t . Exception != null )
115137 {
138+ _logger . LogError ( t . Exception , "failed to load credentials" ) ;
139+ #if DEBUG
116140 Debug . WriteLine ( t . Exception ) ;
117141 Debugger . Break ( ) ;
118- }
119142#endif
143+ }
144+
120145 credentialManagerCts . Dispose ( ) ;
121146 } , CancellationToken . None ) ;
122147
@@ -125,10 +150,14 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
125150 var syncSessionController = _services . GetRequiredService < ISyncSessionController > ( ) ;
126151 _ = syncSessionController . RefreshState ( syncSessionCts . Token ) . ContinueWith ( t =>
127152 {
128- // TODO: log
153+ if ( t . IsCanceled || t . Exception != null )
154+ {
155+ _logger . LogError ( t . Exception , "failed to refresh sync state (canceled = {canceled})" , t . IsCanceled ) ;
129156#if DEBUG
130- if ( t . IsCanceled || t . Exception != null ) Debugger . Break ( ) ;
157+ Debugger . Break ( ) ;
131158#endif
159+ }
160+
132161 syncSessionCts . Dispose ( ) ;
133162 } , CancellationToken . None ) ;
134163
@@ -148,17 +177,44 @@ public void OnActivated(object? sender, AppActivationArguments args)
148177 {
149178 case ExtendedActivationKind . Protocol :
150179 var protoArgs = args . Data as IProtocolActivatedEventArgs ;
180+ if ( protoArgs == null )
181+ {
182+ _logger . LogWarning ( "URI activation with null data" ) ;
183+ return ;
184+ }
185+
151186 HandleURIActivation ( protoArgs . Uri ) ;
152187 break ;
153188
154189 default :
155- // TODO: log
190+ _logger . LogWarning ( "activation for {kind}, which is unhandled" , args . Kind ) ;
156191 break ;
157192 }
158193 }
159194
160195 public void HandleURIActivation ( Uri uri )
161196 {
162- // TODO: handle
197+ // don't log the query string as that's where we include some sensitive information like passwords
198+ _logger . LogInformation ( "handling URI activation for {path}" , uri . AbsolutePath ) ;
199+ }
200+
201+ private static void AddDefaultConfig ( IConfigurationBuilder builder )
202+ {
203+ var logPath = Path . Combine (
204+ Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
205+ "CoderDesktop" ,
206+ logFilename ) ;
207+ builder . AddInMemoryCollection ( new Dictionary < string , string ? >
208+ {
209+ [ MutagenControllerConfigSection + ":MutagenExecutablePath" ] = @"C:\mutagen.exe" ,
210+ [ "Serilog:Using:0" ] = "Serilog.Sinks.File" ,
211+ [ "Serilog:MinimumLevel" ] = "Information" ,
212+ [ "Serilog:Enrich:0" ] = "FromLogContext" ,
213+ [ "Serilog:WriteTo:0:Name" ] = "File" ,
214+ [ "Serilog:WriteTo:0:Args:path" ] = logPath ,
215+ [ "Serilog:WriteTo:0:Args:outputTemplate" ] =
216+ "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}" ,
217+ [ "Serilog:WriteTo:0:Args:rollingInterval" ] = "Day" ,
218+ } ) ;
163219 }
164220}
0 commit comments