1+ using System . Diagnostics ;
2+ using System . Reflection ;
13using DryIoc ;
24using Serilog ;
5+ using Serilog . Core ;
6+ using Serilog . Events ;
7+ using SquidStd . Abstractions . Data . Internal . Config ;
38using SquidStd . Abstractions . Data . Internal . Services ;
49using SquidStd . Abstractions . Interfaces . Services ;
510using SquidStd . Core . Data . Bootstrap ;
@@ -159,8 +164,25 @@ public async ValueTask StartAsync(CancellationToken cancellationToken = default)
159164
160165 try
161166 {
167+ ConfigureLogging ( ) ;
168+
169+ var logger = Log . ForContext < SquidStdBootstrap > ( ) ;
170+ var appName = ResolveAppName ( Options ) ;
171+
172+ logger . Information (
173+ "{Application:l} {ApplicationVersion:l} starting (SquidStd {SquidStdVersion:l}, config {ConfigName}, root {RootDirectory})" ,
174+ appName ,
175+ ResolveVersion ( Assembly . GetEntryAssembly ( ) ) ,
176+ ResolveVersion ( typeof ( SquidStdBootstrap ) . Assembly ) ,
177+ Options . ConfigName ,
178+ Options . RootDirectory
179+ ) ;
180+
162181 var registrations = GetServiceRegistrations ( ) ;
182+ LogRegistrations ( logger , registrations ) ;
183+
163184 var startedInstances = new HashSet < ISquidStdService > ( ReferenceEqualityComparer . Instance ) ;
185+ var totalStopwatch = Stopwatch . StartNew ( ) ;
164186
165187 for ( var i = 0 ; i < registrations . Length ; i ++ )
166188 {
@@ -173,14 +195,33 @@ public async ValueTask StartAsync(CancellationToken cancellationToken = default)
173195 continue ;
174196 }
175197
176- await service . StartAsync ( cancellationToken ) ;
177- _startedServices . Add ( service ) ;
198+ var serviceStopwatch = Stopwatch . StartNew ( ) ;
178199
179- if ( service is IConfigManagerService )
200+ try
180201 {
181- ConfigureLogger ( ) ;
202+ await service . StartAsync ( cancellationToken ) ;
182203 }
204+ catch ( Exception ex )
205+ {
206+ logger . Error ( ex , "Service {Service:l} failed to start" , service . GetType ( ) . Name ) ;
207+
208+ throw ;
209+ }
210+
211+ _startedServices . Add ( service ) ;
212+ logger . Information (
213+ "Started {Service:l} in {Elapsed:0.#}ms" ,
214+ service . GetType ( ) . Name ,
215+ serviceStopwatch . Elapsed . TotalMilliseconds
216+ ) ;
183217 }
218+
219+ logger . Information (
220+ "{Application:l} started: {Count} service(s) in {TotalElapsed:0.#}ms" ,
221+ appName ,
222+ _startedServices . Count ,
223+ totalStopwatch . Elapsed . TotalMilliseconds
224+ ) ;
184225 }
185226 catch
186227 {
@@ -198,13 +239,33 @@ public async ValueTask StopAsync(CancellationToken cancellationToken = default)
198239 return ;
199240 }
200241
242+ var logger = Log . ForContext < SquidStdBootstrap > ( ) ;
243+ var appName = ResolveAppName ( Options ) ;
244+ logger . Information ( "{Application:l} stopping ({Count} service(s))" , appName , _startedServices . Count ) ;
245+
201246 try
202247 {
203248 for ( var i = _startedServices . Count - 1 ; i >= 0 ; i -- )
204249 {
205250 cancellationToken . ThrowIfCancellationRequested ( ) ;
206- await _startedServices [ i ] . StopAsync ( cancellationToken ) ;
251+ var service = _startedServices [ i ] ;
252+
253+ try
254+ {
255+ await service . StopAsync ( cancellationToken ) ;
256+ logger . Information ( "Stopped {Service:l}" , service . GetType ( ) . Name ) ;
257+ }
258+ catch ( OperationCanceledException ) when ( cancellationToken . IsCancellationRequested )
259+ {
260+ throw ;
261+ }
262+ catch ( Exception ex )
263+ {
264+ logger . Warning ( ex , "Service {Service:l} failed to stop" , service . GetType ( ) . Name ) ;
265+ }
207266 }
267+
268+ logger . Information ( "{Application:l} shutdown complete" , appName ) ;
208269 }
209270 finally
210271 {
@@ -251,6 +312,26 @@ public static SquidStdBootstrap Create(Action<SquidStdOptions> configure)
251312 return Create ( options ) ;
252313 }
253314
315+ private static string ResolveAppName ( SquidStdOptions options )
316+ => ! string . IsNullOrWhiteSpace ( options . AppName )
317+ ? options . AppName
318+ : Assembly . GetEntryAssembly ( ) ? . GetName ( ) . Name ?? "SquidStd" ;
319+
320+ private static string ResolveVersion ( Assembly ? assembly )
321+ {
322+ var informational = assembly ? . GetCustomAttribute < AssemblyInformationalVersionAttribute > ( )
323+ ? . InformationalVersion ;
324+
325+ if ( string . IsNullOrWhiteSpace ( informational ) )
326+ {
327+ return assembly ? . GetName ( ) . Version ? . ToString ( ) ?? "0.0.0" ;
328+ }
329+
330+ var metadataStart = informational . IndexOf ( '+' ) ;
331+
332+ return metadataStart > 0 ? informational [ ..metadataStart ] : informational ;
333+ }
334+
254335 private void ConfigureLogger ( )
255336 {
256337 if ( _loggerConfigured || ! Container . IsRegistered < SquidStdLoggerOptions > ( ) )
@@ -261,6 +342,10 @@ private void ConfigureLogger()
261342 var options = Container . Resolve < SquidStdLoggerOptions > ( ) ;
262343 var loggerConfiguration = new LoggerConfiguration ( ) ;
263344
345+ loggerConfiguration
346+ . Enrich . WithProperty ( "Application" , ResolveAppName ( Options ) )
347+ . Enrich . WithProperty ( "ApplicationVersion" , ResolveVersion ( Assembly . GetEntryAssembly ( ) ) ) ;
348+
264349 if ( options . MinimumLevel != LogLevelType . None )
265350 {
266351 var minimumLevel = options . MinimumLevel . ToSerilogLogLevel ( ) ;
@@ -279,6 +364,11 @@ private void ConfigureLogger()
279364 restrictedToMinimumLevel : minimumLevel
280365 ) ;
281366 }
367+
368+ foreach ( var sink in Container . ResolveMany < ILogEventSink > ( ) )
369+ {
370+ loggerConfiguration . WriteTo . Sink ( sink , minimumLevel ) ;
371+ }
282372 }
283373
284374 var logger = loggerConfiguration . CreateLogger ( ) ;
@@ -287,6 +377,58 @@ private void ConfigureLogger()
287377 _loggerConfigured = true ;
288378 }
289379
380+ private void LogRegistrations ( ILogger logger , ServiceRegistrationData [ ] lifecycleRegistrations )
381+ {
382+ List < ConfigRegistrationData > sections = Container . IsRegistered < List < ConfigRegistrationData > > ( )
383+ ? Container . Resolve < List < ConfigRegistrationData > > ( )
384+ : [ ] ;
385+ var containerRegistrations = Container . GetServiceRegistrations ( ) . ToArray ( ) ;
386+
387+ logger . Information (
388+ "Registered {LifecycleCount} lifecycle service(s), {SectionCount} config section(s), {ContainerCount} container registration(s)" ,
389+ lifecycleRegistrations . Length ,
390+ sections . Count ,
391+ containerRegistrations . Length
392+ ) ;
393+
394+ if ( ! logger . IsEnabled ( LogEventLevel . Debug ) )
395+ {
396+ return ;
397+ }
398+
399+ foreach ( var registration in lifecycleRegistrations )
400+ {
401+ logger . Debug (
402+ "Lifecycle service {Service} -> {Implementation} (priority {Priority})" ,
403+ registration . ServiceType . Name ,
404+ registration . ImplementationType . Name ,
405+ registration . Priority
406+ ) ;
407+ }
408+
409+ foreach ( var section in sections )
410+ {
411+ logger . Debug ( "Config section '{Section}' -> {Type}" , section . SectionName , section . ConfigType . Name ) ;
412+ }
413+
414+ foreach ( var group in containerRegistrations
415+ . GroupBy ( registration => registration . ServiceType . Assembly . GetName ( ) . Name ?? "unknown" )
416+ . OrderBy ( group => group . Key , StringComparer . Ordinal ) )
417+ {
418+ logger . Debug (
419+ "{Assembly}: {Count} registration(s): {Services}" ,
420+ group . Key ,
421+ group . Count ( ) ,
422+ string . Join (
423+ ", " ,
424+ group . Select ( registration => registration . ServiceType . Name )
425+ . Distinct ( )
426+ . Order ( StringComparer . Ordinal )
427+ )
428+ ) ;
429+ }
430+ }
431+
290432 private ServiceRegistrationData [ ] GetServiceRegistrations ( )
291433 {
292434 if ( ! Container . IsRegistered < List < ServiceRegistrationData > > ( ) )
0 commit comments