Add exception and logging to Redis cache layer (#154)
This adds initial infrastructure to handle logging for requests. As part of this, Redis-related exceptions are handled by just going to Azure directly but logging to raise visibility of error.
This commit is contained in:
Родитель
62dffb6952
Коммит
1eb8ece63d
|
@ -1,4 +1,5 @@
|
|||
using StackExchange.Redis;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
using WWTWebservices;
|
||||
|
||||
|
@ -7,19 +8,35 @@ namespace WWT.PlateFiles.Caching
|
|||
public class RedisCachedPlateTilePyramid : CachedPlateTilePyramid
|
||||
{
|
||||
private readonly IConnectionMultiplexer _connection;
|
||||
private readonly ILogger<RedisCachedPlateTilePyramid> _logger;
|
||||
private readonly TimeSpan _expiry;
|
||||
|
||||
public RedisCachedPlateTilePyramid(
|
||||
IPlateTilePyramid other,
|
||||
IConnectionMultiplexer connection,
|
||||
ILogger<RedisCachedPlateTilePyramid> logger,
|
||||
CachingOptions options)
|
||||
: base(other)
|
||||
{
|
||||
_connection = connection;
|
||||
_logger = logger;
|
||||
_expiry = options.SlidingExpiration;
|
||||
}
|
||||
|
||||
protected override byte[] GetOrUpdateCache(TileContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetFromCache(context);
|
||||
}
|
||||
catch (RedisException e)
|
||||
{
|
||||
_logger.LogError(e, "Error accessing cache. Defaulting to direct Azure access.");
|
||||
return context.GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetFromCache(TileContext context)
|
||||
{
|
||||
var key = context.GetKey();
|
||||
|
||||
|
@ -28,10 +45,13 @@ namespace WWT.PlateFiles.Caching
|
|||
|
||||
if (cached.HasValue)
|
||||
{
|
||||
_logger.LogInformation("Using cached value for request");
|
||||
return cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No cached value found. Retrieving from Azure and saving in cache");
|
||||
|
||||
var result = context.GetResult();
|
||||
|
||||
// No need to block for the transfer to actually complete. The caching is done asynchronously and
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.1.58" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.9" />
|
||||
<PackageReference Include="Scrutor" Version="3.2.2" />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.IdentityModel.Services;
|
||||
|
@ -8,11 +9,10 @@ using System.Web;
|
|||
using System.Web.Mvc;
|
||||
using System.Web.Optimization;
|
||||
using System.Web.Routing;
|
||||
using Unity;
|
||||
using Unity.AspNet.Mvc;
|
||||
using WWT;
|
||||
using WWT.Azure;
|
||||
using WWT.Providers;
|
||||
using WWT;
|
||||
using WWTWebservices;
|
||||
|
||||
namespace WWTMVC5
|
||||
|
@ -102,6 +102,11 @@ namespace WWTMVC5
|
|||
options.SlidingExpiration = TimeSpan.Parse(ConfigurationManager.AppSettings["SlidingExpiration"]);
|
||||
});
|
||||
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
builder.AddDebug();
|
||||
});
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1359,6 +1359,9 @@
|
|||
<PackageReference Include="Microsoft.Extensions.DependencyInjection">
|
||||
<Version>3.1.9</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug">
|
||||
<Version>3.1.9</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.IdentityModel">
|
||||
<Version>6.1.7600.16394</Version>
|
||||
</PackageReference>
|
||||
|
|
|
@ -9,16 +9,6 @@
|
|||
<section name="configBuilders" type="System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false"/>
|
||||
</configSections>
|
||||
|
||||
<system.diagnostics>
|
||||
<trace>
|
||||
<listeners>
|
||||
<add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics">
|
||||
<filter type=""/>
|
||||
</add>
|
||||
</listeners>
|
||||
</trace>
|
||||
</system.diagnostics>
|
||||
|
||||
<configBuilders>
|
||||
<builders>
|
||||
<add name="Environment" mode="Greedy" type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Environment"/>
|
||||
|
@ -81,7 +71,7 @@ Content-Type: application/x-wt-->
|
|||
<add name="gettourfile" verb="*" path="GetTourFile.aspx" type="WWTMVC5.WwtWebHttpHandler, WWTMVC5" preCondition="managedHandler"/>
|
||||
<add name="gettourfile2" verb="*" path="GetTourFile2.aspx" type="WWTMVC5.WwtWebHttpHandler, WWTMVC5" preCondition="managedHandler"/>
|
||||
</handlers>
|
||||
<modules runAllManagedModulesForAllRequests="false" />
|
||||
<modules runAllManagedModulesForAllRequests="false"/>
|
||||
</system.webServer>
|
||||
|
||||
<appSettings configBuilders="Environment,Secrets,KeyVault">
|
||||
|
@ -148,9 +138,9 @@ Content-Type: application/x-wt-->
|
|||
<add key="DefaultThumbnail" value="star"/>
|
||||
|
||||
<!-- Caching settings -->
|
||||
<add key="UseCaching" value="false" />
|
||||
<add key="RedisConnectionString" value="" />
|
||||
<add key="SlidingExpiration" value="1.00:00:00" />
|
||||
<add key="UseCaching" value="false"/>
|
||||
<add key="RedisConnectionString" value=""/>
|
||||
<add key="SlidingExpiration" value="1.00:00:00"/>
|
||||
|
||||
<!-- Overridden with Configuration Builders -->
|
||||
<add key="TourCache" value=""/>
|
||||
|
@ -219,6 +209,18 @@ Content-Type: application/x-wt-->
|
|||
</entityFramework>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.Logging" publicKeyToken="ADB9793829DDAE60" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.1.9.0" newVersion="3.1.9.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection" publicKeyToken="ADB9793829DDAE60" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.1.9.0" newVersion="3.1.9.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.Configuration.Abstractions" publicKeyToken="ADB9793829DDAE60" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.1.9.0" newVersion="3.1.9.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.Primitives" publicKeyToken="ADB9793829DDAE60" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.1.9.0" newVersion="3.1.9.0"/>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web;
|
||||
|
@ -25,6 +26,8 @@ namespace WWTMVC5
|
|||
context.Response.ContentType = scope.ContentType;
|
||||
}
|
||||
|
||||
scope.Resolve<ILogger<WwtWebHttpHandler>>().LogInformation("Dispatch {Path} to {Provider}", context.Request.Path, scope.Provider.GetType());
|
||||
|
||||
scope.Provider.Run(new SystemWebWwtContext(context));
|
||||
}
|
||||
}
|
||||
|
@ -177,6 +180,8 @@ namespace WWTMVC5
|
|||
|
||||
public RequestProvider Provider { get; }
|
||||
|
||||
public T Resolve<T>() => _scope.ServiceProvider.GetRequiredService<T>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_scope.Dispose();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using Autofac;
|
||||
using Autofac.Extensions.DependencyInjection;
|
||||
using AutofacContrib.NSubstitute;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using NSubstitute.Extensions;
|
||||
using System;
|
||||
|
@ -9,6 +11,16 @@ namespace WWT
|
|||
{
|
||||
internal static class AutoSubstituteExtensions
|
||||
{
|
||||
public static AutoSubstituteBuilder ConfigureServices(this AutoSubstituteBuilder builder, Action<IServiceCollection> configure)
|
||||
=> builder.ConfigureBuilder(b =>
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
configure(services);
|
||||
|
||||
b.Populate(services);
|
||||
});
|
||||
|
||||
public static SubstituteForBuilder<T> ResolveReturnValue<T, TResult>(this SubstituteForBuilder<T> builder, Func<T, TResult> action)
|
||||
where T : class
|
||||
=> builder.ConfigureSubstitute((t, ctx) =>
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
<PackageReference Include="AutoFixture" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.9" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using AutoFixture;
|
||||
using AutofacContrib.NSubstitute;
|
||||
using AutoFixture;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using StackExchange.Redis;
|
||||
|
@ -20,20 +21,21 @@ namespace WWT.PlateFiles.Caching.Tests
|
|||
public void NoCachingByDefault()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
|
||||
var original = Substitute.For<IPlateTilePyramid>();
|
||||
services.AddSingleton(original);
|
||||
|
||||
services.AddCaching(options =>
|
||||
{
|
||||
options.UseCaching = false;
|
||||
});
|
||||
|
||||
using var provider = services.BuildServiceProvider();
|
||||
var mock = AutoSubstitute.Configure()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton(original);
|
||||
services.AddCaching(options =>
|
||||
{
|
||||
options.UseCaching = false;
|
||||
});
|
||||
})
|
||||
.SubstituteFor<IConnectionMultiplexer>()
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var resolved = provider.GetRequiredService<IPlateTilePyramid>();
|
||||
var resolved = mock.Resolve<IPlateTilePyramid>();
|
||||
|
||||
// Assert
|
||||
Assert.Same(original, resolved);
|
||||
|
@ -43,20 +45,21 @@ namespace WWT.PlateFiles.Caching.Tests
|
|||
public void InMemoryWhenNoRedisConnection()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
|
||||
var original = Substitute.For<IPlateTilePyramid>();
|
||||
services.AddSingleton(original);
|
||||
|
||||
services.AddCaching(options =>
|
||||
{
|
||||
options.UseCaching = true;
|
||||
});
|
||||
|
||||
using var provider = services.BuildServiceProvider();
|
||||
var mock = AutoSubstitute.Configure()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton(original);
|
||||
services.AddCaching(options =>
|
||||
{
|
||||
options.UseCaching = true;
|
||||
});
|
||||
})
|
||||
.SubstituteFor<IConnectionMultiplexer>()
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var resolved = provider.GetRequiredService<IPlateTilePyramid>();
|
||||
var resolved = mock.Resolve<IPlateTilePyramid>();
|
||||
|
||||
// Assert
|
||||
var inMemory = Assert.IsType<InMemoryCachedPlateTilePyramid>(resolved);
|
||||
|
@ -68,23 +71,22 @@ namespace WWT.PlateFiles.Caching.Tests
|
|||
public void RedisUsedWithConnectionString()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
|
||||
var original = Substitute.For<IPlateTilePyramid>();
|
||||
services.AddSingleton(original);
|
||||
|
||||
services.AddCaching(options =>
|
||||
{
|
||||
options.UseCaching = true;
|
||||
options.RedisCacheConnectionString = _fixture.Create<string>();
|
||||
});
|
||||
|
||||
services.AddSingleton(Substitute.For<IConnectionMultiplexer>());
|
||||
|
||||
using var provider = services.BuildServiceProvider();
|
||||
var mock = AutoSubstitute.Configure()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton(original);
|
||||
services.AddCaching(options =>
|
||||
{
|
||||
options.UseCaching = true;
|
||||
options.RedisCacheConnectionString = _fixture.Create<string>();
|
||||
});
|
||||
})
|
||||
.SubstituteFor<IConnectionMultiplexer>()
|
||||
.Build();
|
||||
|
||||
// Act
|
||||
var resolved = provider.GetRequiredService<IPlateTilePyramid>();
|
||||
var resolved = mock.Resolve<IPlateTilePyramid>();
|
||||
|
||||
// Assert
|
||||
var redis = Assert.IsType<RedisCachedPlateTilePyramid>(resolved);
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
<TargetFramework>net48</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\WWT.PlateFiles.Caching\WWT.PlateFiles.Caching.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
Загрузка…
Ссылка в новой задаче