Skip to content

fix: HostBuilder.Build() shares LogExtensionPoint.AmbientLoggerFactory#3011

Draft
jonpryor wants to merge 1 commit intomainfrom
dev/jonpryor/jonp-try-to-replace-ILoggerFactory-with-LogExtensionPoint.AmbientLoggerFactory
Draft

fix: HostBuilder.Build() shares LogExtensionPoint.AmbientLoggerFactory#3011
jonpryor wants to merge 1 commit intomainfrom
dev/jonpryor/jonp-try-to-replace-ILoggerFactory-with-LogExtensionPoint.AmbientLoggerFactory

Conversation

@jonpryor
Copy link
Contributor

@jonpryor jonpryor commented 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():

    dotnet new unoapp -preset "recommended"
    
  2. The "No Dependency Injection" (NoDI) pattern, which doesn't use Dependency Injection, and instead has an App.InitializeLogging() method called from Main():

    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():

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, 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 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.

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:

  • Tested code with current supported SDKs
  • Docs have been added/updated which fit documentation template. (for bug fixes / features)
  • Unit Tests and/or UI Tests for the changes have been added (for bug fixes / features) (if applicable)
  • Wasm UI Tests are not showing unexpected any differences. Validate PR Screenshots Compare Test Run results.
  • Contains NO breaking changes
  • Updated the Release Notes
  • Associated with an issue (GitHub or internal)

Other information

Internal Issue (If applicable):

@github-actions
Copy link

Azure Static Web Apps: Your stage site is ready! Visit it here: https://delightful-moss-0c5b8040f-3011.eastus2.azurestaticapps.net

jonpryor added a commit to unoplatform/uno.templates that referenced this pull request Jan 13, 2026
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 configuringn 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
jonpryor added a commit to unoplatform/uno.templates that referenced this pull request Jan 13, 2026
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 configuringn 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
@jonpryor jonpryor force-pushed the dev/jonpryor/jonp-try-to-replace-ILoggerFactory-with-LogExtensionPoint.AmbientLoggerFactory branch from e890863 to abb3fc9 Compare January 13, 2026 03:42
@github-actions
Copy link

Azure Static Web Apps: Your stage site is ready! Visit it here: https://delightful-moss-0c5b8040f-3011.eastus2.azurestaticapps.net

jonpryor added a commit to unoplatform/uno.templates that referenced this pull request Jan 13, 2026
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
jonpryor added a commit 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
@jonpryor jonpryor marked this pull request as draft January 19, 2026 14:52
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)
@jonpryor jonpryor force-pushed the dev/jonpryor/jonp-try-to-replace-ILoggerFactory-with-LogExtensionPoint.AmbientLoggerFactory branch from abb3fc9 to d159348 Compare January 28, 2026 14:22
@jonpryor
Copy link
Contributor Author

I'm increasingly convinced that this approach isn't viable, and will require too many changes to the ecosystem to be viable. See also: #3008 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dependency-Injection-based logging cannot log all the things

1 participant