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
2427private readonly IServiceProvider _services ;
2528
2629private 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+
3442public App ( )
3543{
3644var 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
4155var services = builder . Services ;
4256
57+ // Logging
58+ builder . Services . AddSerilog ( ( _ , loggerConfig ) =>
59+ {
60+ loggerConfig . ReadFrom . Configuration ( builder . Configuration ) ;
61+ } ) ;
62+
4363services . AddSingleton < ICredentialManager , CredentialManager > ( ) ;
4464services . AddSingleton < IRpcController , RpcController > ( ) ;
4565
@@ -69,12 +89,14 @@ public App()
6989services . AddTransient < TrayWindow > ( ) ;
7090
7191_services = services . BuildServiceProvider ( ) ;
92+ _logger = ( ILogger < App > ) ( _services . GetService ( typeof ( ILogger < App > ) ) ! ) ;
7293
7394InitializeComponent ( ) ;
7495}
7596
7697public async Task ExitApplication ( )
7798{
99+ _logger . LogDebug ( "exiting app" ) ;
78100_handleWindowClosed = false ;
79101Exit ( ) ;
80102var syncController = _services . GetRequiredService < ISyncSessionController > ( ) ;
@@ -87,36 +109,39 @@ public async Task ExitApplication()
87109
88110protected override void OnLaunched ( Microsoft . UI . Xaml . LaunchActivatedEventArgs args )
89111{
112+ _logger . LogInformation ( "new instance launched" ) ;
90113// Start connecting to the manager in the background.
91114var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
92115if ( 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#ifDEBUG
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.
108132var credentialManagerCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 15 ) ) ;
109133var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
110134_ = credentialManager . LoadCredentials ( credentialManagerCts . Token ) . ContinueWith ( t=>
111135{
112- // TODO: log
113- #ifDEBUG
114136if ( t . Exception != null )
115137{
138+ _logger . LogError ( t . Exception , "failed to load credentials" ) ;
139+ #ifDEBUG
116140Debug . WriteLine ( t . Exception ) ;
117141Debugger . Break ( ) ;
118- }
119142#endif
143+ }
144+
120145credentialManagerCts . Dispose ( ) ;
121146} , CancellationToken . None ) ;
122147
@@ -125,10 +150,14 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
125150var 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#ifDEBUG
130- if ( t . IsCanceled || t . Exception != null ) Debugger . Break ( ) ;
157+ Debugger . Break ( ) ;
131158#endif
159+ }
160+
132161syncSessionCts . Dispose ( ) ;
133162} , CancellationToken . None ) ;
134163
@@ -148,17 +177,44 @@ public void OnActivated(object? sender, AppActivationArguments args)
148177{
149178case ExtendedActivationKind . Protocol :
150179var protoArgs = args . Data as IProtocolActivatedEventArgs ;
180+ if ( protoArgs == null )
181+ {
182+ _logger . LogWarning ( "URI activation with null data" ) ;
183+ return ;
184+ }
185+
151186HandleURIActivation ( protoArgs . Uri ) ;
152187break ;
153188
154189default :
155- // TODO: log
190+ _logger . LogWarning ( "activation for {kind}, which is unhandled" , args . Kind ) ;
156191break ;
157192}
158193}
159194
160195public 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}