Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public class SettingsModel
/// </summary>
public int? MaxRequestLogCount { get; set; }

/// <summary>
/// Gets or sets whether MaxRequestLogCount should be enforced using a background timer
/// instead of trimming synchronously on each request.
/// </summary>
public bool? SoftMaxRequestLogCountEnabled { get; set; }

/// <summary>
/// Allow a Body for all HTTP Methods. (default set to <c>false</c>).
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/WireMock.Net.Minimal/Owin/IWireMockMiddlewareOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ internal interface IWireMockMiddlewareOptions

int? MaxRequestLogCount { get; set; }

bool? SoftMaxRequestLogCountEnabled { get; set; }

Action<IAppBuilder>? PreWireMockMiddlewareInit { get; set; }

Action<IAppBuilder>? PostWireMockMiddlewareInit { get; set; }
Expand Down
16 changes: 12 additions & 4 deletions src/WireMock.Net.Minimal/Owin/WireMockMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,12 +368,20 @@ private void LogRequest(LogEntry entry, bool addRequest)
}

// In case MaxRequestLogCount has a value greater than 0, try to delete existing request logs based on the count.
if (_options.MaxRequestLogCount is > 0)
// Entries are always appended chronologically, so oldest are at the front - no sort needed.
if (_options.MaxRequestLogCount is > 0 && _options.SoftMaxRequestLogCountEnabled != true)
{
var logEntries = _options.LogEntries.ToList();
foreach (var logEntry in logEntries.OrderBy(le => le.RequestMessage.DateTime).Take(logEntries.Count - _options.MaxRequestLogCount.Value))
while (_options.LogEntries.Count > _options.MaxRequestLogCount.Value)
{
TryRemoveLogEntry(logEntry);
try
{
_options.LogEntries.RemoveAt(0);
}
catch
{
// Ignore exception (can happen during stress testing)
break;
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/WireMock.Net.Minimal/Owin/WireMockMiddlewareOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions

public int? MaxRequestLogCount { get; set; }

public bool? SoftMaxRequestLogCountEnabled { get; set; }

public Action<IAppBuilder>? PreWireMockMiddlewareInit { get; set; }

public Action<IAppBuilder>? PostWireMockMiddlewareInit { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static IWireMockMiddlewareOptions InitFromSettings(
options.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
options.Logger = settings.Logger;
options.MaxRequestLogCount = settings.MaxRequestLogCount;
options.SoftMaxRequestLogCountEnabled = settings.SoftMaxRequestLogCountEnabled;
options.PostWireMockMiddlewareInit = settings.PostWireMockMiddlewareInit;
options.PreWireMockMiddlewareInit = settings.PreWireMockMiddlewareInit;
options.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
Expand Down
2 changes: 2 additions & 0 deletions src/WireMock.Net.Minimal/Server/WireMockServer.Admin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ private IResponseMessage SettingsGet(IRequestMessage requestMessage)
HandleRequestsSynchronously = _settings.HandleRequestsSynchronously,
HostingScheme = _settings.HostingScheme,
MaxRequestLogCount = _settings.MaxRequestLogCount,
SoftMaxRequestLogCountEnabled = _settings.SoftMaxRequestLogCountEnabled,
ProtoDefinitions = _settings.ProtoDefinitions,
QueryParameterMultipleValueSupport = _settings.QueryParameterMultipleValueSupport,
ReadStaticMappings = _settings.ReadStaticMappings,
Expand Down Expand Up @@ -321,6 +322,7 @@ private IResponseMessage SettingsUpdate(IRequestMessage requestMessage)
_settings.DoNotSaveDynamicResponseInLogEntry = settings.DoNotSaveDynamicResponseInLogEntry;
_settings.HandleRequestsSynchronously = settings.HandleRequestsSynchronously;
_settings.MaxRequestLogCount = settings.MaxRequestLogCount;
_settings.SoftMaxRequestLogCountEnabled = settings.SoftMaxRequestLogCountEnabled;
_settings.ProtoDefinitions = settings.ProtoDefinitions;
_settings.ProxyAndRecordSettings = TinyMapperUtils.Instance.Map(settings.ProxyAndRecordSettings);
_settings.QueryParameterMultipleValueSupport = settings.QueryParameterMultipleValueSupport;
Expand Down
37 changes: 37 additions & 0 deletions src/WireMock.Net.Minimal/Server/WireMockServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public partial class WireMockServer : IWireMockServer
private readonly MatcherMapper _matcherMapper;
private readonly MappingToFileSaver _mappingToFileSaver;
private readonly MappingBuilder _mappingBuilder;
private Timer? _softMaxLogTrimTimer;
private readonly IGuidUtils _guidUtils = new GuidUtils();
private readonly IDateTimeUtils _dateTimeUtils = new DateTimeUtils();
private readonly MappingSerializer _mappingSerializer;
Expand Down Expand Up @@ -116,6 +117,8 @@ public void Dispose()
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
_softMaxLogTrimTimer?.Dispose();
_softMaxLogTrimTimer = null;
DisposeEnhancedFileSystemWatcher();
_httpServer?.StopAsync();
}
Expand Down Expand Up @@ -764,5 +767,39 @@ private void InitSettings(WireMockServerSettings settings)
{
SetMaxRequestLogCount(settings.MaxRequestLogCount);
}

// Start or stop the soft max log trim timer based on settings
_softMaxLogTrimTimer?.Dispose();
_softMaxLogTrimTimer = null;
if (settings.SoftMaxRequestLogCountEnabled == true && settings.MaxRequestLogCount is > 0)
{
_softMaxLogTrimTimer = new Timer(
_ => TrimExcessLogEntries(),
null,
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(5));
}
}

private void TrimExcessLogEntries()
{
try
{
var maxCount = _options.MaxRequestLogCount;
if (maxCount is not > 0) return;

var logEntries = _options.LogEntries.ToList();
var excess = logEntries.Count - maxCount.Value;
if (excess <= 0) return;

foreach (var entry in logEntries.OrderBy(e => e.RequestMessage.DateTime).Take(excess))
{
try { _options.LogEntries.Remove(entry); } catch { /* concurrent removal is safe */ }
}
}
catch
{
// Timer callback must never throw -- prevents timer from stopping
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public static bool TryParseArguments(string[] args, IDictionary? environment, [N
HandleRequestsSynchronously = parser.GetBoolValue(nameof(WireMockServerSettings.HandleRequestsSynchronously)),
HostingScheme = parser.GetEnumValue<HostingScheme>(nameof(WireMockServerSettings.HostingScheme)),
MaxRequestLogCount = parser.GetIntValue(nameof(WireMockServerSettings.MaxRequestLogCount)),
SoftMaxRequestLogCountEnabled = parser.GetBoolValue(nameof(WireMockServerSettings.SoftMaxRequestLogCountEnabled)),
ProtoDefinitions = parser.GetObjectValueFromJson<Dictionary<string, string[]>>(nameof(settings.ProtoDefinitions)),
QueryParameterMultipleValueSupport = parser.GetEnumValue<QueryParameterMultipleValueSupport>(nameof(WireMockServerSettings.QueryParameterMultipleValueSupport)),
ReadStaticMappings = parser.GetBoolValue(nameof(WireMockServerSettings.ReadStaticMappings)),
Expand Down
26 changes: 26 additions & 0 deletions src/WireMock.Net.Minimal/Util/ConcurrentObservableCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,32 @@ protected override void MoveItem(int oldIndex, int newIndex)
}
}

/// <summary>
/// Gets the number of elements contained in the collection (thread-safe).
/// </summary>
public new int Count
{
get
{
lock (_lockObject)
{
return Items.Count;
}
}
}

/// <summary>
/// Removes the element at the specified index of the collection (thread-safe).
/// </summary>
/// <param name="index">The zero-based index of the element to remove.</param>
public new void RemoveAt(int index)
{
lock (_lockObject)
{
base.RemoveItem(index);
}
}

public List<T> ToList()
{
lock (_lockObject)
Expand Down
8 changes: 8 additions & 0 deletions src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ public class WireMockServerSettings
[PublicAPI]
public int? MaxRequestLogCount { get; set; }

/// <summary>
/// Gets or sets whether MaxRequestLogCount should be enforced using a background timer
/// instead of trimming synchronously on each request. When enabled, log trimming happens
/// periodically in the background, reducing request processing latency.
/// </summary>
[PublicAPI]
public bool? SoftMaxRequestLogCountEnabled { get; set; }

/// <summary>
/// Action which is called (with the IAppBuilder or IApplicationBuilder) before the internal WireMockMiddleware is initialized. [Optional]
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions test/WireMock.Net.Tests/WireMockServer.Admin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,25 @@ public async Task WireMockServer_Admin_Logging_SetMaxRequestLogCount_To_0_Should
server.Stop();
}

[Fact]
public async Task WireMockServer_Admin_Logging_SetMaxRequestLogCount_HighVolume()
{
// Arrange
using var client = new HttpClient();
using var server = WireMockServer.Start();
server.SetMaxRequestLogCount(5);

// Act - send 50 requests
for (int i = 0; i < 50; i++)
{
await client.GetAsync($"http://localhost:{server.Ports[0]}/req{i}").ConfigureAwait(false);
}

// Assert - should have exactly 5 entries, the most recent ones
server.LogEntries.Should().HaveCount(5);
server.LogEntries.Last().RequestMessage.Path.Should().EndWith("/req49");
}

[Fact]
public async Task WireMockServer_Admin_Logging_FindLogEntries()
{
Expand Down
Loading
Loading