Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Splitio.Redis/Splitio.Redis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Version>7.13.0</Version>
<Version>7.14.0-rc1</Version>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>SplitioRedis.snk</AssemblyOriginatorKeyFile>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
Expand Down
58 changes: 56 additions & 2 deletions src/Splitio/Services/Logger/SplitLoggerFactoryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#if NET_LATEST
#if NET_LATEST
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Logging
{
public static class SplitLoggerFactoryExtensions
Expand All @@ -18,6 +20,58 @@ public static ILoggerFactory GetLoggerFactory()
}

public static bool LoggerFactoryHasValue => _loggerFactory != null;

/// <summary>
/// Configures Split logs for ASP.NET Core applications.
/// The logger factory will be captured when it is first resolved from DI.
/// Only wraps the default ASP.NET Core factory registration.
/// </summary>
public static ILoggingBuilder AddSplitLogs(this ILoggingBuilder builder)
{
// Find and wrap only the default ILoggerFactory registration
ServiceDescriptor loggerFactoryDescriptor = null;
for (int i = 0; i < builder.Services.Count; i++)
{
if (builder.Services[i].ServiceType == typeof(ILoggerFactory))
{
loggerFactoryDescriptor = builder.Services[i];

// Wrap this specific registration to capture the factory when resolved
var wrappedDescriptor = ServiceDescriptor.Describe(
typeof(ILoggerFactory),
sp =>
{
ILoggerFactory factory;

// Resolve the original factory based on how it was registered
if (loggerFactoryDescriptor.ImplementationFactory != null)
{
factory = (ILoggerFactory)loggerFactoryDescriptor.ImplementationFactory(sp);
}
else if (loggerFactoryDescriptor.ImplementationInstance != null)
{
factory = (ILoggerFactory)loggerFactoryDescriptor.ImplementationInstance;
}
else
{
factory = (ILoggerFactory)ActivatorUtilities.CreateInstance(sp, loggerFactoryDescriptor.ImplementationType);
}

// Capture the factory for Split logging
factory.AddSplitLogs();

return factory;
},
loggerFactoryDescriptor.Lifetime);

// Replace only this registration
builder.Services[i] = wrappedDescriptor;
break;
}
}

return builder;
}
}
}
#endif
#endif
3 changes: 2 additions & 1 deletion src/Splitio/Splitio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<Version>7.13.0</Version>
<Version>7.14.0-rc1</Version>
<RootNamespace>Splitio</RootNamespace>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Splitio.snk</AssemblyOriginatorKeyFile>
Expand All @@ -29,6 +29,7 @@

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'net9.0' Or '$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net7.0' Or '$(TargetFramework)' == 'net6.0' Or '$(TargetFramework)' == 'net5.0'">
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.32" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.32" />
<PackageReference Include="System.IO.FileSystem.Watcher" Version="4.3.0" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="BitFaster.Caching" Version="2.4.1" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#if NETCORE
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Splitio_Tests.Unit_Tests.Logger
{
[TestClass]
public class SplitLoggerFactoryExtensionsAspNetTests
{
[TestCleanup]
public void Cleanup()
{
// Reset static state between tests
var emptyFactory = LoggerFactory.Create(builder => { });
Microsoft.Extensions.Logging.SplitLoggerFactoryExtensions.AddSplitLogs(emptyFactory);
}

[TestMethod]
public void AddSplitLogs_WithHostBuilder_ShouldSetLoggerFactoryBeforeHostRuns()
{
// Arrange - Simulate ASP.NET Core Host Builder pattern
var hostBuilder = Host.CreateDefaultBuilder()
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddSplitLogs();
});

// Act - Build the host (this should trigger DI container creation)
using var host = hostBuilder.Build();

// Assert - Check if logger factory is set BEFORE calling host.Run()
// This is critical because logging might be used during startup
var hasValue = Microsoft.Extensions.Logging.SplitLoggerFactoryExtensions.LoggerFactoryHasValue;
var factory = Microsoft.Extensions.Logging.SplitLoggerFactoryExtensions.GetLoggerFactory();

// The current implementation may fail this because the hosted service
// factory is only called when resolving IHostedService instances
Assert.IsNotNull(factory, "Logger factory should be set after Build() but before Run()");
Assert.IsTrue(hasValue, "LoggerFactoryHasValue should be true after Build()");
}

[TestMethod]
public void AddSplitLogs_WithHostBuilder_LoggerFactoryAvailableAfterStart()
{
// Arrange
var hostBuilder = Host.CreateDefaultBuilder()
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddSplitLogs();
});

// Act - Build and start the host
using var host = hostBuilder.Build();

// Force hosted services to be resolved
var hostedServices = host.Services.GetServices<IHostedService>();
foreach (var _ in hostedServices) { } // Force enumeration

// Assert
var hasValue = Microsoft.Extensions.Logging.SplitLoggerFactoryExtensions.LoggerFactoryHasValue;
var factory = Microsoft.Extensions.Logging.SplitLoggerFactoryExtensions.GetLoggerFactory();

Assert.IsNotNull(factory, "Logger factory should be set after hosted services are resolved");
Assert.IsTrue(hasValue);
}

[TestMethod]
public void AddSplitLogs_WithServiceProvider_LoggerFactoryAvailableImmediately()
{
// Arrange - Test eager initialization approach
var services = new ServiceCollection();
services.AddLogging(logging =>
{
logging.AddSplitLogs();
});

// Act - Build service provider
using var serviceProvider = services.BuildServiceProvider();

// Force hosted service to be registered by resolving it
var hostedService = serviceProvider.GetServices<IHostedService>();
var _ = hostedService.GetEnumerator(); // Start enumeration

// Assert - This might not work until hosted services are fully resolved
var hasValue = Microsoft.Extensions.Logging.SplitLoggerFactoryExtensions.LoggerFactoryHasValue;

// If this fails, it means the timing issue exists
Assert.IsTrue(hasValue, "Logger factory should be available after service resolution");
}

[TestMethod]
public void AddSplitLogs_ManualInitialization_WorksImmediately()
{
// Arrange - Alternative approach: manual initialization
var services = new ServiceCollection();
services.AddLogging();

using var serviceProvider = services.BuildServiceProvider();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();

// Act - Manually call AddSplitLogs on the factory
loggerFactory.AddSplitLogs();

// Assert
var hasValue = Microsoft.Extensions.Logging.SplitLoggerFactoryExtensions.LoggerFactoryHasValue;
var retrievedFactory = Microsoft.Extensions.Logging.SplitLoggerFactoryExtensions.GetLoggerFactory();

Assert.IsTrue(hasValue);
Assert.AreSame(loggerFactory, retrievedFactory);
}
}
}
#endif
Loading