diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs index 20493b066..e8b0d1077 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs @@ -46,6 +46,11 @@ namespace Squidex.Domain.Apps.Entities.Apps public Task LogAsync(DomainId appId, RequestLog request) { + if (!requestLogStore.IsEnabled) + { + return Task.CompletedTask; + } + var storedRequest = new Request { Key = appId.ToString(), diff --git a/backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs b/backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs index f8a21951b..0a765b168 100644 --- a/backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs +++ b/backend/src/Squidex.Infrastructure/Log/BackgroundRequestLogStore.cs @@ -10,6 +10,7 @@ using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Squidex.Infrastructure.Timers; using Squidex.Log; @@ -17,23 +18,30 @@ namespace Squidex.Infrastructure.Log { public sealed class BackgroundRequestLogStore : DisposableObjectBase, IRequestLogStore { - private const int Intervall = 10 * 1000; - private const int BatchSize = 1000; private readonly IRequestLogRepository logRepository; private readonly ISemanticLog log; private readonly CompletionTimer timer; + private readonly RequestLogStoreOptions options; private ConcurrentQueue jobs = new ConcurrentQueue(); - public BackgroundRequestLogStore(IRequestLogRepository logRepository, ISemanticLog log) + public bool IsEnabled { + get => options.StoreEnabled; + } + + public BackgroundRequestLogStore(IOptions options, + IRequestLogRepository logRepository, ISemanticLog log) + { + Guard.NotNull(options, nameof(options)); Guard.NotNull(logRepository, nameof(logRepository)); Guard.NotNull(log, nameof(log)); - this.logRepository = logRepository; + this.options = options.Value; + this.logRepository = logRepository; this.log = log; - timer = new CompletionTimer(Intervall, ct => TrackAsync(), Intervall); + timer = new CompletionTimer(options.Value.WriteIntervall, ct => TrackAsync(), options.Value.WriteIntervall); } protected override void DisposeObject(bool disposing) @@ -53,17 +61,24 @@ namespace Squidex.Infrastructure.Log private async Task TrackAsync() { + if (!IsEnabled) + { + return; + } + try { + var batchSize = options.BatchSize; + var localJobs = Interlocked.Exchange(ref jobs, new ConcurrentQueue()); if (!localJobs.IsEmpty) { - var pages = (int)Math.Ceiling((double)localJobs.Count / BatchSize); + var pages = (int)Math.Ceiling((double)localJobs.Count / batchSize); for (var i = 0; i < pages; i++) { - await logRepository.InsertManyAsync(localJobs.Skip(i * BatchSize).Take(BatchSize)); + await logRepository.InsertManyAsync(localJobs.Skip(i * batchSize).Take(batchSize)); } } } diff --git a/backend/src/Squidex.Infrastructure/Log/IRequestLogStore.cs b/backend/src/Squidex.Infrastructure/Log/IRequestLogStore.cs index 85616dc7e..21db736af 100644 --- a/backend/src/Squidex.Infrastructure/Log/IRequestLogStore.cs +++ b/backend/src/Squidex.Infrastructure/Log/IRequestLogStore.cs @@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.Log { public interface IRequestLogStore { + bool IsEnabled { get; } + Task LogAsync(Request request); Task QueryAllAsync(Func callback, string key, DateTime fromDate, DateTime toDate, CancellationToken ct = default); diff --git a/backend/src/Squidex.Infrastructure/Log/RequestLogStoreOptions.cs b/backend/src/Squidex.Infrastructure/Log/RequestLogStoreOptions.cs index 1eea04db6..c7da4b2d6 100644 --- a/backend/src/Squidex.Infrastructure/Log/RequestLogStoreOptions.cs +++ b/backend/src/Squidex.Infrastructure/Log/RequestLogStoreOptions.cs @@ -9,6 +9,12 @@ namespace Squidex.Infrastructure.Log { public sealed class RequestLogStoreOptions { + public bool StoreEnabled { get; set; } + public int StoreRetentionInDays { get; set; } = 90; + + public int BatchSize { get; set; } = 1000; + + public int WriteIntervall { get; set; } = 1000; } } diff --git a/backend/src/Squidex/appsettings.json b/backend/src/Squidex/appsettings.json index 4b4c11a73..900c4a141 100644 --- a/backend/src/Squidex/appsettings.json +++ b/backend/src/Squidex/appsettings.json @@ -338,6 +338,11 @@ */ "logProfiler": false, + /* + * False to disable the log store. + */ + "storeEnabled": true, + /* * The number of days request log items will be stored. */ diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppLogStoreTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppLogStoreTests.cs index 80c41e539..95cd357a9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppLogStoreTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/DefaultAppLogStoreTests.cs @@ -27,11 +27,26 @@ namespace Squidex.Domain.Apps.Entities.Apps sut = new DefaultAppLogStore(requestLogStore); } + [Fact] + public async Task Should_not_forward_request_if_disabled() + { + A.CallTo(() => requestLogStore.IsEnabled) + .Returns(false); + + await sut.LogAsync(DomainId.NewGuid(), default); + + A.CallTo(() => requestLogStore.LogAsync(A._)) + .MustNotHaveHappened(); + } + [Fact] public async Task Should_forward_request_log_to_store() { Request? recordedRequest = null; + A.CallTo(() => requestLogStore.IsEnabled) + .Returns(true); + A.CallTo(() => requestLogStore.LogAsync(A._)) .Invokes((Request request) => recordedRequest = request); diff --git a/backend/tests/Squidex.Infrastructure.Tests/Log/BackgroundRequestLogStoreTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Log/BackgroundRequestLogStoreTests.cs index 0fe626455..8fe3c8338 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Log/BackgroundRequestLogStoreTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Log/BackgroundRequestLogStoreTests.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FakeItEasy; +using Microsoft.Extensions.Options; using Squidex.Log; using Xunit; @@ -17,11 +18,41 @@ namespace Squidex.Infrastructure.Log public class BackgroundRequestLogStoreTests { private readonly IRequestLogRepository requestLogRepository = A.Fake(); + private readonly RequestLogStoreOptions options = new RequestLogStoreOptions(); private readonly BackgroundRequestLogStore sut; public BackgroundRequestLogStoreTests() { - sut = new BackgroundRequestLogStore(requestLogRepository, A.Fake()); + options.StoreEnabled = true; + + sut = new BackgroundRequestLogStore(Options.Create(options), requestLogRepository, A.Fake()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_provide_disabled_from_options(bool enabled) + { + options.StoreEnabled = enabled; + + Assert.Equal(enabled, sut.IsEnabled); + } + + [Fact] + public async Task Should_not_if_disabled() + { + options.StoreEnabled = false; + + for (var i = 0; i < 2500; i++) + { + await sut.LogAsync(new Request { Key = i.ToString() }); + } + + sut.Next(); + sut.Dispose(); + + A.CallTo(() => requestLogRepository.InsertManyAsync(A>._)) + .MustNotHaveHappened(); } [Fact]