Skip to content

Commit

Permalink
Log db operations (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexMacocian authored Nov 28, 2023
1 parent 19dcad7 commit b9b9f9a
Show file tree
Hide file tree
Showing 14 changed files with 630 additions and 32 deletions.
27 changes: 25 additions & 2 deletions GuildWarsPartySearch/Config.Debug.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
{
"IpRateLimitOptions": {
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": false,
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 20
},
{
"Endpoint": "*",
"Period": "10m",
"Limit": 100
},
{
"Endpoint": "*",
"Period": "12h",
"Limit": 3000
}
]
},
"Logging": {
"LogLevel": {
"Default": "Debug",
Expand All @@ -16,11 +37,13 @@
"HeartbeatFrequency": "0:0:1"
},
"StorageAccountOptions": {
"TableName": "searches",
"ContainerName": "content",
"ConnectionString": "[AZURE_TABLESTORAGE_CONNECTIONSTRING]"
},
"PartySearchTableOptions": {
"TableName": "searches"
},
"ContentOptions": {
"ContainerName": "content",
"UpdateFrequency": "0:5:0",
"StagingFolder": "Content"
}
Expand Down
27 changes: 25 additions & 2 deletions GuildWarsPartySearch/Config.Release.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
{
"IpRateLimitOptions": {
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": false,
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 20
},
{
"Endpoint": "*",
"Period": "10m",
"Limit": 100
},
{
"Endpoint": "*",
"Period": "12h",
"Limit": 3000
}
]
},
"Logging": {
"LogLevel": {
"Default": "Information",
Expand All @@ -16,11 +37,13 @@
"HeartbeatFrequency": "0:0:1"
},
"StorageAccountOptions": {
"TableName": "searches",
"ContainerName": "content",
"ConnectionString": "[AZURE_TABLESTORAGE_CONNECTIONSTRING]"
},
"PartySearchTableOptions": {
"TableName": "searches"
},
"ContentOptions": {
"ContainerName": "content",
"UpdateFrequency": "0:1:0",
"StagingFolder": "Content"
}
Expand Down
67 changes: 67 additions & 0 deletions GuildWarsPartySearch/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using GuildWarsPartySearch.Server.Options;
using GuildWarsPartySearch.Server.Services.Azure;
using Microsoft.Extensions.Options;
using System.Core.Extensions;

namespace GuildWarsPartySearch.Server.Extensions;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddSingletonTableClient<TOptions>(this IServiceCollection services)
where TOptions : class, IAzureTableStorageOptions, new()
{
services.ThrowIfNull()
.AddSingleton(sp =>
{
var storageOptions = sp.GetRequiredService<IOptions<StorageAccountOptions>>();
var clientOptions = sp.GetRequiredService<IOptions<TOptions>>();
var logger = sp.GetRequiredService<ILogger<NamedTableClient<TOptions>>>();
return new NamedTableClient<TOptions>(logger, storageOptions.Value.ConnectionString!, clientOptions.Value.TableName);
});

return services;
}

public static IServiceCollection AddScopedTableClient<TOptions>(this IServiceCollection services)
where TOptions : class, IAzureTableStorageOptions, new()
{
services.ThrowIfNull()
.AddScoped(sp =>
{
var storageOptions = sp.GetRequiredService<IOptions<StorageAccountOptions>>();
var clientOptions = sp.GetRequiredService<IOptions<TOptions>>();
var logger = sp.GetRequiredService<ILogger<NamedTableClient<TOptions>>>();
return new NamedTableClient<TOptions>(logger, storageOptions.Value.ConnectionString!, clientOptions.Value.TableName);
});

return services;
}

public static IServiceCollection AddSingletonBlobContainerClient<TOptions>(this IServiceCollection services)
where TOptions : class, IAzureBlobStorageOptions, new()
{
services.ThrowIfNull()
.AddSingleton(sp =>
{
var storageOptions = sp.GetRequiredService<IOptions<StorageAccountOptions>>();
var clientOptions = sp.GetRequiredService<IOptions<TOptions>>();
return new NamedBlobContainerClient<TOptions>(storageOptions.Value.ConnectionString!, clientOptions.Value.ContainerName);
});

return services;
}

public static IServiceCollection AddScopedBlobContainerClient<TOptions>(this IServiceCollection services)
where TOptions : class, IAzureBlobStorageOptions, new()
{
services.ThrowIfNull()
.AddScoped(sp =>
{
var storageOptions = sp.GetRequiredService<IOptions<StorageAccountOptions>>();
var clientOptions = sp.GetRequiredService<IOptions<TOptions>>();
return new NamedBlobContainerClient<TOptions>(storageOptions.Value.ConnectionString!, clientOptions.Value.ContainerName);
});

return services;
}
}
10 changes: 8 additions & 2 deletions GuildWarsPartySearch/Launch/ServerConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AspNetCoreRateLimit;
using Azure.Data.Tables;
using GuildWarsPartySearch.Common.Converters;
using GuildWarsPartySearch.Server.Endpoints;
using GuildWarsPartySearch.Server.Extensions;
Expand All @@ -10,6 +11,8 @@
using GuildWarsPartySearch.Server.Services.Feed;
using GuildWarsPartySearch.Server.Services.Lifetime;
using GuildWarsPartySearch.Server.Services.PartySearch;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Options;
using System.Core.Extensions;
using System.Extensions;
using System.Text.Json.Serialization;
Expand Down Expand Up @@ -61,10 +64,11 @@ public static WebApplicationBuilder SetupOptions(this WebApplicationBuilder buil
builder.ThrowIfNull()
.Services.Configure<EnvironmentOptions>(builder.Configuration.GetSection(nameof(EnvironmentOptions)))
.Configure<ContentOptions>(builder.Configuration.GetSection(nameof(ContentOptions)))
.Configure<PartySearchTableOptions>(builder.Configuration.GetSection(nameof(PartySearchTableOptions)))
.Configure<StorageAccountOptions>(builder.Configuration.GetSection(nameof(StorageAccountOptions)))
.Configure<ServerOptions>(builder.Configuration.GetSection(nameof(ServerOptions)))
.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"))
.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection("IpRateLimitPolicies"));
.Configure<IpRateLimitOptions>(builder.Configuration.GetSection(nameof(IpRateLimitOptions)))
.Configure<IpRateLimitPolicies>(builder.Configuration.GetSection(nameof(IpRateLimitPolicies)));

