11using System ;
2+ using System . Collections . Generic ;
23using System . Diagnostics ;
4+ using System . IO ;
35using System . Threading ;
46using System . Threading . Tasks ;
7+ using Windows . ApplicationModel . Activation ;
58using Coder . Desktop . App . Models ;
69using Coder . Desktop . App . Services ;
710using Coder . Desktop . App . ViewModels ;
1215using Microsoft . Extensions . Configuration ;
1316using Microsoft . Extensions . DependencyInjection ;
1417using Microsoft . Extensions . Hosting ;
18+ using Microsoft . Extensions . Logging ;
1519using Microsoft . UI . Xaml ;
1620using Microsoft . Win32 ;
21+ using Microsoft . Windows . AppLifecycle ;
22+ using Serilog ;
23+ using LaunchActivatedEventArgs = Microsoft . UI . Xaml . LaunchActivatedEventArgs ;
1724
1825namespace Coder . Desktop . App ;
1926
@@ -22,22 +29,39 @@ public partial class App : Application
2229 private readonly IServiceProvider _services ;
2330
2431 private bool _handleWindowClosed = true ;
32+ private const string MutagenControllerConfigSection = "MutagenController" ;
2533
2634#if ! DEBUG
27- private const string MutagenControllerConfigSection = "AppMutagenController" ;
35+ private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App" ;
36+ private const string logFilename = "app.log" ;
2837#else
29- private const string MutagenControllerConfigSection = "DebugAppMutagenController" ;
38+ private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugApp" ;
39+ private const string logFilename = "debug-app.log" ;
3040#endif
3141
42+ private readonly ILogger < App > _logger ;
43+
3244 public App ( )
3345 {
3446 var builder = Host . CreateApplicationBuilder ( ) ;
47+ var configBuilder = builder . Configuration as IConfigurationBuilder ;
3548
36- ( builder . Configuration as IConfigurationBuilder ) . Add (
37- new RegistryConfigurationSource ( Registry . LocalMachine , @"SOFTWARE\Coder Desktop" ) ) ;
49+ // Add config in increasing order of precedence: first builtin defaults, then HKLM, finally HKCU
50+ // so that the user's settings in the registry take precedence.
51+ AddDefaultConfig ( configBuilder ) ;
52+ configBuilder . Add (
53+ new RegistryConfigurationSource ( Registry . LocalMachine , ConfigSubKey ) ) ;
54+ configBuilder . Add (
55+ new RegistryConfigurationSource ( Registry . CurrentUser , ConfigSubKey ) ) ;
3856
3957 var services = builder . Services ;
4058
59+ // Logging
60+ builder . Services . AddSerilog ( ( _ , loggerConfig ) =>
61+ {
62+ loggerConfig . ReadFrom . Configuration ( builder . Configuration ) ;
63+ } ) ;
64+
4165 services . AddSingleton < IAgentApiClientFactory , AgentApiClientFactory > ( ) ;
4266
4367 services . AddSingleton < ICredentialManager , CredentialManager > ( ) ;
@@ -71,12 +95,14 @@ public App()
7195 services . AddTransient < TrayWindow > ( ) ;
7296
7397 _services = services . BuildServiceProvider ( ) ;
98+ _logger = ( ILogger < App > ) _services . GetService ( typeof ( ILogger < App > ) ) ! ;
7499
75100 InitializeComponent ( ) ;
76101 }
77102
78103 public async Task ExitApplication ( )
79104 {
105+ _logger . LogDebug ( "exiting app" ) ;
80106 _handleWindowClosed = false ;
81107 Exit ( ) ;
82108 var syncController = _services . GetRequiredService < ISyncSessionController > ( ) ;
@@ -89,36 +115,39 @@ public async Task ExitApplication()
89115
90116 protected override void OnLaunched ( LaunchActivatedEventArgs args )
91117 {
118+ _logger . LogInformation ( "new instance launched" ) ;
92119 // Start connecting to the manager in the background.
93120 var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
94121 if ( rpcController . GetState ( ) . RpcLifecycle == RpcLifecycle . Disconnected )
95122 // Passing in a CT with no cancellation is desired here, because
96123 // the named pipe open will block until the pipe comes up.
97- // TODO: log
98- _ = rpcController . Reconnect ( CancellationToken . None ) . ContinueWith ( t =>
124+ _logger . LogDebug ( "reconnecting with VPN service" ) ;
125+ _ = rpcController . Reconnect ( CancellationToken . None ) . ContinueWith ( t =>
126+ {
127+ if ( t . Exception != null )
99128 {
129+ _logger . LogError ( t . Exception , "failed to connect to VPN service" ) ;
100130#if DEBUG
101- if ( t . Exception != null )
102- {
103- Debug . WriteLine ( t . Exception ) ;
104- Debugger . Break ( ) ;
105- }
131+ Debug . WriteLine ( t . Exception ) ;
132+ Debugger . Break ( ) ;
106133#endif
107- } ) ;
134+ }
135+ } ) ;
108136
109137 // Load the credentials in the background.
110138 var credentialManagerCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 15 ) ) ;
111139 var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
112140 _ = credentialManager . LoadCredentials ( credentialManagerCts . Token ) . ContinueWith ( t =>
113141 {
114- // TODO: log
115- #if DEBUG
116142 if ( t . Exception != null )
117143 {
144+ _logger . LogError ( t . Exception , "failed to load credentials" ) ;
145+ #if DEBUG
118146 Debug . WriteLine ( t . Exception ) ;
119147 Debugger . Break ( ) ;
120- }
121148#endif
149+ }
150+
122151 credentialManagerCts . Dispose ( ) ;
123152 } , CancellationToken . None ) ;
124153
@@ -127,10 +156,14 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
127156 var syncSessionController = _services . GetRequiredService < ISyncSessionController > ( ) ;
128157 _ = syncSessionController . RefreshState ( syncSessionCts . Token ) . ContinueWith ( t =>
129158 {
130- // TODO: log
159+ if ( t . IsCanceled || t . Exception != null )
160+ {
161+ _logger . LogError ( t . Exception , "failed to refresh sync state (canceled = {canceled})" , t . IsCanceled ) ;
131162#if DEBUG
132- if ( t . IsCanceled || t . Exception != null ) Debugger . Break ( ) ;
163+ Debugger . Break ( ) ;
133164#endif
165+ }
166+
134167 syncSessionCts . Dispose ( ) ;
135168 } , CancellationToken . None ) ;
136169
@@ -143,4 +176,51 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
143176 trayWindow . AppWindow . Hide ( ) ;
144177 } ;
145178 }
179+
180+ public void OnActivated ( object ? sender , AppActivationArguments args )
181+ {
182+ switch ( args . Kind )
183+ {
184+ case ExtendedActivationKind . Protocol :
185+ var protoArgs = args . Data as IProtocolActivatedEventArgs ;
186+ if ( protoArgs == null )
187+ {
188+ _logger . LogWarning ( "URI activation with null data" ) ;
189+ return ;
190+ }
191+
192+ HandleURIActivation ( protoArgs . Uri ) ;
193+ break ;
194+
195+ default :
196+ _logger . LogWarning ( "activation for {kind}, which is unhandled" , args . Kind ) ;
197+ break ;
198+ }
199+ }
200+
201+ public void HandleURIActivation ( Uri uri )
202+ {
203+ // don't log the query string as that's where we include some sensitive information like passwords
204+ _logger . LogInformation ( "handling URI activation for {path}" , uri . AbsolutePath ) ;
205+ }
206+
207+ private static void AddDefaultConfig ( IConfigurationBuilder builder )
208+ {
209+ var logPath = Path . Combine (
210+ Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
211+ "CoderDesktop" ,
212+ logFilename ) ;
213+ builder . AddInMemoryCollection ( new Dictionary < string , string ? >
214+ {
215+ [ MutagenControllerConfigSection + ":MutagenExecutablePath" ] = @"C:\mutagen.exe" ,
216+ [ "Serilog:Using:0" ] = "Serilog.Sinks.File" ,
217+ [ "Serilog:MinimumLevel" ] = "Information" ,
218+ [ "Serilog:Enrich:0" ] = "FromLogContext" ,
219+ [ "Serilog:WriteTo:0:Name" ] = "File" ,
220+ [ "Serilog:WriteTo:0:Args:path" ] = logPath ,
221+ [ "Serilog:WriteTo:0:Args:outputTemplate" ] =
222+ "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}" ,
223+ [ "Serilog:WriteTo:0:Args:rollingInterval" ] = "Day" ,
224+ } ) ;
225+ }
146226}
0 commit comments