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:
Taylor Southwick 2020-10-26 13:31:57 -07:00 коммит произвёл GitHub
Родитель 62dffb6952
Коммит 1eb8ece63d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 106 добавлений и 58 удалений

Просмотреть файл

@ -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>