Added more stats

Added more dependency injection
Made README.md better
This commit is contained in:
2026-06-11 08:17:29 +02:00
committed by Jeroen Saey
parent 001ed93ebb
commit dae624fd45
40 changed files with 11622 additions and 222 deletions
@@ -0,0 +1,179 @@
using EonaCat.LogStack.Core;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace EonaCat.LogStack.Flows;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
/// <summary>
/// Circuit-breaker state machine.
/// </summary>
public enum CircuitState
{
/// <summary>Flow is healthy; all events are forwarded.</summary>
Closed,
/// <summary>Too many failures; events are dropped to protect the inner flow.</summary>
Open,
/// <summary>Recovery probe in progress; one event is let through per interval.</summary>
HalfOpen
}
/// <summary>
/// A decorator flow that implements the Circuit Breaker pattern around any inner flow.
///
/// Transitions:
/// Closed → Open : after <see cref="FailureThreshold"/> consecutive failures
/// Open → HalfOpen : after <see cref="RecoveryTimeout"/> has elapsed
/// HalfOpen→ Closed : on a successful probe write
/// HalfOpen→ Open : on a failed probe write
///
/// When Open, <see cref="BlastAsync"/> returns <see cref="WriteResult.Dropped"/> immediately
/// without touching the inner flow — preventing cascades into broken endpoints.
///
/// The <see cref="StateChanged"/> event fires on every state transition.
/// </summary>
public sealed class CircuitBreakerFlow : FlowBase
{
private readonly IFlow _inner;
private readonly int _failureThreshold;
private readonly TimeSpan _recoveryTimeout;
private volatile int _consecutiveFailures;
private volatile CircuitState _state = CircuitState.Closed;
// Stopwatch restarted every time we enter Open state
private readonly Stopwatch _openedAt = new Stopwatch();
private readonly object _stateLock = new object();
/// <summary>Fires whenever the circuit state changes (from, to).</summary>
public event Action<CircuitState, CircuitState> StateChanged;
public CircuitBreakerFlow(
IFlow inner,
int failureThreshold = 5,
TimeSpan? recoveryTimeout = null,
LogLevel minimumLevel = LogLevel.Trace)
: base($"CircuitBreaker({(inner != null ? inner.Name : "?")})", minimumLevel)
{
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
_failureThreshold = failureThreshold > 0 ? failureThreshold : 5;
_recoveryTimeout = recoveryTimeout ?? TimeSpan.FromSeconds(30);
}
/// <summary>Current state of the circuit breaker.</summary>
public CircuitState State => _state;
/// <summary>Number of consecutive failures since the last successful write.</summary>
public int ConsecutiveFailures => _consecutiveFailures;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
{
if (!IsEnabled || !IsLogLevelEnabled(logEvent))
{
return WriteResult.LevelFiltered;
}
var currentState = GetEffectiveState();
if (currentState == CircuitState.Open)
{
Interlocked.Increment(ref DroppedCount);
return WriteResult.Dropped;
}
WriteResult result;
try
{
result = await _inner.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
}
catch (Exception)
{
result = WriteResult.Failed;
}
if (result == WriteResult.Success)
{
OnSuccess(currentState);
Interlocked.Increment(ref BlastedCount);
}
else
{
OnFailure();
Interlocked.Increment(ref DroppedCount);
}
return result;
}
public override Task FlushAsync(CancellationToken cancellationToken = default)
=> _inner.FlushAsync(cancellationToken);
public override ValueTask DisposeAsync() => _inner.DisposeAsync();
private CircuitState GetEffectiveState()
{
if (_state != CircuitState.Open)
{
return _state;
}
if (_openedAt.Elapsed >= _recoveryTimeout)
{
Transition(CircuitState.Open, CircuitState.HalfOpen);
return CircuitState.HalfOpen;
}
return CircuitState.Open;
}
private void OnSuccess(CircuitState wasState)
{
Interlocked.Exchange(ref _consecutiveFailures, 0);
if (wasState == CircuitState.HalfOpen)
{
Transition(CircuitState.HalfOpen, CircuitState.Closed);
}
}
private void OnFailure()
{
var failures = Interlocked.Increment(ref _consecutiveFailures);
if (_state == CircuitState.HalfOpen)
{
Transition(CircuitState.HalfOpen, CircuitState.Open);
}
else if (_state == CircuitState.Closed && failures >= _failureThreshold)
{
Transition(CircuitState.Closed, CircuitState.Open);
}
}
private void Transition(CircuitState from, CircuitState to)
{
lock (_stateLock)
{
if (_state != from)
{
return;
}
_state = to;
if (to == CircuitState.Open)
{
_openedAt.Restart();
}
else if (to == CircuitState.Closed)
{
_openedAt.Reset();
}
}
StateChanged?.Invoke(from, to);
}
}