Open
Conversation
jonpryor
added a commit
to unoplatform/uno.extensions
that referenced
this pull request
Jan 13, 2026
Fixes: #3008 Context: unoplatform/uno.templates#1921 `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<IMyService, MyService>() // … interface IMyService; class MyService : IMyService { public MyService(ILogger<MyService> logger) => …; } The "No Dependency Injection" pattern provides `ILogger` instances from the [`Log<T>(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 is *not* done by *any* `dotnet new unoapp` templates. 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. Attempt to begin improving this scenario by updating `HostBuilder.CreateServiceProvider()` (via `HostBuilder.Build()`) to [Replace][5] the `ILoggerFactory` registration added by `.AddLogging()` with the `ILoggerFactory` instance stored by `Uno.Extensions.LogExtensionPoint.AmbientLoggerFactory`. This means that *if* NoDI init is performed *before* `HostBuilder.Build()` is invoked, then the `ILoggerFactory` instance created as part of NoDI init is also registered with `IHost.Services`. This in turn means that if NoDI init is performed *and not* DI init, registered services will obtain an `ILogger` instance which follows the NoDI init configuration. Meaning that *only* NoDI init will be required to see *both* NoDI *and* DI-originating 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 [5]: https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.extensions.servicecollectiondescriptorextensions.replace?view=net-10.0-pp#microsoft-extensions-dependencyinjection-extensions-servicecollectiondescriptorextensions-replace(microsoft-extensions-dependencyinjection-iservicecollection-microsoft-extensions-dependencyinjection-servicedescriptor)
7 tasks
Context: unoplatform/uno.extensions#3008 Context: unoplatform/uno.extensions#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<IMyService, MyService>() // … interface IMyService; class MyService : IMyService { public MyService(ILogger<MyService> logger) => …; } The "No Dependency Injection" pattern provides `ILogger` instances from the [`Log<T>(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
c892084 to
e2fab7d
Compare
jeromelaban
approved these changes
Jan 13, 2026
jonpryor
added a commit
to unoplatform/uno.extensions
that referenced
this pull request
Jan 15, 2026
Context: #3008 Context: #3011 Context: unoplatform/uno.templates#1921 Issue #3008 details that there are two `ILogger` "environments" in a Uno app: * The `App.InitializeLogging()` environment ("NoDI"), and * The environment configured by Dependency Injection methods such as `.UseLogging()` and `.UseSerilog()` ("DI"). The problem is that "DI" logging *cannot* capture log messages produced before the DI environment is built, frequently via `builder.Build()` or `builder.NavigateAsync<TShell>()`. *A lot* of code can execute before those methods complete. A further problem is that no default template actually configures both environments: if you use `dotnet new unoapp -di False` you get the NoDI `App.InitializeLogging()` environment, which *will not* log messages from the Dependency Injection enviornment. If you use `dotnet new unoapp -preset "recommended"` you get *only* the DI-based logging, thus missing all messages from the NoDI environment. Thus, the idea for "`ILogger` configuration unification": What If instead of having two separate places for logging, we just had one: the NoDI `App.InitializeLogging()` location. This can be made to work via #3011, which updates `HostBuilder.Build()` so that *if* `LogExtensionPoint.AmbientLoggerFactory` is set, it *replaces* the `ILoggerFactory` that `HostBuilder.Build()` normally configures. This allows `App.InitializeLogging()` to also be used in the DI environment. So far, so useful. But what about [Serilog][0]? Serilog is a widely recommended logger library, and the current `.UseSerilog()` extension method *requires* a full Dependency Injection environment: it cannot be invoked within `App.InitializeLogging()`. Try to square this circle by adding a new `ILoggingBuilder.AddSerilog()` extension method. This would allow Serilog to be configured within `App.InitializeLogging()`, helping to support a unified logging setup experience. [0]: https://platform.uno/docs/articles/external/uno.extensions/doc/Overview/Logging/LoggingOverview.html#serilog
7 tasks
| /// <summary> | ||
| /// Configures global Uno Platform logging | ||
| /// </summary> | ||
| public static void InitializeLogging() |
Contributor
There was a problem hiding this comment.
I think keeping the original (single) InitializeLogging method will be simpler and will be consistent across both blank and recommended templates.
In fact, we could even move this to App.logging.cs or similar in order to make it clearer that's where logging is configured.
| { | ||
| //+:cnd:noEmit | ||
| #if (!useDependencyInjection && useLoggingFallback) | ||
| App.InitializeLogging(); |
Member
There was a problem hiding this comment.
Analogous change is also needed in src/Uno.Templates/content/unoapp/MyExtensionsApp.1/Platforms/Android/Main.Android.cs
jonpryor
added a commit
to unoplatform/uno.extensions
that referenced
this pull request
Jan 28, 2026
Fixes: #3008 Context: unoplatform/uno.templates#1921 `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<IMyService, MyService>() // … interface IMyService; class MyService : IMyService { public MyService(ILogger<MyService> logger) => …; } The "No Dependency Injection" pattern provides `ILogger` instances from the [`Log<T>(this 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 is *not* done by *any* `dotnet new unoapp` templates. 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. Attempt to begin improving this scenario by updating `HostBuilder.CreateServiceProvider()` (via `HostBuilder.Build()`) to [Replace][5] the `ILoggerFactory` registration added by `.AddLogging()` with the `ILoggerFactory` instance stored by `Uno.Extensions.LogExtensionPoint.AmbientLoggerFactory`. This means that *if* NoDI init is performed *before* `HostBuilder.Build()` is invoked, then the `ILoggerFactory` instance created as part of NoDI init is also registered with `IHost.Services`. This in turn means that if NoDI init is performed *and not* DI init, registered services will obtain an `ILogger` instance which follows the NoDI init configuration. Meaning that *only* NoDI init will be required to see *both* NoDI *and* DI-originating messages. Additionally, if both NoDI and DI init is performed, DI init will log a warning message indicating that the `AmbientLoggerFactory` is used. [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 [5]: https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.extensions.servicecollectiondescriptorextensions.replace?view=net-10.0-pp#microsoft-extensions-dependencyinjection-extensions-servicecollectiondescriptorextensions-replace(microsoft-extensions-dependencyinjection-iservicecollection-microsoft-extensions-dependencyinjection-servicedescriptor)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context: unoplatform/uno.extensions#3008
Context: unoplatform/uno.extensions#3011
dotnet new unoapptemplates have two patterns forILoggerconfiguration:The recommended pattern, which uses Dependency Injection (DI) via
.UseLogging():The "No Dependency Injection" (NoDI) pattern, which doesn't use Dependency Injection, and instead has an
App.InitializeLogging()method called fromMain():The wonderful thing is, those log different, non-overlapping, things.
The Dependency Injection pattern provides an
ILoggerinstance to constructors of types registered with e.g.services.AddSingleton():The "No Dependency Injection" pattern provides
ILoggerinstances from theLog<T>(T)extension method, which is used extensively within Uno itself, e.g.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 anydotnet new unoapptemplates 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 ifApp.InitializeLogging()registers anILoggerFactory, that instance will be used by default by DI-based logging, even if.UseLogging()is not used.Update uno.templates to finish the harmonization:
Always emit an
App.InitializeLogging()method, and call it fromMain().`Refactor
App.InitializeLogging()and the.UseLogging()callback so that both call a newApp.ConfigureLogging()method.ConfigureLogging()is responsible for configuringn theILoggingBuilderinstance, and this setup allows bothApp.InitializeLogging()and.UseLogging()to share the same code, centralizingILoggerconfiguration.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.GitHub Issue (If applicable): closes #
PR Type
What kind of change does this PR introduce?
What is the current behavior?
What is the new behavior?
PR Checklist
Please check if your PR fulfills the following requirements:
Other information
Internal Issue (If applicable):