Updated packages

This commit is contained in:
2026-06-01 18:04:57 +02:00
parent ad201a91ff
commit bace56ccb3
6 changed files with 130 additions and 17 deletions
@@ -25,7 +25,7 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Net.Http.Json" Version="10.0.7" /> <PackageReference Include="System.Net.Http.Json" Version="10.0.8" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\EonaCat.LogStack\EonaCat.LogStack.csproj" /> <ProjectReference Include="..\EonaCat.LogStack\EonaCat.LogStack.csproj" />
@@ -35,8 +35,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Diagnostics.EventLog" Version="10.0.7" /> <PackageReference Include="System.Diagnostics.EventLog" Version="10.0.8" />
<PackageReference Include="System.Threading.AccessControl" Version="10.0.7" /> <PackageReference Include="System.Threading.AccessControl" Version="10.0.8" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+7 -7
View File
@@ -66,18 +66,18 @@ It features a rich fluent API for routing log events to dozens of destinations f
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EonaCat.Json" Version="2.2.2" /> <PackageReference Include="EonaCat.Json" Version="2.2.3" />
<PackageReference Include="EonaCat.Versioning" Version="1.2.9"> <PackageReference Include="EonaCat.Versioning" Version="1.5.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.0.2" /> <PackageReference Include="EonaCat.Versioning.Helpers" Version="1.5.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.7" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.7" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.8" />
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Threading.Channels" Version="10.0.7" /> <PackageReference Include="System.Threading.Channels" Version="10.0.8" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="LICENSE.md"> <None Update="LICENSE.md">
@@ -15,24 +15,65 @@ namespace EonaCat.LogStack.Flows
{ {
private readonly IFlow _primary; private readonly IFlow _primary;
private readonly IFlow _secondary; private readonly IFlow _secondary;
private readonly TimeSpan _recoveryCheckInterval;
private readonly int _failureThreshold;
public FailoverFlow(IFlow primary, IFlow secondary) private IFlow _activeFlow;
private long _consecutiveFailures;
private DateTime _lastRecoveryCheck;
private CancellationTokenSource _recoveryCancellation;
private Task _recoveryTask;
private readonly object _syncLock = new object();
public FailoverFlow(
IFlow primary,
IFlow secondary,
TimeSpan? recoveryCheckInterval = null,
int failureThreshold = 5)
: base($"Failover({primary.Name})", primary.MinimumLevel) : base($"Failover({primary.Name})", primary.MinimumLevel)
{ {
_primary = primary ?? throw new ArgumentNullException(nameof(primary)); _primary = primary ?? throw new ArgumentNullException(nameof(primary));
_secondary = secondary ?? throw new ArgumentNullException(nameof(secondary)); _secondary = secondary ?? throw new ArgumentNullException(nameof(secondary));
_recoveryCheckInterval = recoveryCheckInterval ?? TimeSpan.FromSeconds(10);
_failureThreshold = failureThreshold;
_activeFlow = _primary;
_consecutiveFailures = 0;
_lastRecoveryCheck = DateTime.UtcNow;
_recoveryCancellation = new CancellationTokenSource();
_recoveryTask = StartRecoveryDetectionAsync();
} }
public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default) public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
{ {
var result = await _primary.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false); var result = await _activeFlow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
if (result == WriteResult.Success) if (result == WriteResult.Success)
{ {
// Reset failure counter on success
Interlocked.Exchange(ref _consecutiveFailures, 0);
return result; return result;
} }
return await _secondary.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false); // Only failover to secondary if using primary
if (_activeFlow == _primary)
{
var failures = Interlocked.Increment(ref _consecutiveFailures);
if (failures >= _failureThreshold)
{
lock (_syncLock)
{
// Double-check inside lock
if (_activeFlow == _primary && Interlocked.Read(ref _consecutiveFailures) >= _failureThreshold)
{
_activeFlow = _secondary;
Interlocked.Exchange(ref _consecutiveFailures, 0);
}
}
}
}
return result;
} }
public override async Task FlushAsync(CancellationToken cancellationToken = default) public override async Task FlushAsync(CancellationToken cancellationToken = default)
@@ -43,8 +84,80 @@ namespace EonaCat.LogStack.Flows
public override async ValueTask DisposeAsync() public override async ValueTask DisposeAsync()
{ {
_recoveryCancellation?.Cancel();
if (_recoveryTask != null)
{
try
{
await _recoveryTask.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Expected when cancelling the recovery task
}
}
_recoveryCancellation?.Dispose();
await _primary.DisposeAsync(); await _primary.DisposeAsync();
await _secondary.DisposeAsync(); await _secondary.DisposeAsync();
} }
private async Task StartRecoveryDetectionAsync()
{
try
{
while (!_recoveryCancellation.Token.IsCancellationRequested)
{
await Task.Delay(_recoveryCheckInterval, _recoveryCancellation.Token).ConfigureAwait(false);
// Only check recovery if we've switched to secondary
if (_activeFlow == _secondary)
{
await CheckPrimaryRecoveryAsync().ConfigureAwait(false);
}
}
}
catch (OperationCanceledException)
{
// Expected when shutting down
}
}
private async Task CheckPrimaryRecoveryAsync()
{
try
{
// Try to write a minimal test log to check if primary is healthy
var testEvent = new LogEvent
{
Level = LogLevel.Trace,
Message = new ReadOnlyMemory<char>("recovery_check".ToCharArray()),
Timestamp = DateTime.UtcNow.Ticks,
Category = "RecoveryDetection",
ThreadId = Thread.CurrentThread.ManagedThreadId
};
var result = await _primary.BlastAsync(testEvent, _recoveryCancellation.Token).ConfigureAwait(false);
if (result == WriteResult.Success)
{
lock (_syncLock)
{
if (_activeFlow == _secondary)
{
_activeFlow = _primary;
Interlocked.Exchange(ref _consecutiveFailures, 0);
_lastRecoveryCheck = DateTime.UtcNow;
}
}
}
}
catch
{
// Silently catch exceptions during recovery check
// The primary is still unhealthy
}
}
} }
} }
+2 -2
View File
@@ -433,7 +433,7 @@ public sealed class LogBuilder
/// <summary> /// <summary>
/// Adds a Failover flow that switches to a secondary flow if the primary fails. /// Adds a Failover flow that switches to a secondary flow if the primary fails.
/// </summary> /// </summary>
public LogBuilder WriteToFailover(IFlow primaryFlow, IFlow secondaryFlow) public LogBuilder WriteToFailover(IFlow primaryFlow, IFlow secondaryFlow, TimeSpan? recoveryCheckInterval = null, int failureThreshold = 5)
{ {
if (primaryFlow == null) if (primaryFlow == null)
{ {
@@ -445,7 +445,7 @@ public sealed class LogBuilder
throw new ArgumentNullException(nameof(secondaryFlow)); throw new ArgumentNullException(nameof(secondaryFlow));
} }
_flows.Add(new FailoverFlow(primaryFlow, secondaryFlow)); _flows.Add(new FailoverFlow(primaryFlow, secondaryFlow, recoveryCheckInterval, failureThreshold));
return this; return this;
} }
@@ -7,11 +7,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EonaCat.Versioning" Version="1.2.9"> <PackageReference Include="EonaCat.Versioning" Version="1.5.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.0.2" /> <PackageReference Include="EonaCat.Versioning.Helpers" Version="1.5.0" />
<PackageReference Include="EonaCat.Web.RateLimiter" Version="1.0.3" /> <PackageReference Include="EonaCat.Web.RateLimiter" Version="1.0.3" />
<PackageReference Include="EonaCat.Web.Tracer" Version="2.0.2" /> <PackageReference Include="EonaCat.Web.Tracer" Version="2.0.2" />
</ItemGroup> </ItemGroup>