return builder;
}
Expand All @@ -82,6 +86,8 @@ public static IServiceCollection SetupServices(this IServiceCollection services)
services.AddScoped<ICharNameValidator, CharNameValidator>();
services.AddSingleton<ILiveFeedService, LiveFeedService>();
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
services.AddScopedTableClient<PartySearchTableOptions>();
services.AddSingletonBlobContainerClient<ContentOptions>();
return services;
}

Expand Down
3 changes: 2 additions & 1 deletion GuildWarsPartySearch/Options/ContentOptions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace GuildWarsPartySearch.Server.Options;

public sealed class ContentOptions
public sealed class ContentOptions : IAzureBlobStorageOptions
{
public TimeSpan UpdateFrequency { get; set; } = TimeSpan.FromMinutes(5);
public string StagingFolder { get; set; } = "Content";
public string ContainerName { get; set; } = default!;
}
6 changes: 6 additions & 0 deletions GuildWarsPartySearch/Options/IAzureBlobStorageOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace GuildWarsPartySearch.Server.Options;

public interface IAzureBlobStorageOptions
{
string ContainerName { get; set; }
}
6 changes: 6 additions & 0 deletions GuildWarsPartySearch/Options/IAzureTableStorageOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace GuildWarsPartySearch.Server.Options;

