From e2fab7d4b27f1c295aed6927aacb5571a0bd37fe Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 12 Jan 2026 22:31:57 -0500 Subject: [PATCH] fix: Log All-The-Things! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/unoplatform/uno.extensions/issues/3008 Context: https://github.com/unoplatform/uno.extensions/pull/3011 `dotnet new unoapp` templates have two patterns for `ILogger` configuration: 1. The *recommended* pattern, which uses Dependency Injection (DI) via [`.UseLogging()`][0]: dotnet new unoapp -preset "recommended" 2. The "No Dependency Injection" (NoDI) pattern, which *doesn't* use Dependency Injection, and instead has an [`App.InitializeLogging()`][1] method called from [`Main()`][2]: dotnet new unoapp -di False The wonderful thing is, those log *different*, non-overlapping, things. The Dependency Injection pattern provides an `ILogger` instance to constructors of types registered with e.g. [`services.AddSingleton()`][3]: services.AddSingleton() // … interface IMyService; class MyService : IMyService { public MyService(ILogger logger) => …; } The "No Dependency Injection" pattern provides `ILogger` instances from the [`Log(T)` extension method][4], which is used extensively within Uno itself, e.g. // https://github.com/unoplatform/uno/blob/77c6ca0533a63be48c6bbdd830c8653e17a9b0fa/src/Uno.UI.Dispatching/Native/NativeDispatcher.cs#L146 dispatcher.Log().Error("NativeDispatcher unhandled exception", exception); Using only DI logging initialization will miss NoDI messages. Using only NoDI logging initialization will miss DI messages. In order to capture *all* `ILogger`-originated messages, *both* DI and NoDI log initialization patterns must be used, which was *not* done by *any* `dotnet new unoapp` templates before this change. Additionally, DI-based logging is *optional* -- it can be disabled via `dotnet new unoapp -di False …` -- and thus its presence cannot necessarily be relied upon. unoplatform/uno.extensions#3011 updates `HostBuilder.Build()` so that *if* `App.InitializeLogging()` registers an `ILoggerFactory`, that instance will be used *by default* by DI-based logging, *even if* `.UseLogging()` is not used. Update uno.templates to finish the harmonization: 1. *Always* emit an `App.InitializeLogging()` method, and call it from `Main()`.` 2. *Refactor* `App.InitializeLogging()` and the `.UseLogging()` callback so that both call a new `App.ConfigureLogging()` method. `ConfigureLogging()` is responsible for configuring the `ILoggingBuilder` instance, and this setup allows both `App.InitializeLogging()` and `.UseLogging()` to share the same code, centralizing `ILogger` configuration. This should work even without unoplatform/uno.extensions#3011, ensuring that *all* `ILogger`-originated messages are logged, *so long as* DI-based logging is used. (This adds NoDI logging to the DI template.) That said, it is preferred that unoplatform/uno.extensions#3011 exist *first*, so that even if `.UseLogging()` isn't used, DI-based logging will still generate log messages. [0]: https://github.com/unoplatform/uno.templates/blob/8988cac5efc444de0afeb400da1da24620b5c083/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.recommended.cs#L81-L112 [1]: https://github.com/unoplatform/uno.templates/blob/8988cac5efc444de0afeb400da1da24620b5c083/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.blank.cs#L115-L182 [2]: https://github.com/unoplatform/uno.templates/blob/8988cac5efc444de0afeb400da1da24620b5c083/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/Desktop/Program.cs#L10-L14 [3]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.servicecollectionserviceextensions.addsingleton?view=net-10.0-pp [4]: https://platform.uno/docs/articles/logging.html#uno-logging-extensions --- .../unoapp/MyExtensionsApp.1/App.blank.cs | 108 +++++++-------- .../MyExtensionsApp.1/App.recommended.cs | 127 ++++++++++++++---- .../Platforms/Desktop/Program.cs | 4 - .../Platforms/WebAssembly/Program.cs | 4 - .../Platforms/iOS/Main.iOS.cs | 4 - 5 files changed, 157 insertions(+), 90 deletions(-) diff --git a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.blank.cs b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.blank.cs index a6fbed9ea..aba1617c8 100644 --- a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.blank.cs +++ b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.blank.cs @@ -106,79 +106,81 @@ void OnNavigationFailed(object sender, NavigationFailedEventArgs e) { throw new InvalidOperationException($"Failed to load {e.SourcePageType.FullName}: {e.Exception}"); } -//+:cnd:noEmit -#if (useLoggingFallback) /// /// Configures global Uno Platform logging /// - public static void InitializeLogging() + private static ILoggingBuilder ConfigureLogging(ILoggingBuilder builder, LogLevel defaultLogLevel) { -//-:cnd:noEmit -#if DEBUG - // Logging is disabled by default for release builds, as it incurs a significant - // initialization cost from Microsoft.Extensions.Logging setup. If startup performance - // is a concern for your application, keep this disabled. If you're running on the web or - // desktop targets, you can use URL or command line parameters to enable it. - // - // For more performance documentation: https://platform.uno/docs/articles/Uno-UI-Performance.html - - var factory = LoggerFactory.Create(builder => - { #if __WASM__ - builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider()); + builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider()); #elif __IOS__ - builder.AddProvider(new global::Uno.Extensions.Logging.OSLogLoggerProvider()); + builder.AddProvider(new global::Uno.Extensions.Logging.OSLogLoggerProvider()); - // Log to the Visual Studio Debug console - builder.AddConsole(); -#else - builder.AddConsole(); -#endif + // Log to the Visual Studio Debug console + builder.AddConsole(); +#else // !__WASM__ && !__IOS__ + builder.AddConsole(); +#endif // __WASM__ || __IOS__ - // Exclude logs below this level - builder.SetMinimumLevel(LogLevel.Information); + // Exclude logs below this level + builder.SetMinimumLevel(defaultLogLevel); - // Default filters for Uno Platform namespaces - builder.AddFilter("Uno", LogLevel.Warning); - builder.AddFilter("Windows", LogLevel.Warning); - builder.AddFilter("Microsoft", LogLevel.Warning); + // Default filters for Uno Platform namespaces + builder.AddFilter("Uno", LogLevel.Warning); + builder.AddFilter("Windows", LogLevel.Warning); + builder.AddFilter("Microsoft", LogLevel.Warning); - // Generic Xaml events - // builder.AddFilter("Microsoft.UI.Xaml", LogLevel.Debug ); - // builder.AddFilter("Microsoft.UI.Xaml.VisualStateGroup", LogLevel.Debug ); - // builder.AddFilter("Microsoft.UI.Xaml.StateTriggerBase", LogLevel.Debug ); - // builder.AddFilter("Microsoft.UI.Xaml.UIElement", LogLevel.Debug ); - // builder.AddFilter("Microsoft.UI.Xaml.FrameworkElement", LogLevel.Trace ); + // Generic Xaml events + // builder.AddFilter("Microsoft.UI.Xaml", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.VisualStateGroup", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.StateTriggerBase", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.UIElement", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.FrameworkElement", LogLevel.Trace ); - // Layouter specific messages - // builder.AddFilter("Microsoft.UI.Xaml.Controls", LogLevel.Debug ); - // builder.AddFilter("Microsoft.UI.Xaml.Controls.Layouter", LogLevel.Debug ); - // builder.AddFilter("Microsoft.UI.Xaml.Controls.Panel", LogLevel.Debug ); + // Layouter specific messages + // builder.AddFilter("Microsoft.UI.Xaml.Controls", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.Controls.Layouter", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.Controls.Panel", LogLevel.Debug ); - // builder.AddFilter("Windows.Storage", LogLevel.Debug ); + // builder.AddFilter("Windows.Storage", LogLevel.Debug ); - // Binding related messages - // builder.AddFilter("Microsoft.UI.Xaml.Data", LogLevel.Debug ); - // builder.AddFilter("Microsoft.UI.Xaml.Data", LogLevel.Debug ); + // Binding related messages + // builder.AddFilter("Microsoft.UI.Xaml.Data", LogLevel.Debug ); - // Binder memory references tracking - // builder.AddFilter("Uno.UI.DataBinding.BinderReferenceHolder", LogLevel.Debug ); + // Binder memory references tracking + // builder.AddFilter("Uno.UI.DataBinding.BinderReferenceHolder", LogLevel.Debug ); - // DevServer and HotReload related - // builder.AddFilter("Uno.UI.RemoteControl", LogLevel.Information); + // DevServer and HotReload related + // builder.AddFilter("Uno.UI.RemoteControl", LogLevel.Information); - // Debug JS interop - // builder.AddFilter("Uno.Foundation.WebAssemblyRuntime", LogLevel.Debug ); - }); + // Debug JS interop + // builder.AddFilter("Uno.Foundation.WebAssemblyRuntime", LogLevel.Debug ); - global::Uno.Extensions.LogExtensionPoint.AmbientLoggerFactory = factory; + return builder; + } + + /// + /// Configures global Uno Platform logging + /// + public static void InitializeLogging() + { +#if DEBUG + // Logging is disabled by default for release builds, as it incurs a significant + // initialization cost from Microsoft.Extensions.Logging setup. If startup performance + // is a concern for your application, keep this disabled. If you're running on the web or + // desktop targets, you can use URL or command line parameters to enable it. + // + // For more performance documentation: https://platform.uno/docs/articles/Uno-UI-Performance.html + var factory = LoggerFactory.Create(builder => ConfigureLogging(builder, LogLevel.Information)); + + // LogExtensionPoint.AmbientLoggerFactory is used by `Uno.dll` et al for + // logging messages, such as from `NativeDispatcher`. + global::Uno.Extensions.LogExtensionPoint.AmbientLoggerFactory = factory; #if HAS_UNO global::Uno.UI.Adapter.Microsoft.Extensions.Logging.LoggingAdapter.Initialize(); -#endif -#endif -//+:cnd:noEmit +#endif // HAS_UNO +#endif // DEBUG } -#endif } diff --git a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.recommended.cs b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.recommended.cs index b9fe245e2..49b4913df 100644 --- a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.recommended.cs +++ b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/App.recommended.cs @@ -81,34 +81,15 @@ protected async override void OnLaunched(LaunchActivatedEventArgs args) #if useLogging .UseLogging(configure: (context, logBuilder) => { - // Configure log levels for different categories of logging - logBuilder - .SetMinimumLevel( - context.HostingEnvironment.IsDevelopment() ? + ConfigureLogging( + logBuilder, + // Configure log levels for different categories of logging + context.HostingEnvironment.IsDevelopment() ? LogLevel.Information : LogLevel.Warning) + .CoreLogLevel(LogLevel.Warning); - // Default filters for core Uno Platform namespaces - .CoreLogLevel(LogLevel.Warning); - - // Uno Platform namespace filter groups - // Uncomment individual methods to see more detailed logging - //// Generic Xaml events - //logBuilder.XamlLogLevel(LogLevel.Debug); - //// Layout specific messages - //logBuilder.XamlLayoutLogLevel(LogLevel.Debug); - //// Storage messages - //logBuilder.StorageLogLevel(LogLevel.Debug); - //// Binding related messages - //logBuilder.XamlBindingLogLevel(LogLevel.Debug); - //// Binder memory references tracking - //logBuilder.BinderMemoryReferenceLogLevel(LogLevel.Debug); - //// DevServer and HotReload related - //logBuilder.HotReloadCoreLogLevel(LogLevel.Information); - //// Debug JS interop - //logBuilder.WebAssemblyLogLevel(LogLevel.Debug); - - }, enableUnoLogging: true) + }, enableUnoLogging: false) #endif #if useSerilog .UseSerilog(consoleLoggingEnabled: true, fileLoggingEnabled: true) @@ -311,4 +292,100 @@ private static void RegisterRoutes(IViewRegistry views, IRouteRegistry routes) #endif } #endif + + /// + /// Configures global Uno Platform logging + /// + private static ILoggingBuilder ConfigureLogging(ILoggingBuilder builder, LogLevel defaultLogLevel) + { +#if __WASM__ + builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider()); +#elif __IOS__ + builder.AddProvider(new global::Uno.Extensions.Logging.OSLogLoggerProvider()); + + // Log to the Visual Studio Debug console + builder.AddConsole(); +#else // !__WASM__ && !__IOS__ + builder.AddConsole(); +#endif // __WASM__ || __IOS__ + + // Exclude logs below this level + builder.SetMinimumLevel(defaultLogLevel); + + // Default filters for Uno Platform namespaces + builder.AddFilter("Uno", LogLevel.Warning); + builder.AddFilter("Windows", LogLevel.Warning); + builder.AddFilter("Microsoft", LogLevel.Warning); + + // Generic Xaml events + // builder.AddFilter("Microsoft.UI.Xaml", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.VisualStateGroup", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.StateTriggerBase", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.UIElement", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.FrameworkElement", LogLevel.Trace ); + + // Layouter specific messages + // builder.AddFilter("Microsoft.UI.Xaml.Controls", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.Controls.Layouter", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.Controls.Panel", LogLevel.Debug ); + + // builder.AddFilter("Windows.Storage", LogLevel.Debug ); + + // Binding related messages + // builder.AddFilter("Microsoft.UI.Xaml.Data", LogLevel.Debug ); + // builder.AddFilter("Microsoft.UI.Xaml.Data", LogLevel.Debug ); + + // Binder memory references tracking + // builder.AddFilter("Uno.UI.DataBinding.BinderReferenceHolder", LogLevel.Debug ); + + // DevServer and HotReload related + // builder.AddFilter("Uno.UI.RemoteControl", LogLevel.Information); + + // Debug JS interop + // builder.AddFilter("Uno.Foundation.WebAssemblyRuntime", LogLevel.Debug ); + + // Uno Platform namespace filter groups + // Uncomment individual methods to see more detailed logging + //// Generic Xaml events + //builder.XamlLogLevel(LogLevel.Debug); + //// Layout specific messages + //builder.XamlLayoutLogLevel(LogLevel.Debug); + //// Storage messages + //builder.StorageLogLevel(LogLevel.Debug); + //// Binding related messages + //builder.XamlBindingLogLevel(LogLevel.Debug); + //// Binder memory references tracking + //builder.BinderMemoryReferenceLogLevel(LogLevel.Debug); + //// DevServer and HotReload related + //builder.HotReloadCoreLogLevel(LogLevel.Information); + //// Debug JS interop + //builder.WebAssemblyLogLevel(LogLevel.Debug); + + return builder; + } + + /// + /// Configures global Uno Platform logging + /// + public static void InitializeLogging() + { +#if DEBUG + // Logging is disabled by default for release builds, as it incurs a significant + // initialization cost from Microsoft.Extensions.Logging setup. If startup performance + // is a concern for your application, keep this disabled. If you're running on the web or + // desktop targets, you can use URL or command line parameters to enable it. + // + // For more performance documentation: https://platform.uno/docs/articles/Uno-UI-Performance.html + + var factory = LoggerFactory.Create(builder => ConfigureLogging(builder, LogLevel.Information)); + + // LogExtensionPoint.AmbientLoggerFactory is used by `Uno.dll` et al for + // logging messages, such as from `NativeDispatcher`. + // Some of these can occur *before* Dependency Injection `.UseLogging()` executes. + global::Uno.Extensions.LogExtensionPoint.AmbientLoggerFactory = factory; +#if HAS_UNO + global::Uno.UI.Adapter.Microsoft.Extensions.Logging.LoggingAdapter.Initialize(); +#endif // HAS_UNO +#endif // DEBUG + } } diff --git a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/Desktop/Program.cs b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/Desktop/Program.cs index a9f1f1ddd..f3fcaca76 100644 --- a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/Desktop/Program.cs +++ b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/Desktop/Program.cs @@ -7,11 +7,7 @@ internal class Program [STAThread] public static void Main(string[] args) { -//+:cnd:noEmit -#if (!useDependencyInjection && useLoggingFallback) App.InitializeLogging(); -#endif -//-:cnd:noEmit var host = UnoPlatformHostBuilder.Create() .App(() => new App()) diff --git a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/WebAssembly/Program.cs b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/WebAssembly/Program.cs index 8c1feb391..105483fb9 100644 --- a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/WebAssembly/Program.cs +++ b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/WebAssembly/Program.cs @@ -6,12 +6,8 @@ public class Program { public static async Task Main(string[] args) { -//+:cnd:noEmit -#if (!useDependencyInjection && useLoggingFallback) App.InitializeLogging(); -#endif -//-:cnd:noEmit var host = UnoPlatformHostBuilder.Create() .App(() => new App()) .UseWebAssembly() diff --git a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/iOS/Main.iOS.cs b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/iOS/Main.iOS.cs index 1b55f72f5..d886edbaa 100644 --- a/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/iOS/Main.iOS.cs +++ b/src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/iOS/Main.iOS.cs @@ -8,12 +8,8 @@ public class EntryPoint // This is the main entry point of the application. public static void Main(string[] args) { -//+:cnd:noEmit -#if (!useDependencyInjection && useLoggingFallback) App.InitializeLogging(); -#endif -//-:cnd:noEmit var host = UnoPlatformHostBuilder.Create() .App(() => new App()) .UseAppleUIKit()