Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 33 additions & 25 deletions src/Uno.Core.Extensions.Logging.Singleton/LogExtensionPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
// ******************************************************************
using System;
using System.Threading;
using CommonServiceLocator;
using Microsoft.Extensions.Logging;
using System.Diagnostics;

Expand All @@ -26,6 +25,7 @@ public static class LogExtensionPoint
{
private static ILoggerFactory? _loggerFactory;
private static Func<ILoggerFactory?, ILoggerFactory, ILoggerFactory>? _factoryInterceptor;
private static IServiceProvider? _serviceProvider;

private static class Container<T>
{
Expand All @@ -49,6 +49,21 @@ public static ILoggerFactory AmbientLoggerFactory
}
}

/// <summary>
/// An optional <see cref="IServiceProvider"/> used to resolve the ambient
/// <see cref="ILoggerFactory"/> the first time one is requested.
/// </summary>
/// <remarks>
/// Assign this before the first logger is created; once <see cref="AmbientLoggerFactory"/>
/// has resolved a factory it is cached. When left <c>null</c>, a default
/// <see cref="LoggerFactory"/> is used.
/// </remarks>
public static IServiceProvider? ServiceProvider
{
get => Volatile.Read(ref _serviceProvider);
set => Volatile.Write(ref _serviceProvider, value);
}

/// <summary>
/// Registers an optional interceptor that is invoked whenever <see cref="AmbientLoggerFactory"/>
/// is set. The callback receives the current factory and the proposed new factory, and must
Expand Down Expand Up @@ -122,37 +137,30 @@ public static ILogger Log(this Type forType)

private static ILoggerFactory GetFactory()
{
if (ServiceLocator.IsLocationProviderSet)
var serviceProvider = Volatile.Read(ref _serviceProvider);

if (serviceProvider is null)
{
try
{
var service = ServiceLocator.Current.GetService(typeof(ILoggerFactory));
return new LoggerFactory();
}

if (service is ILoggerFactory factory)
{
return factory;
}
try
{
var service = serviceProvider.GetService(typeof(ILoggerFactory));

throw new InvalidOperationException($"The service {service?.GetType()} is not of type ILoggerFactory.");
}
catch (Exception e)
if (service is ILoggerFactory factory)
{
if (e is NullReferenceException || e is InvalidOperationException)
{
#if HAS_CONSOLE
Console.WriteLine("***** WARNING *****");
Console.WriteLine("Unable to get the service locator ({0}), using the default logger to System.Diagnostics.Debug", e.Message);
#endif
return new LoggerFactory();
}
else
{
throw;
}
return factory;
}

throw new InvalidOperationException($"The service {service?.GetType()} is not of type ILoggerFactory.");
}
else
catch (Exception e) when (e is NullReferenceException or InvalidOperationException)
{
#if HAS_CONSOLE
Console.WriteLine("***** WARNING *****");
Console.WriteLine("Unable to resolve an ILoggerFactory from the service provider ({0}), using the default logger to System.Diagnostics.Debug", e.Message);
#endif
return new LoggerFactory();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
<Compile Include="..\Uno.Core.Extensions.Threading\Transactional.cs" Link="Helper\Transactional.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommonServiceLocator" Version="2.0.5" />
</ItemGroup>

<ItemGroup>
<Folder Include="Helper\" />
</ItemGroup>
Expand Down
73 changes: 73 additions & 0 deletions src/Uno.Core.Tests/LogExtensionPointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ public void Setup()
{
// Clear any interceptor from previous tests.
LogExtensionPoint.RegisterFactoryInterceptor(null);
LogExtensionPoint.ServiceProvider = null;
}

[TestCleanup]
public void Cleanup()
{
LogExtensionPoint.RegisterFactoryInterceptor(null);
LogExtensionPoint.ServiceProvider = null;
}

[TestMethod]
Expand Down Expand Up @@ -231,6 +233,77 @@ public void AmbientLoggerFactory_Set_InterceptorRunsOnceEvenUnderContention()
Assert.AreEqual(1, callCount, "Interceptor should run exactly once per set");
}

[TestMethod]
public void ServiceProvider_RoundTripsAssignedValue()
{
var provider = new StubServiceProvider(t => null);

LogExtensionPoint.ServiceProvider = provider;

Assert.AreSame(provider, LogExtensionPoint.ServiceProvider);
}

[TestMethod]
public void AmbientLoggerFactory_ServiceProviderNotSet_UsesDefaultFactory()
{
ResetAmbientFactory();

Assert.IsNotNull(LogExtensionPoint.AmbientLoggerFactory);
}

[TestMethod]
public void AmbientLoggerFactory_ServiceProviderResolvesFactory_UsesResolvedFactory()
{
var expected = new LoggerFactory();
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
LogExtensionPoint.ServiceProvider = new StubServiceProvider(
t => t == typeof(ILoggerFactory) ? expected : null);
ResetAmbientFactory();

Assert.AreSame(expected, LogExtensionPoint.AmbientLoggerFactory);
}

[TestMethod]
public void AmbientLoggerFactory_ServiceProviderReturnsWrongType_FallsBackToDefaultFactory()
{
LogExtensionPoint.ServiceProvider = new StubServiceProvider(t => "not a logger factory");
ResetAmbientFactory();

Assert.IsNotNull(LogExtensionPoint.AmbientLoggerFactory);
}

[TestMethod]
[ExpectedException(typeof(NotSupportedException))]
public void AmbientLoggerFactory_ServiceProviderThrowsUnexpectedException_Rethrows()
{
// Only NullReference/InvalidOperation fall back to a default factory; anything
// else indicates a broken container and must surface to the caller.
LogExtensionPoint.ServiceProvider = new StubServiceProvider(
t => { throw new NotSupportedException(); });
ResetAmbientFactory();

var unused = LogExtensionPoint.AmbientLoggerFactory;
}

/// <summary>
/// Clears the cached ambient factory so the next read resolves one again.
/// </summary>
private static void ResetAmbientFactory()
{
LogExtensionPoint.AmbientLoggerFactory = null;
}

/// <summary>
/// Minimal <see cref="IServiceProvider"/> driven by a resolver delegate.
/// </summary>
private sealed class StubServiceProvider : IServiceProvider
{
private readonly Func<Type, object> _resolve;

public StubServiceProvider(Func<Type, object> resolve) { _resolve = resolve; }

public object GetService(Type serviceType) { return _resolve(serviceType); }
}

/// <summary>
/// Simple test logger provider that captures log messages.
/// </summary>
Expand Down