public interface IAzureTableStorageOptions
{
string TableName { get; set; }
}
6 changes: 6 additions & 0 deletions GuildWarsPartySearch/Options/PartySearchTableOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace GuildWarsPartySearch.Server.Options;

public class PartySearchTableOptions : IAzureTableStorageOptions
{
public string TableName { get; set; } = default!;
}
35 changes: 35 additions & 0 deletions GuildWarsPartySearch/Services/Azure/InterceptingAsyncPageable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Azure;

namespace GuildWarsPartySearch.Server.Services.Azure;

public class InterceptingAsyncPageable<T> : AsyncPageable<T> where T : notnull
{
private readonly Action<Page<T>> interceptPage;
private readonly Action interceptSuccess;
private readonly AsyncPageable<T> originalPageable;

public InterceptingAsyncPageable(AsyncPageable<T> originalPageable, Action<Page<T>> interceptPage, Action interceptSuccess)
{
this.originalPageable = originalPageable;
this.interceptPage = interceptPage;
this.interceptSuccess = interceptSuccess;
}

public override async IAsyncEnumerable<Page<T>> AsPages(string? continuationToken = null, int? pageSizeHint = null)
{
await foreach (var page in this.originalPageable.AsPages(continuationToken, pageSizeHint))
{
this.interceptPage(page);
yield return page;
}
}

public async override IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
await foreach(var item in this.originalPageable)
{
this.interceptSuccess();
yield return item;
}
}
}
35 changes: 35 additions & 0 deletions GuildWarsPartySearch/Services/Azure/InterceptingPageable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Azure;

namespace GuildWarsPartySearch.Server.Services.Azure;

public sealed class InterceptingPageable<T> : Pageable<T> where T: notnull
{
private readonly Action<Page<T>> interceptPage;
private readonly Action interceptSuccess;
private readonly Pageable<T> originalPageable;

public InterceptingPageable(Pageable<T> originalPageable, Action<Page<T>> interceptPage, Action interceptSuccess)
{
this.originalPageable = originalPageable;
this.interceptPage = interceptPage;
this.interceptSuccess = interceptSuccess;
}

public override IEnumerable<Page<T>> AsPages(string? continuationToken = null, int? pageSizeHint = null)
{
foreach (var page in this.originalPageable.AsPages(continuationToken, pageSizeHint))
{
this.interceptPage(page);
yield return page;
}
}

public override IEnumerator<T> GetEnumerator()
{
foreach (var item in this.originalPageable)
{
this.interceptSuccess();
yield return item;
}
}
}
40 changes: 40 additions & 0 deletions GuildWarsPartySearch/Services/Azure/NamedBlobContainerClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Azure;
using Azure.Core;
using Azure.Storage;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using GuildWarsPartySearch.Server.Options;

namespace GuildWarsPartySearch.Server.Services.Azure;

public class NamedBlobContainerClient<TOptions> : BlobContainerClient
where TOptions : IAzureBlobStorageOptions
{
public NamedBlobContainerClient(string connectionString, string blobContainerName) : base(connectionString, blobContainerName)
{
}

public NamedBlobContainerClient(Uri blobContainerUri, BlobClientOptions? options = null) : base(blobContainerUri, options)
{
}

public NamedBlobContainerClient(string connectionString, string blobContainerName, BlobClientOptions options) : base(connectionString, blobContainerName, options)
{
}

public NamedBlobContainerClient(Uri blobContainerUri, StorageSharedKeyCredential credential, BlobClientOptions? options = null) : base(blobContainerUri, credential, options)
{
}

public NamedBlobContainerClient(Uri blobContainerUri, AzureSasCredential credential, BlobClientOptions? options = null) : base(blobContainerUri, credential, options)
{
}

public NamedBlobContainerClient(Uri blobContainerUri, TokenCredential credential, BlobClientOptions? options = null) : base(blobContainerUri, credential, options)
{
}

protected NamedBlobContainerClient()
{
}
}
Loading

0 comments on commit b9b9f9a

Please sign in to comment.