This guide provides solutions for common configuration issues, debugging techniques, and troubleshooting steps for MeteredMemoryCache implementations. Use this guide to diagnose and resolve problems with cache metrics, service registration, and OpenTelemetry integration.
Symptom: Cache operations are working but no metrics appear in your monitoring system.
Possible Causes & Solutions:
// ❌ Problem: Meter not registered
services.AddNamedMeteredMemoryCache("user-cache");
// ✅ Solution: Ensure Meter is registered
services.AddSingleton<Meter>(sp => new Meter("MyApp.Cache"));
services.AddNamedMeteredMemoryCache("user-cache");// ❌ Problem: Missing OpenTelemetry setup
services.AddNamedMeteredMemoryCache("user-cache");
// ✅ Solution: Add OpenTelemetry metrics
services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddMeter("MyApp.Cache") // Must match meter name
.AddOtlpExporter());// ❌ Problem: Meter names don't match
services.AddSingleton<Meter>(sp => new Meter("AppCache"));
services.AddOpenTelemetry()
.WithMetrics(metrics => metrics.AddMeter("MyApp.Cache")); // Different name!
// ✅ Solution: Use consistent meter names
const string METER_NAME = "MyApp.Cache";
services.AddSingleton<Meter>(sp => new Meter(METER_NAME));
services.AddOpenTelemetry()
.WithMetrics(metrics => metrics.AddMeter(METER_NAME));Symptom: InvalidOperationException or dependency injection errors during startup.
// ❌ Problem: Conflicting cache registrations
services.AddMemoryCache();
services.AddNamedMeteredMemoryCache("cache1");
services.AddNamedMeteredMemoryCache("cache2"); // May conflict with first registration
// ✅ Solution: Use keyed services for multiple caches
services.AddKeyedSingleton<IMemoryCache>("cache1", (sp, key) =>
sp.GetRequiredKeyedService<IMemoryCache>("cache1"));
services.AddKeyedSingleton<IMemoryCache>("cache2", (sp, key) =>
sp.GetRequiredKeyedService<IMemoryCache>("cache2"));// ❌ Problem: Trying to decorate non-existent cache
services.DecorateMemoryCacheWithMetrics("my-cache");
// ✅ Solution: Register base cache first
services.AddMemoryCache();
services.DecorateMemoryCacheWithMetrics("my-cache");// ❌ Problem: Missing options or validation services
services.AddNamedMeteredMemoryCache("cache");
// Missing: Options framework, validation, etc.
// ✅ Solution: Ensure all dependencies are registered
services.AddOptions(); // Required for options pattern
services.AddNamedMeteredMemoryCache("cache");Symptom: OptionsValidationException during cache creation or service startup.
// ❌ Problem: Invalid cache name
services.AddNamedMeteredMemoryCache(""); // Empty string
services.AddNamedMeteredMemoryCache(" "); // Whitespace
services.AddNamedMeteredMemoryCache(null!); // Null
// ✅ Solution: Use valid cache names
services.AddNamedMeteredMemoryCache("user-cache");
services.AddNamedMeteredMemoryCache("product.catalog");
services.AddNamedMeteredMemoryCache("session_store");// ❌ Problem: Invalid tag configuration
services.AddNamedMeteredMemoryCache("cache", options =>
{
options.AdditionalTags.Add("", "value"); // Empty key
options.AdditionalTags.Add("key", null!); // Null value
options.AdditionalTags.Add("cache.name", "override"); // Reserved key
});
// ✅ Solution: Use valid tag keys and values
services.AddNamedMeteredMemoryCache("cache", options =>
{
options.AdditionalTags.Add("environment", "production");
options.AdditionalTags.Add("region", "us-east-1");
options.AdditionalTags.Add("version", "v1.2.3");
});Symptom: Unexpected performance degradation after adding MeteredMemoryCache.
// ❌ Problem: Too many dynamic tags
services.AddNamedMeteredMemoryCache("cache", options =>
{
// Adding 50+ tags will impact performance
for (int i = 0; i < 100; i++)
{
options.AdditionalTags.Add($"tag{i}", $"value{i}");
}
});
// ✅ Solution: Limit to essential tags
services.AddNamedMeteredMemoryCache("cache", options =>
{
options.AdditionalTags.Add("environment", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));
options.AdditionalTags.Add("instance", Environment.MachineName);
// Keep to 5-10 tags maximum
});// ❌ Problem: Disposing inner cache when it shouldn't be
services.AddSingleton<IMemoryCache>(sp => new MemoryCache(new MemoryCacheOptions()));
services.DecorateMemoryCacheWithMetrics("cache"); // Will dispose the singleton!
// ✅ Solution: Control disposal behavior
services.AddSingleton<IMemoryCache>(sp => new MemoryCache(new MemoryCacheOptions()));
services.DecorateMemoryCacheWithMetrics("cache", configureOptions: options =>
{
options.DisposeInner = false; // Don't dispose singleton
});// Add detailed logging for dependency injection
services.AddLogging(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Debug);
});
// Log OpenTelemetry metrics export
services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddMeter("MyApp.Cache")
.AddConsoleExporter() // Add console exporter for debugging
.AddOtlpExporter());// Check service registrations at startup
public void ConfigureServices(IServiceCollection services)
{
services.AddNamedMeteredMemoryCache("user-cache");
// Debug service registrations
var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
try
{
var cache = scope.ServiceProvider.GetRequiredService<IMemoryCache>();
var isMetered = cache is MeteredMemoryCache;
Console.WriteLine($"Cache is metered: {isMetered}");
var meter = scope.ServiceProvider.GetRequiredService<Meter>();
Console.WriteLine($"Meter name: {meter.Name}");
}
catch (Exception ex)
{
Console.WriteLine($"Service resolution failed: {ex.Message}");
}
}// Create a test harness to verify metrics
public class MetricsTestHarness
{
private readonly List<Measurement<long>> _measurements = new();
private readonly MeterProvider _meterProvider;
public MetricsTestHarness()
{
_meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter("MyApp.Cache")
.AddInMemoryExporter(_measurements)
.Build();
}
public void TestCacheMetrics()
{
using var serviceProvider = CreateServiceProvider();
var cache = serviceProvider.GetRequiredService<IMemoryCache>();
// Perform cache operations
cache.Set("key1", "value1");
cache.TryGetValue("key1", out _); // Hit
cache.TryGetValue("key2", out _); // Miss
// Verify metrics were collected
_meterProvider.ForceFlush(TimeSpan.FromSeconds(1));
var hits = _measurements.Where(m => m.Name == "cache_hits_total").Sum(m => m.Value);
var misses = _measurements.Where(m => m.Name == "cache_misses_total").Sum(m => m.Value);
Console.WriteLine($"Hits: {hits}, Misses: {misses}");
}
}// Track memory allocation in development
public class MemoryTrackingCache : IMemoryCache
{
private readonly IMemoryCache _inner;
private long _allocationBytes;
public MemoryTrackingCache(IMemoryCache inner)
{
_inner = inner;
}
public ICacheEntry CreateEntry(object key)
{
var before = GC.GetTotalMemory(false);
var entry = _inner.CreateEntry(key);
var after = GC.GetTotalMemory(false);
Interlocked.Add(ref _allocationBytes, after - before);
Console.WriteLine($"CreateEntry allocated: {after - before} bytes (Total: {_allocationBytes})");
return entry;
}
// Implement other IMemoryCache methods...
}# Check if metrics are being exported (using OTLP HTTP exporter)
curl -X POST http://localhost:4318/v1/metrics \
-H "Content-Type: application/x-protobuf" \
-H "Accept: application/x-protobuf" \
--data-binary @- <<< ""
# Expected: Should not return 404 if collector is running// Use BenchmarkDotNet for performance analysis
[MemoryDiagnoser]
[ThreadingDiagnoser]
public class CachePerformanceBenchmark
{
private IMemoryCache _rawCache;
private IMemoryCache _meteredCache;
[GlobalSetup]
public void Setup()
{
_rawCache = new MemoryCache(new MemoryCacheOptions());
var meter = new Meter("Benchmark");
_meteredCache = new MeteredMemoryCache(_rawCache, meter, "benchmark");
}
[Benchmark(Baseline = true)]
public void RawCache_Operation() => _rawCache.Set("key", "value");
[Benchmark]
public void MeteredCache_Operation() => _meteredCache.Set("key", "value");
}Error: InvalidOperationException: No IMemoryCache registration found. Register IMemoryCache before calling DecorateMemoryCacheWithMetrics.
Solution: Register a base cache implementation first:
services.AddMemoryCache(); // Add this first
services.DecorateMemoryCacheWithMetrics("my-cache");Error: ArgumentException: Cache name must be non-empty (Parameter 'cacheName')
Solution: Provide a valid cache name:
// ❌ services.AddNamedMeteredMemoryCache("");
services.AddNamedMeteredMemoryCache("valid-cache-name"); // ✅Error: Multiple caches registered with the same name causing conflicts.
Solution: Use unique cache names or keyed services:
services.AddNamedMeteredMemoryCache("user-cache");
services.AddNamedMeteredMemoryCache("product-cache"); // Different nameError: Metrics stop working after application shutdown or container restart.
Solution: Ensure proper disposal order:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register MeterProvider as singleton, not scoped
services.AddSingleton<MeterProvider>(sp =>
Sdk.CreateMeterProviderBuilder()
.AddMeter("MyApp.Cache")
.Build());
}
}services.AddLogging(builder =>
{
builder.AddStructuredConsole(); // Or Serilog
builder.AddFilter("MeteredMemoryCache", LogLevel.Debug);
});services.AddHealthChecks()
.AddCheck<CacheHealthCheck>("cache-metrics");
public class CacheHealthCheck : IHealthCheck
{
private readonly IMemoryCache _cache;
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken)
{
try
{
// Test cache operations
_cache.Set("health-check", DateTime.UtcNow, TimeSpan.FromSeconds(1));
var retrieved = _cache.TryGetValue("health-check", out _);
return retrieved
? HealthCheckResult.Healthy("Cache is operational")
: HealthCheckResult.Degraded("Cache operations failing");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Cache is not responding", ex);
}
}
}// Add application insights or similar monitoring
services.AddApplicationInsightsTelemetry();
// Custom metrics for monitoring
services.AddSingleton<IHostedService, MetricsMonitoringService>();
public class MetricsMonitoringService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Check if metrics are flowing
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}Contact the development team or file an issue if you encounter:
- Unexpected exceptions not covered in this guide
- Performance degradation exceeding documented overhead (>50ns for reads, >100ns for writes)
- Memory leaks related to metric collection
- Thread safety issues in high-concurrency scenarios
- Integration problems with specific OpenTelemetry exporters
- MeteredMemoryCache Usage Guide
- OpenTelemetry Integration
- Performance Characteristics
- Multi-Cache Scenarios
For additional support, see the project's GitHub issues or discussions section.