16
16
using Microsoft . Win32 ;
17
17
using Microsoft . Windows . AppLifecycle ;
18
18
using Windows . ApplicationModel . Activation ;
19
+ using Microsoft . Extensions . Logging ;
20
+ using Serilog ;
21
+ using System . Collections . Generic ;
19
22
20
23
namespace Coder . Desktop . App ;
21
24
@@ -24,22 +27,39 @@ public partial class App : Application
24
27
private readonly IServiceProvider _services ;
25
28
26
29
private bool _handleWindowClosed = true ;
30
+ private const string MutagenControllerConfigSection = "MutagenController" ;
27
31
28
32
#if ! DEBUG
29
- private const string MutagenControllerConfigSection = "AppMutagenController" ;
33
+ private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App" ;
34
+ private const string logFilename = "app.log" ;
30
35
#else
31
- private const string MutagenControllerConfigSection = "DebugAppMutagenController" ;
36
+ private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugApp" ;
37
+ private const string logFilename = "debug-app.log" ;
32
38
#endif
33
39
40
+ private readonly ILogger < App > _logger ;
41
+
34
42
public App ( )
35
43
{
36
44
var builder = Host . CreateApplicationBuilder ( ) ;
45
+ var configBuilder = builder . Configuration as IConfigurationBuilder ;
37
46
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 ) ) ;
40
54
41
55
var services = builder . Services ;
42
56
57
+ // Logging
58
+ builder . Services . AddSerilog ( ( _ , loggerConfig ) =>
59
+ {
60
+ loggerConfig . ReadFrom . Configuration ( builder . Configuration ) ;
61
+ } ) ;
62
+
43
63
services . AddSingleton < ICredentialManager , CredentialManager > ( ) ;
44
64
services . AddSingleton < IRpcController , RpcController > ( ) ;
45
65
@@ -69,12 +89,14 @@ public App()
69
89
services . AddTransient < TrayWindow > ( ) ;
70
90
71
91
_services = services . BuildServiceProvider ( ) ;
92
+ _logger = ( ILogger < App > ) ( _services . GetService ( typeof ( ILogger < App > ) ) ! ) ;
72
93
73
94
InitializeComponent ( ) ;
74
95
}
75
96
76
97
public async Task ExitApplication ( )
77
98
{
99
+ _logger . LogDebug ( "exiting app" ) ;
78
100
_handleWindowClosed = false ;
79
101
Exit ( ) ;
80
102
var syncController = _services . GetRequiredService < ISyncSessionController > ( ) ;
@@ -87,36 +109,39 @@ public async Task ExitApplication()
87
109
88
110
protected override void OnLaunched ( Microsoft . UI . Xaml . LaunchActivatedEventArgs args )
89
111
{
112
+ _logger . LogInformation ( "new instance launched" ) ;
90
113
// Start connecting to the manager in the background.
91
114
var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
92
115
if ( rpcController . GetState ( ) . RpcLifecycle == RpcLifecycle . Disconnected )
93
116
// Passing in a CT with no cancellation is desired here, because
94
117
// 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 )
97
122
{
123
+ _logger . LogError ( t . Exception , "failed to connect to VPN service" ) ;
98
124
#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 ( ) ;
104
127
#endif
105
- } ) ;
128
+ }
129
+ } ) ;
106
130
107
131
// Load the credentials in the background.
108
132
var credentialManagerCts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 15 ) ) ;
109
133
var credentialManager = _services . GetRequiredService < ICredentialManager > ( ) ;
110
134
_ = credentialManager . LoadCredentials ( credentialManagerCts . Token ) . ContinueWith ( t =>
111
135
{
112
- // TODO: log
113
- #if DEBUG
114
136
if ( t . Exception != null )
115
137
{
138
+ _logger . LogError ( t . Exception , "failed to load credentials" ) ;
139
+ #if DEBUG
116
140
Debug . WriteLine ( t . Exception ) ;
117
141
Debugger . Break ( ) ;
118
- }
119
142
#endif
143
+ }
144
+
120
145
credentialManagerCts . Dispose ( ) ;
121
146
} , CancellationToken . None ) ;
122
147
@@ -125,10 +150,14 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar
125
150
var syncSessionController = _services . GetRequiredService < ISyncSessionController > ( ) ;
126
151
_ = syncSessionController . RefreshState ( syncSessionCts . Token ) . ContinueWith ( t =>
127
152
{
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 ) ;
129
156
#if DEBUG
130
- if ( t . IsCanceled || t . Exception != null ) Debugger . Break ( ) ;
157
+ Debugger . Break ( ) ;
131
158
#endif
159
+ }
160
+
132
161
syncSessionCts . Dispose ( ) ;
133
162
} , CancellationToken . None ) ;
134
163
@@ -148,17 +177,44 @@ public void OnActivated(object? sender, AppActivationArguments args)
148
177
{
149
178
case ExtendedActivationKind . Protocol :
150
179
var protoArgs = args . Data as IProtocolActivatedEventArgs ;
180
+ if ( protoArgs == null )
181
+ {
182
+ _logger . LogWarning ( "URI activation with null data" ) ;
183
+ return ;
184
+ }
185
+
151
186
HandleURIActivation ( protoArgs . Uri ) ;
152
187
break ;
153
188
154
189
default :
155
- // TODO: log
190
+ _logger . LogWarning ( "activation for {kind}, which is unhandled" , args . Kind ) ;
156
191
break ;
157
192
}
158
193
}
159
194
160
195
public void HandleURIActivation ( Uri uri )
161
196
{
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
+ } ) ;
163
219
}
164
220
}
0 commit comments