diff --git a/Directory.Build.props b/Directory.Build.props index f1e1da0..6acef26 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,9 +8,8 @@ 1.0.3 - - 1.3.3 + + 1.3.4 1.0.0 diff --git a/src/Features/PowerCSharp.Feature.Cache.Abstractions/NoOp/NoOpCacheService.cs b/src/Features/PowerCSharp.Feature.Cache.Abstractions/NoOp/NoOpCacheService.cs index 99b08dc..2ad548f 100644 --- a/src/Features/PowerCSharp.Feature.Cache.Abstractions/NoOp/NoOpCacheService.cs +++ b/src/Features/PowerCSharp.Feature.Cache.Abstractions/NoOp/NoOpCacheService.cs @@ -14,7 +14,13 @@ public sealed class NoOpCacheService : ICacheService /// Creates the NoOp cache and logs that caching is inert. public NoOpCacheService(ILogger logger) { - logger.LogInformation("Cache feature is disabled or unconfigured; using NoOp in-memory cache."); + // NOTE: this constructor can run even when a real provider (e.g. BitFaster) is the one + // actually injected as ICacheService — ASP.NET Core's ValidateOnBuild (on by default in + // Development) constructs every registered descriptor, including this shadowed safe-off + // floor, purely to validate the DI graph. Seeing this log does not by itself mean the app + // is using NoOp; check the Cache feature's "Cache feature resolved" diagnostic line for + // the implementation actually bound to ICacheService. + logger.LogInformation("NoOp in-memory cache constructed (ICacheService safe-off floor)."); } /// diff --git a/src/Features/PowerCSharp.Feature.Cache.Abstractions/NoOp/NoOpDiskCacheService.cs b/src/Features/PowerCSharp.Feature.Cache.Abstractions/NoOp/NoOpDiskCacheService.cs index 0afbaed..0fd34d1 100644 --- a/src/Features/PowerCSharp.Feature.Cache.Abstractions/NoOp/NoOpDiskCacheService.cs +++ b/src/Features/PowerCSharp.Feature.Cache.Abstractions/NoOp/NoOpDiskCacheService.cs @@ -14,7 +14,11 @@ public sealed class NoOpDiskCacheService : IDiskCacheService /// Creates the NoOp disk cache and logs that disk caching is inert. public NoOpDiskCacheService(ILogger logger) { - logger.LogInformation("Cache feature is disabled or unconfigured; using NoOp disk cache."); + // NOTE: see the matching comment in NoOpCacheService — this can be constructed by + // ASP.NET Core's ValidateOnBuild even when a real provider (e.g. Disk) is the one + // actually injected as IDiskCacheService. Check the Cache feature's "Cache feature + // resolved" diagnostic line for the implementation actually in use. + logger.LogInformation("NoOp disk cache constructed (IDiskCacheService safe-off floor)."); } /// diff --git a/src/Features/PowerCSharp.Feature.Cache.BitFaster/BitFasterCacheService.cs b/src/Features/PowerCSharp.Feature.Cache.BitFaster/BitFasterCacheService.cs index f4c3aa1..04c0d6a 100644 --- a/src/Features/PowerCSharp.Feature.Cache.BitFaster/BitFasterCacheService.cs +++ b/src/Features/PowerCSharp.Feature.Cache.BitFaster/BitFasterCacheService.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Diagnostics; using BitFaster.Caching.Lru; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using PowerCSharp.Feature.Cache.Abstractions; @@ -20,10 +21,16 @@ public sealed class BitFasterCacheService : ICacheService private readonly ConcurrentDictionary _metadata = new(); /// Creates the cache with capacity from . - public BitFasterCacheService(IOptions options) + public BitFasterCacheService(IOptions options, ILogger logger) { var capacity = Math.Max(1, options.Value.Capacity); _cache = new ConcurrentLru(capacity); + + logger.LogInformation( + "BitFaster in-memory cache initialized (capacity={Capacity}). This constructor running does not " + + "by itself confirm this instance is the one injected as ICacheService — see the Cache feature's " + + "'Cache feature resolved' diagnostic line for the actually-bound implementation.", + capacity); } /// diff --git a/src/Features/PowerCSharp.Feature.Cache.BitFaster/PowerCSharp.Feature.Cache.BitFaster.csproj b/src/Features/PowerCSharp.Feature.Cache.BitFaster/PowerCSharp.Feature.Cache.BitFaster.csproj index 4fc8ff0..250b2d3 100644 --- a/src/Features/PowerCSharp.Feature.Cache.BitFaster/PowerCSharp.Feature.Cache.BitFaster.csproj +++ b/src/Features/PowerCSharp.Feature.Cache.BitFaster/PowerCSharp.Feature.Cache.BitFaster.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Features/PowerCSharp.Feature.Cache/CacheFeatureModule.cs b/src/Features/PowerCSharp.Feature.Cache/CacheFeatureModule.cs index 7e33b5b..746665d 100644 --- a/src/Features/PowerCSharp.Feature.Cache/CacheFeatureModule.cs +++ b/src/Features/PowerCSharp.Feature.Cache/CacheFeatureModule.cs @@ -31,10 +31,14 @@ public void ConfigureServices(IFeatureRegistrationContext context) { // Force-load the abstractions assembly so the CLR can resolve its types during discovery. _ = Assembly.Load("PowerCSharp.Feature.Cache.Abstractions"); - + context.Services.Configure( context.Configuration.GetSection($"PowerFeatures:{Key}")); + // NOTE: context.Logger is NullLogger at this stage (the DI container isn't built yet, so + // no real ILoggerFactory exists). Anything logged here is silently discarded. The + // authoritative "which implementation actually won" diagnostic is logged for real in + // ConfigurePipeline below, once a real logger is available. if (!context.Flags.IsEnabled(FeatureKey)) { context.Logger.LogInformation("Cache feature disabled; registering NoOp cache services."); @@ -45,4 +49,26 @@ public void ConfigureServices(IFeatureRegistrationContext context) context.Services.TryAddSingleton(); context.Services.TryAddSingleton(); } + + /// + /// + /// Diagnostic-only: resolves the services actually bound to and + /// from the fully-built container and logs their concrete + /// types with a real logger. This is the authoritative answer to "which cache implementation + /// is actually active" — unlike constructor-time logs from individual implementations (which + /// can fire for shadowed/overridden registrations too, e.g. under ASP.NET Core's + /// ValidateOnBuild in Development, without ever being the instance that's actually injected). + /// + public void ConfigurePipeline(IFeaturePipelineContext context) + { + var services = context.App.ApplicationServices; + var cache = services.GetRequiredService(); + var diskCache = services.GetRequiredService(); + + context.Logger.LogInformation( + "Cache feature resolved -> ICacheService: {CacheImplementation}; IDiskCacheService: {DiskCacheImplementation}. " + + "A NoOp implementation here means no provider package overrode the safe-off floor for that contract.", + cache.GetType().FullName, + diskCache.GetType().FullName); + } }